Skip to content

Commit a6d9fa1

Browse files
committed
Merge remote-tracking branch 'upstream/develop' into feature/pbs-26-9
Conflicts not fixed in admin_tests/nodes/test_views.py
2 parents ebde26c + 0abef37 commit a6d9fa1

13 files changed

Lines changed: 255 additions & 69 deletions

File tree

CHANGELOG

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO.
44

5+
26.10.4 (2026-06-04)
6+
====================
7+
8+
- Fix duplication in scheduled notifications
9+
10+
26.10.3 (2026-06-02)
11+
====================
12+
13+
- Update how invalid cookie/session is handled
14+
15+
26.10.2 (2026-05-27)
16+
====================
17+
18+
- Fix admin embargo report page by paging
19+
20+
26.10.1 (2026-05-22)
21+
====================
22+
23+
- Hotfix to resume (rather than restart) a long task after connection error
24+
525
26.10.0 (2026-05-19)
626
===================
727

admin/nodes/views.py

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from django.contrib import messages
77
from django.contrib.auth.mixins import PermissionRequiredMixin
88
from django.core.exceptions import PermissionDenied, ValidationError
9-
from django.db.models import F, Case, When, IntegerField
9+
from django.db.models import F, Case, When, IntegerField, Prefetch
1010
from django.http import HttpResponse
1111
from django.shortcuts import redirect, reverse, get_object_or_404
1212
from django.urls import NoReverseMatch
@@ -515,42 +515,50 @@ class EmbargoReportView(PermissionRequiredMixin, TemplateView):
515515
- active embargoes that are past their end date
516516
- upcoming active embargoes
517517
"""
518+
518519
template_name = 'nodes/embargo_report.html'
519520
permission_required = 'osf.view_registration'
520521
raise_exception = True
521522

522-
def get_context_data(self, **kwargs):
523-
context = super().get_context_data(**kwargs)
524-
pending_embargoes = Embargo.objects.pending_embargoes().select_related('initiated_by')
525-
active_embargoes = Embargo.objects.active_embargoes().select_related('initiated_by')
526-
527-
pending_overdue_embargoes = [
528-
embargo for embargo in pending_embargoes
529-
if embargo.should_be_embargoed
530-
]
531-
532-
overdue_embargoes = [
533-
embargo for embargo in active_embargoes
534-
if embargo.should_be_completed
535-
]
536-
537-
upcoming_queryset = active_embargoes.filter(
538-
end_date__gte=timezone.now(),
539-
).order_by('end_date')
523+
def _embargo_report_queryset(self, queryset):
524+
return queryset.select_related('initiated_by').prefetch_related(
525+
Prefetch(
526+
'registrations',
527+
queryset=Registration.objects.filter(is_deleted=False).prefetch_related(
528+
'guids',
529+
).only('id', 'title', 'is_public', 'embargo_id'),
530+
),
531+
)
540532

541-
page_number = self.request.GET.get('page') or 1
542-
paginator = Paginator(upcoming_queryset, 10)
533+
def paginate_embargo_report(self, request, queryset, page_param):
534+
paginator = Paginator(queryset, settings.EMBARGO_REPORT_PAGE_SIZE)
535+
page_number = request.GET.get(page_param) or 1
543536
try:
544-
upcoming_page = paginator.page(page_number)
537+
return paginator.page(page_number)
545538
except InvalidPage:
546-
upcoming_page = paginator.page(1)
539+
return paginator.page(1)
540+
541+
def get_context_data(self, **kwargs):
542+
context = super().get_context_data(**kwargs)
543+
request = self.request
547544

548545
context.update({
549546
'now': timezone.now(),
550-
'pending_overdue_embargoes': pending_overdue_embargoes,
551-
'overdue_embargoes': overdue_embargoes,
552-
'upcoming_embargoes': upcoming_page.object_list,
553-
'upcoming_page': upcoming_page,
547+
'upcoming_page': self.paginate_embargo_report(
548+
request,
549+
self._embargo_report_queryset(Embargo.objects.active_upcoming()),
550+
'upcoming_page',
551+
),
552+
'pending_page': self.paginate_embargo_report(
553+
request,
554+
self._embargo_report_queryset(Embargo.objects.pending_past_activation_window()),
555+
'pending_page',
556+
),
557+
'overdue_page': self.paginate_embargo_report(
558+
request,
559+
self._embargo_report_queryset(Embargo.objects.active_past_end_date()),
560+
'overdue_page',
561+
),
554562
})
555563
return context
556564

admin/templates/nodes/embargo_report.html

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ <h2>Upcoming Active Embargoes</h2>
1818
<th>Initiated By</th>
1919
</tr>
2020
</thead>
21-
{% include "util/pagination.html" with items=upcoming_page status='' pagin=False order='' %}
21+
{% if upcoming_page.paginator.num_pages > 1 %}
22+
{% include "util/pagination.html" with items=upcoming_page page_param="upcoming_page" status='' pagin=False order='' %}
23+
{% endif %}
2224
<tbody>
23-
{% for embargo in upcoming_embargoes %}
24-
{% with registration=embargo.registrations.first %}
25+
{% for embargo in upcoming_page %}
26+
{% with registration=embargo.registrations.all.0 %}
2527
{% if registration %}
2628
<tr>
2729
<td>
@@ -66,9 +68,12 @@ <h2>Pending Embargoes Past Pending Window</h2>
6668
<th>Initiated By</th>
6769
</tr>
6870
</thead>
71+
{% if pending_page.paginator.num_pages > 1 %}
72+
{% include "util/pagination.html" with items=pending_page page_param="pending_page" status='' pagin=False order='' %}
73+
{% endif %}
6974
<tbody>
70-
{% for embargo in pending_overdue_embargoes %}
71-
{% with registration=embargo.registrations.first %}
75+
{% for embargo in pending_page %}
76+
{% with registration=embargo.registrations.all.0 %}
7277
{% if registration %}
7378
<tr>
7479
<td>
@@ -113,9 +118,12 @@ <h2>Active Embargoes Past Pending Window</h2>
113118
<th>Initiated By</th>
114119
</tr>
115120
</thead>
121+
{% if overdue_page.paginator.num_pages > 1 %}
122+
{% include "util/pagination.html" with items=overdue_page page_param="overdue_page" status='' pagin=False order='' %}
123+
{% endif %}
116124
<tbody>
117-
{% for embargo in overdue_embargoes %}
118-
{% with registration=embargo.registrations.first %}
125+
{% for embargo in overdue_page %}
126+
{% with registration=embargo.registrations.all.0 %}
119127
{% if registration %}
120128
<tr>
121129
<td>
@@ -154,4 +162,3 @@ <h2>Active Embargoes Past Pending Window</h2>
154162
</table>
155163

156164
{% endblock %}
157-

admin/templates/util/pagination.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
<div class="pagination pagination-lg">
44
<span>
55
{% if items.has_previous %}
6-
<a href="?page=1&amp;status={{ status }}&amp;p={{ pagin }}&amp;order_by={{ order }}{{ extra_query_params }}"
6+
<a href="?{{ page_param|default:"page" }}=1&amp;status={{ status }}&amp;p={{ pagin }}&amp;order_by={{ order }}{{ extra_query_params }}"
77
class="btn btn-primary">
88
|
99
</a>
10-
<a href="?page={{ items.previous_page_number }}&amp;status={{ status }}&amp;p={{ pagin }}&amp;order_by={{ order }}{{ extra_query_params }}"
10+
<a href="?{{ page_param|default:"page" }}={{ items.previous_page_number }}&amp;status={{ status }}&amp;p={{ pagin }}&amp;order_by={{ order }}{{ extra_query_params }}"
1111
class="btn btn-primary">
1212
<i class="fa fa-angle-left"></i>
1313
</a>
@@ -25,11 +25,11 @@
2525
</span>
2626

2727
{% if items.has_next %}
28-
<a href="?page={{ items.next_page_number }}&amp;status={{ status }}&amp;p={{ pagin }}&amp;order_by={{ order }}{{ extra_query_params }}"
28+
<a href="?{{ page_param|default:"page" }}={{ items.next_page_number }}&amp;status={{ status }}&amp;p={{ pagin }}&amp;order_by={{ order }}{{ extra_query_params }}"
2929
class="btn btn-primary">
3030
<i class="fa fa-angle-right"></i>
3131
</a>
32-
<a href="?page={{ items.paginator.num_pages }}&amp;status={{ status }}&amp;p={{ pagin }}&amp;order_by={{ order }}{{ extra_query_params }}"
32+
<a href="?{{ page_param|default:"page" }}={{ items.paginator.num_pages }}&amp;status={{ status }}&amp;p={{ pagin }}&amp;order_by={{ order }}{{ extra_query_params }}"
3333
class="btn btn-primary">
3434
|
3535
</a>

admin_tests/nodes/test_views.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
AbstractNode,
1919
RegistrationApproval,
2020
Embargo,
21+
Sanction,
2122
SchemaResponse,
2223
DraftRegistration,
2324

@@ -44,7 +45,8 @@
4445
CheckArchiveStatusRegistrationsView,
4546
ForceArchiveRegistrationsView,
4647
ApprovalBacklogListView,
47-
ConfirmApproveBacklogView
48+
ConfirmApproveBacklogView,
49+
EmbargoReportView,
4850
)
4951
from admin_tests.utilities import setup_log_view, setup_view, handle_post_view_request
5052
from api_tests.share._utils import mock_update_share
@@ -62,14 +64,15 @@
6264
RegistrationApprovalFactory,
6365
RegistrationProviderFactory,
6466
DraftRegistrationFactory,
67+
EmbargoFactory,
6568
get_default_metaschema
6669
)
6770
from osf.utils.workflows import ApprovalStates, RegistrationModerationStates
6871
from osf.utils import permissions
6972
from osf.exceptions import NodeStateError
7073

7174

72-
from website.settings import REGISTRATION_APPROVAL_TIME
75+
from website.settings import REGISTRATION_APPROVAL_TIME, EMBARGO_PENDING_TIME
7376

7477

7578
def patch_messages(request):
@@ -1116,6 +1119,7 @@ def test_file_is_removed_from_registration_osfstorage(self):
11161119
name=registration_osfstorage.archive_folder_name
11171120
).children.exists()
11181121
assert not self.registration_registered_from.files.exists()
1122+
<<<<<<< HEAD
11191123
remove_log = self.registration_registered_from.logs.filter(
11201124
action=NodeLog.FILE_REMOVED,
11211125
foreign_user=NodeLog.SUPPORT_USER_LABEL,
@@ -1233,3 +1237,65 @@ def test_update_registration_date(self):
12331237
foreign_user=NodeLog.SUPPORT_USER_LABEL,
12341238
).latest('date')
12351239
assert_support_attributed_log(log, self.admin_user)
1240+
=======
1241+
1242+
1243+
class TestEmbargoReportView(AdminTestCase):
1244+
1245+
def setUp(self):
1246+
super().setUp()
1247+
self.request = RequestFactory().get('/nodes/embargo_report/')
1248+
self.view = setup_log_view(EmbargoReportView(), self.request)
1249+
1250+
def test_pending_past_activation_window_in_report(self):
1251+
embargo = EmbargoFactory(approve=False)
1252+
embargo.initiation_date = timezone.now() - EMBARGO_PENDING_TIME - timezone.timedelta(days=1)
1253+
embargo.save()
1254+
1255+
context = self.view.get_context_data()
1256+
assert embargo in context['pending_page']
1257+
1258+
def test_recent_pending_embargo_excluded(self):
1259+
embargo = EmbargoFactory(approve=False)
1260+
embargo.initiation_date = timezone.now()
1261+
embargo.save()
1262+
1263+
context = self.view.get_context_data()
1264+
assert embargo not in context['pending_page']
1265+
1266+
def test_active_past_end_date_in_report(self):
1267+
embargo = EmbargoFactory(
1268+
approve=True,
1269+
end_date=timezone.now() - timezone.timedelta(days=1),
1270+
)
1271+
embargo.state = Sanction.APPROVED
1272+
embargo.save()
1273+
1274+
context = self.view.get_context_data()
1275+
assert embargo in context['overdue_page']
1276+
1277+
def test_active_upcoming_in_report(self):
1278+
embargo = EmbargoFactory(
1279+
approve=True,
1280+
end_date=timezone.now() + timezone.timedelta(days=30),
1281+
)
1282+
embargo.state = Sanction.APPROVED
1283+
embargo.save()
1284+
1285+
context = self.view.get_context_data()
1286+
assert embargo in context['upcoming_page']
1287+
1288+
def test_deleted_registration_embargo_excluded(self):
1289+
embargo = EmbargoFactory(
1290+
approve=True,
1291+
end_date=timezone.now() - timezone.timedelta(days=1),
1292+
)
1293+
embargo.state = Sanction.APPROVED
1294+
embargo.save()
1295+
registration = embargo.registrations.first()
1296+
registration.is_deleted = True
1297+
registration.save()
1298+
1299+
context = self.view.get_context_data()
1300+
assert embargo not in context['overdue_page']
1301+
>>>>>>> upstream/develop

framework/sessions/__init__.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def create_session(response, data=None):
164164
def before_request():
165165
# TODO: Fix circular import
166166
from framework.auth.core import get_user
167-
from framework.auth import cas, utils
167+
from framework.auth import cas
168168
UserSessionMap = apps.get_model('osf.UserSessionMap')
169169

170170
# Request Type 1: Service ticket validation during CAS login.
@@ -215,8 +215,15 @@ def before_request():
215215
try:
216216
user_session = flask_get_session_from_cookie(cookie)
217217
except InvalidCookieOrSessionError:
218-
# If invalid session/cookie happens, perform a full logout to clear both CAS and OSF Sessions
219-
response = redirect(utils.get_default_osf_logout_url())
218+
# If invalid session/cookie happens, only remove the invalid cookie and redirect to the same request.
219+
# This ensures users landing on the page/link they previously clicked.
220+
# Case 1: If it's a public page, they land on the correct page.
221+
# Case 2: If it's a private page and if they already have a CAS cookie, they will be automatically logged
222+
# back in and land on the correct page.
223+
# Case 3: If it's a private page and if they don't have a CAS cookie, they will need to manually log in.
224+
# After successful login, they will land on the correct page.
225+
redirect_url = request.url
226+
response = redirect(redirect_url)
220227
response.delete_cookie(settings.COOKIE_NAME, domain=settings.OSF_COOKIE_DOMAIN)
221228
return response
222229
# Case 1: anonymous session that is used for first time external (e.g. ORCiD) login only

notifications/tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def send_user_email_task(self, user_id, notification_ids, **kwargs):
9494
return
9595

9696
try:
97-
notifications_qs = Notification.objects.filter(id__in=notification_ids)
97+
notifications_qs = Notification.objects.filter(id__in=notification_ids, sent__isnull=True)
9898
rendered_notifications, failed_notifications = safe_render_notification(notifications_qs, email_task)
9999
notifications_qs = notifications_qs.exclude(id__in=failed_notifications)
100100

@@ -168,7 +168,7 @@ def send_moderator_email_task(self, user_id, notification_ids, provider_content_
168168
return
169169

170170
try:
171-
notifications_qs = Notification.objects.filter(id__in=notification_ids)
171+
notifications_qs = Notification.objects.filter(id__in=notification_ids, sent__isnull=True)
172172
rendered_notifications, failed_notifications = safe_render_notification(notifications_qs, email_task)
173173
notifications_qs = notifications_qs.exclude(id__in=failed_notifications)
174174

0 commit comments

Comments
 (0)