diff --git a/.github/actions/gen-report/action.yml b/.github/actions/gen-report/action.yml index 81292f78103..35015045192 100644 --- a/.github/actions/gen-report/action.yml +++ b/.github/actions/gen-report/action.yml @@ -4,12 +4,12 @@ runs: using: "composite" steps: - name: Archive code coverage results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{github.job}} HTML REPORT path: report.html - name: Generate Report - uses: dorny/test-reporter@v2 + uses: dorny/test-reporter@v3 if: success() || failure() # run this step even if previous step failed with: name: ${{github.job}} REPORT # Name of the check run which will be created diff --git a/.github/actions/start-build/action.yml b/.github/actions/start-build/action.yml index 6cb181801e4..22d8deae0d5 100644 --- a/.github/actions/start-build/action.yml +++ b/.github/actions/start-build/action.yml @@ -4,7 +4,7 @@ runs: using: "composite" steps: - id: cache-objects - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.cache key: reqs_${{ hashFiles('poetry.lock') }} @@ -16,7 +16,7 @@ runs: with: ELASTICSEARCH6_ARCHIVE: ${{ env.ELASTICSEARCH6_ARCHIVE }} - name: Set up Python 3.12 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: '3.12' - name: Install lxml diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 33942968529..00c5820b5b8 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -17,9 +17,9 @@ jobs: build-cache: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - id: cache-objects - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: ~/.cache key: reqs_${{ hashFiles('poetry.lock') }} @@ -50,7 +50,7 @@ jobs: # Maps tcp port 5432 on service container to the host - 5432:5432 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - uses: ./.github/actions/start-build - name: Run tests run: poetry run python3 -m invoke test-ci-addons --junit @@ -77,7 +77,7 @@ jobs: # Maps tcp port 5432 on service container to the host - 5432:5432 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - uses: ./.github/actions/start-build - name: Run tests run: poetry run python3 -m invoke test-ci-website --junit @@ -104,7 +104,7 @@ jobs: # Maps tcp port 5432 on service container to the host - 5432:5432 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - uses: ./.github/actions/start-build - name: NVM & yarn install run: poetry run python3 -m invoke assets --dev @@ -133,7 +133,7 @@ jobs: # Maps tcp port 5432 on service container to the host - 5432:5432 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - uses: ./.github/actions/start-build - name: Run tests run: poetry run python3 -m invoke test-ci-api2 --junit @@ -161,7 +161,7 @@ jobs: # Maps tcp port 5432 on service container to the host - 5432:5432 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - uses: ./.github/actions/start-build - name: Run tests run: poetry run python3 -m invoke test-ci-api3-and-osf --junit @@ -194,7 +194,7 @@ jobs: - 1025:1025 - 8025:8025 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - uses: ./.github/actions/start-build - name: Run tests run: poetry run python3 -m invoke test-ci-mailhog -n 1 --junit @@ -222,7 +222,7 @@ jobs: # Maps tcp port 5432 on service container to the host - 5432:5432 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - uses: ./.github/actions/start-build - name: Run tests run: poetry run python3 -m invoke test-ci-scripts -n 1 --junit diff --git a/CHANGELOG b/CHANGELOG index d2cf41a6c83..08306263ab1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,36 @@ We follow the CalVer (https://calver.org/) versioning scheme: YY.MINOR.MICRO. +26.9.0 (2026-05-07) +=================== + +- OSF4I In-progress SSO Project - BE Piece + +26.8.2 (2026-05-06) +=================== + +- Hotfix to avoid 502 on creating preprint version from rejected version + +26.8.1 (2026-04-30) +=================== + +- Hotfix for registrations sometimes fail to become public after approval + +26.8.0 (2026-04-23) +=================== + +- Miscellaneous improvements and bug fixes + +26.7.1 (2026-04-22) +=================== + +- Hotfix to improve notifications and logging for failed registrations + +26.7.0 (2026-04-08) +=================== + +- ORCiD Integration Project - BE Part + 26.6.3 (2026-04-02) =================== diff --git a/admin/base/urls.py b/admin/base/urls.py index d19d2dc638b..9ff5e03a03e 100644 --- a/admin/base/urls.py +++ b/admin/base/urls.py @@ -37,6 +37,7 @@ re_path(r'^cedar_metadata_templates/', include('admin.cedar.urls', namespace='cedar_metadata_templates')), re_path(r'^draft_registrations/', include('admin.draft_registrations.urls', namespace='draft_registrations')), re_path(r'^files/', include('admin.files.urls', namespace='files')), + re_path(r'^share_reindex/', include('admin.share_reindex.urls', namespace='share_reindex')), ]), ), ] diff --git a/admin/institutions/forms.py b/admin/institutions/forms.py index ccb7e9c4e77..f482700a8c1 100644 --- a/admin/institutions/forms.py +++ b/admin/institutions/forms.py @@ -1,5 +1,5 @@ from django import forms -from osf.models import Institution +from osf.models.institution import Institution, SSOAvailability class InstitutionForm(forms.ModelForm): @@ -10,6 +10,25 @@ class Meta: 'is_deleted', 'contributors', 'storage_regions', ] + def clean(self): + super().clean() + + if hasattr(self, 'cleaned_data') and self.changed_data: + if not self.cleaned_data['delegation_protocol']: + if self.cleaned_data['sso_availability'] != SSOAvailability.UNAVAILABLE.value: + self.add_error(None, 'SSO availability must be set to "Unavailable" when no delegation protocol is configured.') + + elif self.cleaned_data['deactivated']: + if self.cleaned_data['sso_availability'] != SSOAvailability.HIDDEN.value: + self.add_error(None, 'SSO availability must be set to "Hidden" when the institution is deactivated.') + + else: + if self.cleaned_data['sso_availability'] not in [ + SSOAvailability.PUBLIC.value, + SSOAvailability.HIDDEN.value + ]: + self.add_error(None, 'SSO availability must be set to "Public" or "Hidden" when delegation protocol is configured.') + class InstitutionalMetricsAdminRegisterForm(forms.Form): """ A form that finds an existing OSF User, and grants permissions to that diff --git a/admin/institutions/views.py b/admin/institutions/views.py index 536d916d937..50b1bcf9d4c 100644 --- a/admin/institutions/views.py +++ b/admin/institutions/views.py @@ -56,6 +56,7 @@ def get_context_data(self, *args, **kwargs): institution_dict = model_to_dict(institution) kwargs.setdefault('page_number', self.request.GET.get('page', '1')) kwargs['institution'] = institution_dict + kwargs['cas_login_url'] = institution.cas_login_url kwargs['logo_path'] = institution.logo_path kwargs['banner_path'] = institution.banner_path fields = institution_dict @@ -117,6 +118,17 @@ def get_context_data(self, *args, **kwargs): def get_success_url(self, *args, **kwargs): return reverse_lazy('institutions:detail', kwargs={'institution_id': self.kwargs.get('institution_id')}) + def post(self, request, *args, **kwargs): + # Override `post` method in `django.views.generic.edit.ProcessFormView` due to custom behavior + self.object = self.get_object() + form = self.get_form() + if form.is_valid(): + return self.form_valid(form) + else: + for error in form.non_field_errors(): + messages.error(request, error) + return redirect('institutions:detail', institution_id=self.kwargs.get('institution_id')) + class InstitutionExport(PermissionRequiredMixin, View): permission_required = 'osf.view_institution' diff --git a/admin/management/urls.py b/admin/management/urls.py index d583deb2ce0..c046b3bed18 100644 --- a/admin/management/urls.py +++ b/admin/management/urls.py @@ -19,5 +19,7 @@ re_path(r'^empty_metadata_dataarchive_registration_bulk_resync', views.EmptyMetadataDataarchiveRegistrationBulkResync.as_view(), name='empty-metadata-dataarchive-registration-bulk-resync'), re_path(r'^sync_notification_templates', views.SyncNotificationTemplates.as_view(), - name='sync_notification_templates') + name='sync_notification_templates'), + re_path(r'^remove_orcid_from_user_social', views.RemoveOrcidFromUserSocial.as_view(), + name='remove_orcid_from_user_social') ] diff --git a/admin/management/views.py b/admin/management/views.py index 36f3d893f24..f2052822f37 100644 --- a/admin/management/views.py +++ b/admin/management/views.py @@ -12,6 +12,7 @@ from osf.management.commands.fetch_cedar_metadata_templates import ingest_cedar_metadata_templates from osf.management.commands.sync_doi_metadata import sync_doi_metadata, sync_doi_empty_metadata_dataarchive_registrations from osf.management.commands.populate_notification_types import populate_notification_types +from osf.management.commands.remove_orcid_from_user_social import remove_orcid_from_user_social from scripts.find_spammy_content import manage_spammy_content from django.urls import reverse from django.shortcuts import redirect @@ -181,3 +182,11 @@ def post(self, request): populate_notification_types() messages.success(request, 'Notification templates have been successfully synced.') return redirect(reverse('management:commands')) + + +class RemoveOrcidFromUserSocial(ManagementCommandPermissionView): + + def post(self, request): + remove_orcid_from_user_social() + messages.success(request, 'Orcid from user social have been successfully removed.') + return redirect(reverse('management:commands')) diff --git a/admin/nodes/urls.py b/admin/nodes/urls.py index 087a1d57e94..12d533ccfd0 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'), @@ -51,4 +52,7 @@ re_path(r'^(?P[a-z0-9]+)/system_tags/(?P[a-z0-9]+)/remove/$', views.NodeRemoveSystemTag.as_view(), name='remove-system-tag'), re_path(r'^(?P[a-z0-9]+)/update_permissions/$', views.NodeUpdatePermissionsView.as_view(), name='update-permissions'), re_path(r'^(?P[a-z0-9]+)/remove_file/$', views.NodeRemoveFileView.as_view(), name='remove-file'), + re_path(r'^(?P[a-z0-9]+)/add_osfstorage_file/$', views.NodeAddOsfStorageFileView.as_view(), name='add-osfstorage-file'), + re_path(r'^(?P[a-z0-9]+)/remove_osfstorage_file/$', views.NodeRemoveOsfStorageFileView.as_view(), name='remove-osfstorage-file'), + ] diff --git a/admin/nodes/views.py b/admin/nodes/views.py index 01ebad686ce..a97fec18597 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -16,13 +16,16 @@ 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 from admin.base.views import GuidView from admin.nodes.forms import AddSystemTagForm, RegistrationDateForm from admin.notifications.views import delete_selected_notifications +from addons.osfstorage.models import OsfStorageFolder from api.caching.tasks import update_storage_usage_cache from api.share.utils import update_share from framework import status @@ -39,6 +42,7 @@ SpamStatus, TrashedFile ) +from osf.models.sanctions import Embargo from osf.models.admin_log_entry import ( update_admin_log, NODE_REMOVED, @@ -50,6 +54,7 @@ REINDEX_SHARE, REINDEX_ELASTIC, ) +from osf.models.files import File from osf.utils.permissions import ADMIN, API_CONTRIBUTOR_PERMISSIONS from scripts.approve_registrations import approve_past_pendings from website import settings, search @@ -474,6 +479,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' @@ -846,6 +899,72 @@ def _remove_file_from_schema_response_blocks(registration, removed_file_id): return redirect(self.get_success_url()) +class NodeAddOsfStorageFileView(NodeMixin, View): + """ Allows an authorized user to add a file to osfstorage of an archived node. + """ + permission_required = 'osf.change_node' + + def post(self, request, *args, **kwargs): + registration = self.get_object() + guid_id = request.POST.get('file-guid', '').strip() + guid = Guid.load(guid_id) + if not guid: + messages.error(request, 'No file found with the provided guid.') + return redirect(self.get_success_url()) + + file = guid.referent + if not isinstance(file, File): + messages.error(request, 'The guid provided does not correspond to a file.') + return redirect(self.get_success_url()) + + parent_node = registration.registered_from + if not parent_node: + messages.error(request, 'The registration does not have the parent node.') + return redirect(self.get_success_url()) + + if not parent_node.files.filter(id=file.id).exists(): + messages.error(request, 'The file with the provided guid is not part of the parent node.') + return redirect(self.get_success_url()) + + osfstorage = registration.get_addon('osfstorage') + # copy file to Archive of OSF Storage folder + archive_folder = OsfStorageFolder.objects.filter( + parent=osfstorage.get_root(), + name=osfstorage.archive_folder_name + ).first() + file.copy_under(archive_folder) + messages.success(request, 'The file was successfully added.') + return redirect(self.get_success_url()) + + +class NodeRemoveOsfStorageFileView(NodeMixin, View): + """ Allows an authorized user to remove a file from osfstorage of an archived node. + """ + permission_required = 'osf.change_node' + + def post(self, request, *args, **kwargs): + registration = self.get_object() + guid_id = request.POST.get('file-guid', '').strip() + guid = Guid.load(guid_id) + if not guid: + messages.error(request, 'No file found with the provided guid.') + return redirect(self.get_success_url()) + + file = guid.referent + if not isinstance(file, File): + messages.error(request, 'The guid provided does not correspond to a file.') + return redirect(self.get_success_url()) + + registration_file = registration.files.filter(id=file.id) + if not registration_file.exists(): + messages.error(request, 'The file with the provided guid is not part of the registration.') + return redirect(self.get_success_url()) + + registration_file.delete() + messages.success(request, 'The file was successfully removed.') + return redirect(self.get_success_url()) + + class RemoveStuckRegistrationsView(NodeMixin, View): """ Allows an authorized user to remove a registrations if it's stuck in the archiving process. """ diff --git a/admin/preprints/urls.py b/admin/preprints/urls.py index d6eabdd870c..960e2e297e5 100644 --- a/admin/preprints/urls.py +++ b/admin/preprints/urls.py @@ -17,6 +17,7 @@ re_path(r'^(?P\w+)/remove_user/(?P[a-z0-9]+)/$', views.PreprintRemoveContributorView.as_view(), name='remove-user'), re_path(r'^(?P\w+)/make_private/$', views.PreprintMakePrivate.as_view(), name='make-private'), re_path(r'^(?P\w+)/fix_editing/$', views.PreprintFixEditing.as_view(), name='fix-editing'), + re_path(r'^(?P\w+)/fix_coi/$', views.PreprintFixCOI.as_view(), name='fix-coi'), re_path(r'^(?P\w+)/make_public/$', views.PreprintMakePublic.as_view(), name='make-public'), re_path(r'^(?P\w+)/remove/$', views.PreprintDeleteView.as_view(), name='remove'), re_path(r'^(?P\w+)/restore/$', views.PreprintDeleteView.as_view(), name='restore'), diff --git a/admin/preprints/views.py b/admin/preprints/views.py index 99d18d83650..3153ae301c8 100644 --- a/admin/preprints/views.py +++ b/admin/preprints/views.py @@ -668,6 +668,22 @@ def post(self, request, *args, **kwargs): return redirect(self.get_success_url()) +class PreprintFixCOI(PreprintMixin, View): + """ Allows an authorized user to manually fix conflict of interest field. + """ + permission_required = 'osf.change_preprint' + + def post(self, request, *args, **kwargs): + preprint = self.get_object() + if preprint.conflict_of_interest_statement and not preprint.has_coi: + preprint.update_has_coi(auth=request, has_coi=True, ignore_permission=True) + messages.success(request, 'The COI was successfully fixed.') + else: + messages.error(request, 'The COI is either already fixed or the preprint does not have conflict of interest set.') + + return redirect(self.get_success_url()) + + class PreprintMakePublic(PreprintMixin, View): """ Allows an authorized user to manually make a private preprint public. """ diff --git a/admin/registration_schemas/views.py b/admin/registration_schemas/views.py index aa205839029..df8aef09887 100644 --- a/admin/registration_schemas/views.py +++ b/admin/registration_schemas/views.py @@ -17,7 +17,7 @@ class RegistrationSchemaDetailView(FormView, PermissionRequiredMixin, TemplateVi Allows authorized users to view and edit some attributes of a Registration Schema. """ template_name = 'registration_schemas/registration_schema.html' - permission_required = 'osf.view_registration_schema' + permission_required = 'osf.view_registrationschema' raise_exception = True form_class = RegistrationSchemaEditForm @@ -143,7 +143,7 @@ class RegistrationSchemaListView(PermissionRequiredMixin, ListView): Allows authorized users to view all Registration Schema. """ template_name = 'registration_schemas/registration_schema_list.html' - permission_required = 'osf.view_registration_schema' + permission_required = 'osf.view_registrationschema' raise_exception = True def get_queryset(self): diff --git a/admin/share_reindex/__init__.py b/admin/share_reindex/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/admin/share_reindex/urls.py b/admin/share_reindex/urls.py new file mode 100644 index 00000000000..9eeb5d6d290 --- /dev/null +++ b/admin/share_reindex/urls.py @@ -0,0 +1,9 @@ +from django.urls import re_path +from . import views + +app_name = 'admin' + +urlpatterns = [ + re_path(r'^$', views.FailedShareIndexedGuidList.as_view(), name='list'), + re_path(r'^(?P[^/]+)/$', views.FailedShareIndexedGuidReindex.as_view(), name='reindex-share-resource'), +] diff --git a/admin/share_reindex/views.py b/admin/share_reindex/views.py new file mode 100644 index 00000000000..0d68194f7e5 --- /dev/null +++ b/admin/share_reindex/views.py @@ -0,0 +1,52 @@ +from django.contrib.auth.mixins import PermissionRequiredMixin +from django.urls import reverse +from django.shortcuts import redirect +from django.views.generic import ListView, View +from osf.models import Guid +from urllib.parse import urlencode +from api.share.utils import get_not_indexed_guids_for_resource_with_no_indexed_guid, task__reindex_failed_or_not_indexed_resource_into_share + +class FailedShareIndexedGuidList(PermissionRequiredMixin, ListView): + paginate_by = 25 + template_name = 'share_reindex/list.html' + permission_required = 'osf.update_share_reindex' + raise_exception = True + model = Guid + + def get_queryset(self): + resource_type = self.request.GET.get('type', 'projects') + return get_not_indexed_guids_for_resource_with_no_indexed_guid(resource_type) + + def get_context_data(self, **kwargs): + query_set = kwargs.pop('object_list', self.object_list) + page_size = self.get_paginate_by(query_set) + paginator, page, query_set, is_paginated = self.paginate_queryset(query_set, page_size) + kwargs.setdefault('items_to_index', query_set) + kwargs.setdefault('page', page) + resource_type = self.request.GET.get('type', 'projects') + kwargs.setdefault('selected_resource_type', resource_type) + resource_type_detail_mapping = { + 'users': 'users:user', 'preprints': 'preprints:preprint', 'registries': 'nodes:node', 'projects': 'nodes:node', 'files': 'files:file' + } + + kwargs.setdefault('resource_detail', resource_type_detail_mapping.get(resource_type)) + resource_type_guid_reindex = { + 'users': 'users:reindex-share-user', 'preprints': 'preprints:reindex-share-preprint', 'registries': 'nodes:reindex-share-node', 'projects': 'nodes:reindex-share-node' + } + kwargs.setdefault('resource_guid_reindex', resource_type_guid_reindex.get(resource_type)) + status_msg = f'Reindex of {resource_type} started, please check later.' if self.request.GET.get('status') == 'indexing' else '' + kwargs.setdefault('share_reindex_message', status_msg) + return super().get_context_data(**kwargs) + + +class FailedShareIndexedGuidReindex(PermissionRequiredMixin, View): + permission_required = 'osf.update_share_reindex' + raise_exception = True + + def post(self, request, *args, **kwargs): + resource_type = self.kwargs.get('resource_type') + # reindex 100_000 guids in background task for specific resource_type and resource is public + task__reindex_failed_or_not_indexed_resource_into_share.delay(resource_type) + base_url = reverse('share_reindex:list') + query_string = urlencode({'type': resource_type, 'status': 'indexing'}) + return redirect(f"{base_url}?{query_string}") diff --git a/admin/templates/base.html b/admin/templates/base.html index e6f10794c29..31a89b74037 100644 --- a/admin/templates/base.html +++ b/admin/templates/base.html @@ -169,6 +169,7 @@
  • IA Backlog
  • DOI Backlog
  • Approval Backlog
  • +
  • Embargo Report
  • {% endif %} @@ -316,6 +317,9 @@ {% if perms.osf.change_cedarmetadatatemplate %}
  • Cedar Metadata Templates
  • {% endif %} + {% if perms.osf.update_share_reindex %} +
  • Share Reindex
  • + {% endif %} {% if perms.osf.change_maintenancestate %}
  • Maintenance Alerts
  • {% endif %} diff --git a/admin/templates/institutions/detail.html b/admin/templates/institutions/detail.html index 8c4a9e79e15..ef2c9d5ff94 100644 --- a/admin/templates/institutions/detail.html +++ b/admin/templates/institutions/detail.html @@ -1,7 +1,33 @@ {% extends "base.html" %} {% load static %} {% block top_includes %} - + + {% endblock %} {% load comment_extras %} {% block title %} @@ -34,6 +60,18 @@ {% if perms.osf.change_institution %} Manage Admins {% endif %} + + @@ -169,5 +207,27 @@

    Are you sure you want to run monthly report for this institution?

    }); }); }); + + window.openCopyPopup = function(text) { + const modal = document.getElementById("copy-modal"); + const input = document.getElementById("copy-input"); + input.value = text; + modal.classList.add("show_modal"); + navigator.clipboard.writeText(text).catch(() => {}); + input.focus(); + input.select(); + }; + + window.closeCopyPopup = function() { + document.getElementById("copy-modal").classList.remove("show_modal"); + }; + + // Close on outside click + window.onclick = function(event) { + const modal = document.getElementById("copy-modal"); + if (event.target === modal) { + modal.classList.remove("show_modal"); + } + }; {% endblock %} diff --git a/admin/templates/institutions/list.html b/admin/templates/institutions/list.html index f990c778d25..47e6d09233f 100644 --- a/admin/templates/institutions/list.html +++ b/admin/templates/institutions/list.html @@ -20,6 +20,7 @@

    List of Institutions

    Name Description Status + SSO Availability @@ -37,6 +38,7 @@

    List of Institutions

    {% else %} DEACTIVATED {% endif %} + {{ institution.sso_availability }} {% endfor %} diff --git a/admin/templates/management/commands.html b/admin/templates/management/commands.html index dd90affd5ff..edf242abfdd 100644 --- a/admin/templates/management/commands.html +++ b/admin/templates/management/commands.html @@ -165,6 +165,19 @@

    Sync Notification Templates

    +
    +

    Remove existing orcid info from user social

    +

    + Use this management command to remove existing orcid info from user social. +

    +
    + {% csrf_token %} + +
    +
    {% endblock %} diff --git a/admin/templates/nodes/add_file_to_osfstorage.html b/admin/templates/nodes/add_file_to_osfstorage.html new file mode 100644 index 00000000000..3a27f36b26f --- /dev/null +++ b/admin/templates/nodes/add_file_to_osfstorage.html @@ -0,0 +1,36 @@ +{% if node.is_registration and node.archived %} + + Add File (Osfstorage) + + +{% endif %} \ No newline at end of file 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/admin/templates/nodes/node.html b/admin/templates/nodes/node.html index 3e86290b249..68e451a59d4 100644 --- a/admin/templates/nodes/node.html +++ b/admin/templates/nodes/node.html @@ -29,6 +29,13 @@ +
    +
    +
    + {% include "nodes/add_file_to_osfstorage.html" with node=node %} + {% include "nodes/remove_file_from_osfstorage.html" with node=node %} +
    +

    {{ node.type|cut:'osf.'|title }}: {{ node.title }} ({{node.guid}})

    diff --git a/admin/templates/nodes/remove_file_from_osfstorage.html b/admin/templates/nodes/remove_file_from_osfstorage.html new file mode 100644 index 00000000000..6c26c35ee89 --- /dev/null +++ b/admin/templates/nodes/remove_file_from_osfstorage.html @@ -0,0 +1,36 @@ +{% if node.is_registration and node.archived %} + + Remove File (Osfstorage) + + +{% endif %} \ No newline at end of file diff --git a/admin/templates/preprints/fix_coi.html b/admin/templates/preprints/fix_coi.html new file mode 100644 index 00000000000..1324c4b53c5 --- /dev/null +++ b/admin/templates/preprints/fix_coi.html @@ -0,0 +1,21 @@ +{% if perms.osf.change_preprint %} + + Fix COI + + +{% endif %} diff --git a/admin/templates/preprints/preprint.html b/admin/templates/preprints/preprint.html index d716b60aeb7..36066a94ccf 100644 --- a/admin/templates/preprints/preprint.html +++ b/admin/templates/preprints/preprint.html @@ -28,6 +28,7 @@ {% include "preprints/make_public.html" with preprint=preprint %} {% include "preprints/make_published.html" with preprint=preprint %} {% include "preprints/fix_editing.html" with preprint=preprint %} + {% include "preprints/fix_coi.html" with preprint=preprint %} {% include "preprints/assign_new_version.html" with preprint=preprint %} diff --git a/admin/templates/share_reindex/list.html b/admin/templates/share_reindex/list.html new file mode 100644 index 00000000000..34083b75696 --- /dev/null +++ b/admin/templates/share_reindex/list.html @@ -0,0 +1,133 @@ +{% extends "base.html" %} +{% load render_bundle from webpack_loader %} +{% load comment_extras %} + +{% load static %} +{% block top_includes %} + +{% endblock %} +{% block title %} + Share Reindex +{% endblock title %} +{% block content %} +

    Share Reindex

    + + {% include "util/pagination.html" with items=page extra_query_params="&type="|add:selected_resource_type %} + + +
    +
    +
    + + + +
    +
    + + SHARE Reindex All {{selected_resource_type}} + + + +
    +
    + +
    +

    {{share_reindex_message}}

    +
    + + +
    + + + + {% if selected_resource_type == 'projects' or selected_resource_type == 'preprints' or selected_resource_type == 'registries' %} + + {% elif selected_resource_type == 'users' %} + + {% else %} + + {% endif %} + + + {% if selected_resource_type != 'files' %} + + {% endif %} + + + + {% for item in items_to_index %} + + + {% if selected_resource_type == 'projects' or selected_resource_type == 'preprints' or selected_resource_type == 'registries' %} + + {% elif selected_resource_type == 'users' %} + + {% else %} + + {% endif %} + + + + {% if selected_resource_type != 'files' %} + + + {% endif %} + + + + {% endfor %} + +
    GuidTitleFullnameNameDatetime Last IndexedReindex
    + + {{item.first_guid}} + + {{item.title}}{{item.fullname}}{{item.name}}{{item.date_last_indexed}} + SHARE Reindex +
    + +{% endblock content %} \ No newline at end of file diff --git a/admin/templates/util/pagination.html b/admin/templates/util/pagination.html index 8a3a2d82ce6..59e726f52c9 100644 --- a/admin/templates/util/pagination.html +++ b/admin/templates/util/pagination.html @@ -3,11 +3,11 @@ -
    - -
    - http://orcid.org/ - -
    -
    -
    @@ -190,7 +182,6 @@