Skip to content

Commit 35ed310

Browse files
committed
Merge tag '26.10.2' into develop
Fix amdin embargo report page by paging
2 parents 91c02ba + dd229f3 commit 35ed310

9 files changed

Lines changed: 177 additions & 48 deletions

File tree

CHANGELOG

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

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

5+
26.10.2 (2026-05-27)
6+
====================
7+
8+
- Fix admin embargo report page by paging
9+
510
26.10.1 (2026-05-22)
611
====================
712

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
@@ -487,42 +487,50 @@ class EmbargoReportView(PermissionRequiredMixin, TemplateView):
487487
- active embargoes that are past their end date
488488
- upcoming active embargoes
489489
"""
490+
490491
template_name = 'nodes/embargo_report.html'
491492
permission_required = 'osf.view_registration'
492493
raise_exception = True
493494

494-
def get_context_data(self, **kwargs):
495-
context = super().get_context_data(**kwargs)
496-
pending_embargoes = Embargo.objects.pending_embargoes().select_related('initiated_by')
497-
active_embargoes = Embargo.objects.active_embargoes().select_related('initiated_by')
498-
499-
pending_overdue_embargoes = [
500-
embargo for embargo in pending_embargoes
501-
if embargo.should_be_embargoed
502-
]
503-
504-
overdue_embargoes = [
505-
embargo for embargo in active_embargoes
506-
if embargo.should_be_completed
507-
]
508-
509-
upcoming_queryset = active_embargoes.filter(
510-
end_date__gte=timezone.now(),
511-
).order_by('end_date')
495+
def _embargo_report_queryset(self, queryset):
496+
return queryset.select_related('initiated_by').prefetch_related(
497+
Prefetch(
498+
'registrations',
499+
queryset=Registration.objects.filter(is_deleted=False).prefetch_related(
500+
'guids',
501+
).only('id', 'title', 'is_public', 'embargo_id'),
502+
),
503+
)
512504

513-
page_number = self.request.GET.get('page') or 1
514-
paginator = Paginator(upcoming_queryset, 10)
505+
def paginate_embargo_report(self, request, queryset, page_param):
506+
paginator = Paginator(queryset, settings.EMBARGO_REPORT_PAGE_SIZE)
507+
page_number = request.GET.get(page_param) or 1
515508
try:
516-
upcoming_page = paginator.page(page_number)
509+
return paginator.page(page_number)
517510
except InvalidPage:
518-
upcoming_page = paginator.page(1)
511+
return paginator.page(1)
512+
513+
def get_context_data(self, **kwargs):
514+
context = super().get_context_data(**kwargs)
515+
request = self.request
519516

520517
context.update({
521518
'now': timezone.now(),
522-
'pending_overdue_embargoes': pending_overdue_embargoes,
523-
'overdue_embargoes': overdue_embargoes,
524-
'upcoming_embargoes': upcoming_page.object_list,
525-
'upcoming_page': upcoming_page,
519+
'upcoming_page': self.paginate_embargo_report(
520+
request,
521+
self._embargo_report_queryset(Embargo.objects.active_upcoming()),
522+
'upcoming_page',
523+
),
524+
'pending_page': self.paginate_embargo_report(
525+
request,
526+
self._embargo_report_queryset(Embargo.objects.pending_past_activation_window()),
527+
'pending_page',
528+
),
529+
'overdue_page': self.paginate_embargo_report(
530+
request,
531+
self._embargo_report_queryset(Embargo.objects.active_past_end_date()),
532+
'overdue_page',
533+
),
526534
})
527535
return context
528536

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: 65 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
)
@@ -39,7 +40,8 @@
3940
CheckArchiveStatusRegistrationsView,
4041
ForceArchiveRegistrationsView,
4142
ApprovalBacklogListView,
42-
ConfirmApproveBacklogView
43+
ConfirmApproveBacklogView,
44+
EmbargoReportView,
4345
)
4446
from admin_tests.utilities import setup_log_view, setup_view, handle_post_view_request
4547
from api_tests.share._utils import mock_update_share
@@ -57,14 +59,15 @@
5759
RegistrationApprovalFactory,
5860
RegistrationProviderFactory,
5961
DraftRegistrationFactory,
62+
EmbargoFactory,
6063
get_default_metaschema
6164
)
6265
from osf.utils.workflows import ApprovalStates, RegistrationModerationStates
6366
from osf.utils import permissions
6467
from osf.exceptions import NodeStateError
6568

6669

67-
from website.settings import REGISTRATION_APPROVAL_TIME
70+
from website.settings import REGISTRATION_APPROVAL_TIME, EMBARGO_PENDING_TIME
6871

6972

7073
def patch_messages(request):
@@ -1069,3 +1072,63 @@ def test_file_is_removed_from_registration_osfstorage(self):
10691072
name=registration_osfstorage.archive_folder_name
10701073
).children.exists()
10711074
assert not self.registration_registered_from.files.exists()
1075+
1076+
1077+
class TestEmbargoReportView(AdminTestCase):
1078+
1079+
def setUp(self):
1080+
super().setUp()
1081+
self.request = RequestFactory().get('/nodes/embargo_report/')
1082+
self.view = setup_log_view(EmbargoReportView(), self.request)
1083+
1084+
def test_pending_past_activation_window_in_report(self):
1085+
embargo = EmbargoFactory(approve=False)
1086+
embargo.initiation_date = timezone.now() - EMBARGO_PENDING_TIME - timezone.timedelta(days=1)
1087+
embargo.save()
1088+
1089+
context = self.view.get_context_data()
1090+
assert embargo in context['pending_page']
1091+
1092+
def test_recent_pending_embargo_excluded(self):
1093+
embargo = EmbargoFactory(approve=False)
1094+
embargo.initiation_date = timezone.now()
1095+
embargo.save()
1096+
1097+
context = self.view.get_context_data()
1098+
assert embargo not in context['pending_page']
1099+
1100+
def test_active_past_end_date_in_report(self):
1101+
embargo = EmbargoFactory(
1102+
approve=True,
1103+
end_date=timezone.now() - timezone.timedelta(days=1),
1104+
)
1105+
embargo.state = Sanction.APPROVED
1106+
embargo.save()
1107+
1108+
context = self.view.get_context_data()
1109+
assert embargo in context['overdue_page']
1110+
1111+
def test_active_upcoming_in_report(self):
1112+
embargo = EmbargoFactory(
1113+
approve=True,
1114+
end_date=timezone.now() + timezone.timedelta(days=30),
1115+
)
1116+
embargo.state = Sanction.APPROVED
1117+
embargo.save()
1118+
1119+
context = self.view.get_context_data()
1120+
assert embargo in context['upcoming_page']
1121+
1122+
def test_deleted_registration_embargo_excluded(self):
1123+
embargo = EmbargoFactory(
1124+
approve=True,
1125+
end_date=timezone.now() - timezone.timedelta(days=1),
1126+
)
1127+
embargo.state = Sanction.APPROVED
1128+
embargo.save()
1129+
registration = embargo.registrations.first()
1130+
registration.is_deleted = True
1131+
registration.save()
1132+
1133+
context = self.view.get_context_data()
1134+
assert embargo not in context['overdue_page']

osf/models/sanctions.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -456,15 +456,60 @@ def _email_template_context(self, user, node, is_authorizer=False, urls=None):
456456
return {}
457457

458458

459+
class EmbargoQuerySet(models.QuerySet):
460+
461+
def has_non_deleted_registrations(self):
462+
"""Keep embargoes linked to at least one non-deleted registration.
463+
464+
Excludes orphaned embargoes (no registration) and embargoes whose only
465+
linked registration(s) are soft-deleted. Matches Embargo.is_deleted.
466+
"""
467+
return self.filter(registrations__is_deleted=False).distinct()
468+
469+
459470
class EmbargoManager(models.Manager):
460471

461-
def pending_embargoes(self):
472+
def get_queryset(self):
473+
return EmbargoQuerySet(self.model, using=self._db)
474+
475+
def pending_embargoes(self, exclude_deleted=False):
462476
"""Embargoes that are still awaiting admin approval."""
463-
return self.filter(state=self.model.UNAPPROVED)
477+
queryset = self.filter(state=self.model.UNAPPROVED)
478+
if exclude_deleted:
479+
queryset = queryset.has_non_deleted_registrations()
480+
return queryset
464481

465-
def active_embargoes(self):
482+
def active_embargoes(self, exclude_deleted=False):
466483
"""Embargoes that have been approved and are currently in effect."""
467-
return self.filter(state=self.model.APPROVED)
484+
queryset = self.filter(state=self.model.APPROVED)
485+
if exclude_deleted:
486+
queryset = queryset.has_non_deleted_registrations()
487+
return queryset
488+
489+
def pending_past_activation_window(self):
490+
"""Pending embargoes that should have been activated (matches should_be_embargoed)."""
491+
cutoff = timezone.now() - osf_settings.EMBARGO_PENDING_TIME
492+
return (
493+
self.pending_embargoes(exclude_deleted=True)
494+
.filter(initiation_date__lte=cutoff)
495+
.order_by('initiation_date')
496+
)
497+
498+
def active_past_end_date(self):
499+
"""Active embargoes past end date (matches should_be_completed)."""
500+
return (
501+
self.active_embargoes(exclude_deleted=True)
502+
.filter(end_date__lt=timezone.now())
503+
.order_by('end_date')
504+
)
505+
506+
def active_upcoming(self):
507+
"""Active embargoes with a future end date."""
508+
return (
509+
self.active_embargoes(exclude_deleted=True)
510+
.filter(end_date__gte=timezone.now())
511+
.order_by('end_date')
512+
)
468513

469514

470515
class Embargo(SanctionCallbackMixin, EmailApprovableSanction):

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "OSF",
3-
"version": "26.10.1",
3+
"version": "26.10.2",
44
"description": "Facilitating Open Science",
55
"repository": "https://github.com/CenterForOpenScience/osf.io",
66
"author": "Center for Open Science",

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[tool.poetry]
22
name = "osf-io"
3-
version = "26.10.1"
3+
version = "26.10.2"
44
description = "The code for [https://osf.io](https://osf.io)."
5-
authors = ["Your Name <you@example.com>"]
5+
authors = ["Longze Chen <cslzchen@gmail.com>"]
66
license = "Apache License 2.0"
77
readme = "README.md"
88
packages = [{include = "osf"}]

website/settings/defaults.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def parent_dir(path):
7474
# Date range for embargo periods
7575
EMBARGO_END_DATE_MIN = datetime.timedelta(days=2)
7676
EMBARGO_END_DATE_MAX = datetime.timedelta(days=1460) # Four years
77+
EMBARGO_REPORT_PAGE_SIZE = 10
7778

7879
# Question titles to be reomved for anonymized VOL
7980
ANONYMIZED_TITLES = ['Authors']

0 commit comments

Comments
 (0)