diff --git a/admin/nodes/urls.py b/admin/nodes/urls.py index 087a1d57e94..e350adb90fa 100644 --- a/admin/nodes/urls.py +++ b/admin/nodes/urls.py @@ -8,6 +8,7 @@ re_path(r'^flagged_spam$', views.NodeFlaggedSpamList.as_view(), name='flagged-spam'), re_path(r'^known_spam$', views.NodeKnownSpamList.as_view(), name='known-spam'), re_path(r'^known_ham$', views.NodeKnownHamList.as_view(), name='known-ham'), + re_path(r'^embargo_report/$', views.EmbargoReportView.as_view(), name='embargo-report'), re_path(r'^doi_backlog_list/$', views.DoiBacklogListView.as_view(), name='doi-backlog-list'), re_path(r'^approval_backlog_list/$', views.ApprovalBacklogListView.as_view(), name='approval-backlog-list'), re_path(r'^confirm_approve_backlog_list/$', views.ConfirmApproveBacklogView.as_view(), name='confirm-approve-backlog-list'), diff --git a/admin/nodes/views.py b/admin/nodes/views.py index 01ebad686ce..9a403131547 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -16,7 +16,9 @@ View, FormView, ListView, + TemplateView ) +from django.core.paginator import Paginator, InvalidPage from admin.base.forms import GuidForm from admin.base.utils import change_embargo_date @@ -39,6 +41,7 @@ SpamStatus, TrashedFile ) +from osf.models.sanctions import Embargo from osf.models.admin_log_entry import ( update_admin_log, NODE_REMOVED, @@ -474,6 +477,54 @@ def get_context_data(self, **kwargs): } +class EmbargoReportView(PermissionRequiredMixin, TemplateView): + """Report view for inspecting current and overdue embargoed registrations. + + Shows: + - pending embargoes that should have been activated + - active embargoes that are past their end date + - upcoming active embargoes + """ + template_name = 'nodes/embargo_report.html' + permission_required = 'osf.view_registration' + raise_exception = True + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + pending_embargoes = Embargo.objects.pending_embargoes().select_related('initiated_by') + active_embargoes = Embargo.objects.active_embargoes().select_related('initiated_by') + + pending_overdue_embargoes = [ + embargo for embargo in pending_embargoes + if embargo.should_be_embargoed + ] + + overdue_embargoes = [ + embargo for embargo in active_embargoes + if embargo.should_be_completed + ] + + upcoming_queryset = active_embargoes.filter( + end_date__gte=timezone.now(), + ).order_by('end_date') + + page_number = self.request.GET.get('page') or 1 + paginator = Paginator(upcoming_queryset, 10) + try: + upcoming_page = paginator.page(page_number) + except InvalidPage: + upcoming_page = paginator.page(1) + + context.update({ + 'now': timezone.now(), + 'pending_overdue_embargoes': pending_overdue_embargoes, + 'overdue_embargoes': overdue_embargoes, + 'upcoming_embargoes': upcoming_page.object_list, + 'upcoming_page': upcoming_page, + }) + return context + + class ConfirmApproveBacklogView(RegistrationListView): template_name = 'nodes/registration_approval_list.html' permission_required = 'osf.view_registrationapproval' diff --git a/admin/templates/base.html b/admin/templates/base.html index e6f10794c29..92a0541ef34 100644 --- a/admin/templates/base.html +++ b/admin/templates/base.html @@ -169,6 +169,7 @@
  • IA Backlog
  • DOI Backlog
  • Approval Backlog
  • +
  • Embargo Report
  • {% endif %} diff --git a/admin/templates/nodes/embargo_report.html b/admin/templates/nodes/embargo_report.html new file mode 100644 index 00000000000..bbc68c994d1 --- /dev/null +++ b/admin/templates/nodes/embargo_report.html @@ -0,0 +1,157 @@ +{% extends "base.html" %} +{% load node_extras %} + +{% block content %} + + +

    Upcoming Active Embargoes

    + + + + + + + + + + + + {% include "util/pagination.html" with items=upcoming_page status='' pagin=False order='' %} + + {% for embargo in upcoming_embargoes %} + {% with registration=embargo.registrations.first %} + {% if registration %} + + + + + + + + + {% endif %} + {% endwith %} + {% empty %} + + + + {% endfor %} + +
    RegistrationEmbargo IDStateEmbargo StartEmbargo EndInitiated By
    + + {{ registration.title | truncatechars:30 }} + + {{ embargo.id }}{{ embargo.state }}{{ embargo.initiation_date|date:"F j, Y P" }}{{ embargo.end_date|date:"F j, Y P" }} + {% if embargo.initiated_by %} + + {{ embargo.initiated_by.fullname }} + + {% else %} + — + {% endif %} +
    No active embargoes found.
    + +

    Pending Embargoes Past Pending Window

    +

    These embargoes are still awaiting approval but have passed the automatic activation window.

    + + + + + + + + + + + + + {% for embargo in pending_overdue_embargoes %} + {% with registration=embargo.registrations.first %} + {% if registration %} + + + + + + + + + {% endif %} + {% endwith %} + {% empty %} + + + + {% endfor %} + +
    RegistrationEmbargo IDStateEmbargo InitiationEmbargo EndInitiated By
    + + {{ registration.title | truncatechars:30 }} + + {{ embargo.id }}{{ embargo.state }}{{ embargo.initiation_date|date:"F j, Y P" }}{{ embargo.end_date|date:"F j, Y P" }} + {% if embargo.initiated_by %} + + {{ embargo.initiated_by.fullname }} + + {% else %} + — + {% endif %} +
    No pending embargoes past the pending window.
    + +

    Active Embargoes Past Pending Window

    +

    These embargoes have an end date in the past but the embargo is still marked as active.

    + + + + + + + + + + + + + {% for embargo in overdue_embargoes %} + {% with registration=embargo.registrations.first %} + {% if registration %} + + + + + + + + + {% endif %} + {% endwith %} + {% empty %} + + + + {% endfor %} + +
    RegistrationEmbargo IDStateEmbargo EndIs Registration Public?Initiated By
    + + {{ registration.title | truncatechars:30 }} + + {{ embargo.id }}{{ embargo.state }}{{ embargo.end_date|date:"F j, Y P" }} + {% if registration.is_public %} + Yes + {% else %} + No + {% endif %} + + {% if embargo.initiated_by %} + + {{ embargo.initiated_by.fullname }} + + {% else %} + — + {% endif %} +
    No overdue active embargoes found.
    + +{% endblock %} + diff --git a/osf/models/sanctions.py b/osf/models/sanctions.py index 8855852f1a1..b387f22ef16 100644 --- a/osf/models/sanctions.py +++ b/osf/models/sanctions.py @@ -456,6 +456,17 @@ def _email_template_context(self, user, node, is_authorizer=False, urls=None): return {} +class EmbargoManager(models.Manager): + + def pending_embargoes(self): + """Embargoes that are still awaiting admin approval.""" + return self.filter(state=self.model.UNAPPROVED) + + def active_embargoes(self): + """Embargoes that have been approved and are currently in effect.""" + return self.filter(state=self.model.APPROVED) + + class Embargo(SanctionCallbackMixin, EmailApprovableSanction): """Embargo object for registrations waiting to go public.""" SANCTION_TYPE = SanctionTypes.EMBARGO @@ -472,6 +483,8 @@ class Embargo(SanctionCallbackMixin, EmailApprovableSanction): initiated_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.CASCADE) for_existing_registration = models.BooleanField(default=False) + objects = EmbargoManager() + @property def is_completed(self): return self.state == self.COMPLETED @@ -497,6 +510,17 @@ def embargo_end_date(self): def pending_registration(self): return not self.for_existing_registration and self.is_pending_approval + @property + def should_be_embargoed(self): + return ( + (timezone.now() - self.initiation_date) >= osf_settings.EMBARGO_PENDING_TIME + and not self.is_deleted + ) + + @property + def should_be_completed(self): + return self.end_date and self.end_date < timezone.now() and not self.is_deleted + def _get_registration(self): return self.registrations.first() diff --git a/scripts/embargo_registrations.py b/scripts/embargo_registrations.py index 8291b611b2e..f90d5f43626 100644 --- a/scripts/embargo_registrations.py +++ b/scripts/embargo_registrations.py @@ -28,9 +28,9 @@ def main(dry_run=True): - pending_embargoes = Embargo.objects.filter(state=Embargo.UNAPPROVED) + pending_embargoes = Embargo.objects.pending_embargoes() for embargo in pending_embargoes: - if should_be_embargoed(embargo): + if embargo.should_be_embargoed: if dry_run: logger.warning('Dry run mode') try: @@ -77,9 +77,9 @@ def main(dry_run=True): transaction.savepoint_rollback(sid) - active_embargoes = Embargo.objects.filter(state=Embargo.APPROVED) + active_embargoes = Embargo.objects.active_embargoes() for embargo in active_embargoes: - if embargo.end_date < timezone.now() and not embargo.is_deleted: + if embargo.should_be_completed: if dry_run: logger.warning('Dry run mode') parent_registration = Registration.objects.get(embargo=embargo) @@ -117,11 +117,6 @@ def main(dry_run=True): transaction.savepoint_rollback(sid) -def should_be_embargoed(embargo): - """Returns true if embargo was initiated more than 48 hours prior.""" - return (timezone.now() - embargo.initiation_date) >= settings.EMBARGO_PENDING_TIME and not embargo.is_deleted - - @celery_app.task(name='scripts.embargo_registrations') def run_main(dry_run=True): if not dry_run: