From 25d892fee9d5a58bce419a393e1e80ce56e875b4 Mon Sep 17 00:00:00 2001 From: Vlad0n20 Date: Mon, 3 Nov 2025 16:47:14 +0200 Subject: [PATCH 1/4] Fix 502s when trying to make it public --- osf/models/node.py | 15 ++++++++++----- osf_tests/test_registrations.py | 21 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/osf/models/node.py b/osf/models/node.py index 7aee1cb0880..84af239036b 100644 --- a/osf/models/node.py +++ b/osf/models/node.py @@ -1234,11 +1234,16 @@ def set_privacy(self, permissions, auth=None, log=True, save=True, meeting_creat status.push_status_message(message, kind='info', trust=False) # Update existing identifiers - if self.get_identifier_value('doi'): - update_doi_metadata_on_change(self._id) - elif self.is_registration: - doi = self.request_identifier('doi')['doi'] - self.set_identifier_value('doi', doi) + try: + if self.get_identifier_value('doi'): + update_doi_metadata_on_change(self._id) + elif self.is_registration: + doi = self.request_identifier('doi')['doi'] + self.set_identifier_value('doi', doi) + except Exception: + logger.exception( + f'Failed to create/update DOI for {self._id} during set_privacy. ' + ) if log: action = NodeLog.MADE_PUBLIC if permissions == 'public' else NodeLog.MADE_PRIVATE diff --git a/osf_tests/test_registrations.py b/osf_tests/test_registrations.py index a93ffba6264..e9cb101d988 100644 --- a/osf_tests/test_registrations.py +++ b/osf_tests/test_registrations.py @@ -373,6 +373,27 @@ def test_legacy_private_registrations_can_be_made_public(self, registration, aut registration.set_privacy(Node.PUBLIC, auth=auth) assert registration.is_public + def test_registration_becomes_public_even_when_doi_creation_fails(self, registration, auth): + registration.is_public = False + existing_doi = registration.get_identifier('doi') + if existing_doi: + existing_doi.delete() + registration.save() + + assert registration.get_identifier_value('doi') is None + + with mock.patch.object(registration, 'get_doi_client') as mock_get_client: + mock_client = mock.Mock() + mock_client.create_identifier.side_effect = Exception('DataCite API unavailable') + mock_get_client.return_value = mock_client + + result = registration.set_privacy(Node.PUBLIC, auth=auth, log=False) + + assert registration.is_public is True + assert result is True + + mock_client.create_identifier.assert_called_once() + class TestRegisterNodeContributors: From ab8ca9cbcf919a699abb23d438d355556c70c26e Mon Sep 17 00:00:00 2001 From: Vlad0n20 Date: Tue, 4 Nov 2025 16:51:18 +0200 Subject: [PATCH 2/4] Update except block for get_identifier_value --- osf/models/node.py | 19 ++++++++++++------- osf_tests/test_registrations.py | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/osf/models/node.py b/osf/models/node.py index 84af239036b..3d35538ef8a 100644 --- a/osf/models/node.py +++ b/osf/models/node.py @@ -1234,16 +1234,21 @@ def set_privacy(self, permissions, auth=None, log=True, save=True, meeting_creat status.push_status_message(message, kind='info', trust=False) # Update existing identifiers - try: - if self.get_identifier_value('doi'): + if self.get_identifier_value('doi'): + try: update_doi_metadata_on_change(self._id) - elif self.is_registration: + except Exception: + logger.exception( + f'Failed to update DOI metadata for {self._id} during set_privacy. ' + ) + elif self.is_registration: + try: doi = self.request_identifier('doi')['doi'] self.set_identifier_value('doi', doi) - except Exception: - logger.exception( - f'Failed to create/update DOI for {self._id} during set_privacy. ' - ) + except Exception: + logger.exception( + f'Failed to create DOI for registration {self._id} during set_privacy. ' + ) if log: action = NodeLog.MADE_PUBLIC if permissions == 'public' else NodeLog.MADE_PRIVATE diff --git a/osf_tests/test_registrations.py b/osf_tests/test_registrations.py index e9cb101d988..690fca63dbf 100644 --- a/osf_tests/test_registrations.py +++ b/osf_tests/test_registrations.py @@ -394,6 +394,24 @@ def test_registration_becomes_public_even_when_doi_creation_fails(self, registra mock_client.create_identifier.assert_called_once() + @mock.patch('osf.models.node.update_doi_metadata_on_change') + def test_registration_becomes_public_even_when_doi_metadata_update_fails(self, mock_update_doi, registration, auth): + + registration.is_public = False + registration.set_identifier_value('doi', '10.1234/test.doi') + registration.save() + + assert registration.get_identifier_value('doi') == '10.1234/test.doi' + + mock_update_doi.side_effect = Exception('DataCite metadata update failed') + + result = registration.set_privacy(Node.PUBLIC, auth=auth, log=False) + + assert registration.is_public is True + assert result is True + + mock_update_doi.assert_called_once_with(registration._id) + class TestRegisterNodeContributors: From 90bba906f82c0fa893909da7d9129e8712a7b787 Mon Sep 17 00:00:00 2001 From: Vlad0n20 Date: Wed, 5 Nov 2025 16:34:22 +0200 Subject: [PATCH 3/4] Fix F811 redefinition of unused 'Auth' --- admin_tests/preprints/test_views.py | 1 - 1 file changed, 1 deletion(-) diff --git a/admin_tests/preprints/test_views.py b/admin_tests/preprints/test_views.py index 357ff643a06..5731c5c9aac 100644 --- a/admin_tests/preprints/test_views.py +++ b/admin_tests/preprints/test_views.py @@ -23,7 +23,6 @@ from osf.models.spam import SpamStatus from osf.utils.workflows import DefaultStates, RequestTypes from osf.utils.permissions import ADMIN -from framework.auth import Auth from admin_tests.utilities import setup_view, setup_log_view, handle_post_view_request From 200f244850ccf14fd2915c80a83572f9e622a6de Mon Sep 17 00:00:00 2001 From: Vlad0n20 Date: Wed, 5 Nov 2025 17:16:15 +0200 Subject: [PATCH 4/4] Add admin loging if DOI creation failed --- osf/models/admin_log_entry.py | 3 +++ osf/models/node.py | 46 ++++++++++++++++++++++++++------- osf_tests/test_registrations.py | 10 ++++--- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/osf/models/admin_log_entry.py b/osf/models/admin_log_entry.py index 169bbe3faef..99f4de940f5 100644 --- a/osf/models/admin_log_entry.py +++ b/osf/models/admin_log_entry.py @@ -34,6 +34,9 @@ PREPRINT_REMOVED = 70 PREPRINT_RESTORED = 71 +DOI_CREATION_FAILED = 80 +DOI_UPDATE_FAILED = 81 + def update_admin_log(user_id, object_id, object_repr, message, action_flag=UNKNOWN): AdminLogEntry.objects.log_action( user_id=user_id, diff --git a/osf/models/node.py b/osf/models/node.py index 3d35538ef8a..733e18c60eb 100644 --- a/osf/models/node.py +++ b/osf/models/node.py @@ -1214,6 +1214,30 @@ def set_privacy(self, permissions, auth=None, log=True, save=True, meeting_creat # Embargoed registrations can be made public early self.request_embargo_termination(auth.user) return False + + if not self.get_identifier_value('doi'): + try: + doi = self.request_identifier('doi')['doi'] + self.set_identifier_value('doi', doi) + except Exception as e: + from osf.models.admin_log_entry import update_admin_log, DOI_CREATION_FAILED + logger.exception( + f'Failed to create DOI for registration {self._id} during set_privacy. ' + f'Registration cannot be made public without a DOI.' + ) + if auth and auth.user: + update_admin_log( + user_id=auth.user.id, + object_id=self._id, + object_repr=f'Registration {self.title}', + message=f'DOI creation failed during make public: {str(e)}. DataCite may be unavailable.', + action_flag=DOI_CREATION_FAILED + ) + raise NodeStateError( + 'Unable to make registration public: DOI creation failed. ' + 'This may be due to a temporary DataCite service outage. ' + 'Please try again later or contact support if the issue persists.' + ) self.is_public = True elif permissions == 'private' and self.is_public: if self.is_registration and not self.is_pending_embargo and not force: @@ -1233,22 +1257,24 @@ def set_privacy(self, permissions, auth=None, log=True, save=True, meeting_creat if message: status.push_status_message(message, kind='info', trust=False) - # Update existing identifiers + # Update existing identifiers metadata if self.get_identifier_value('doi'): try: update_doi_metadata_on_change(self._id) - except Exception: + except Exception as e: + from osf.models.admin_log_entry import update_admin_log, DOI_UPDATE_FAILED logger.exception( f'Failed to update DOI metadata for {self._id} during set_privacy. ' ) - elif self.is_registration: - try: - doi = self.request_identifier('doi')['doi'] - self.set_identifier_value('doi', doi) - except Exception: - logger.exception( - f'Failed to create DOI for registration {self._id} during set_privacy. ' - ) + # Log DOI metadata update failures for tracking + if auth and auth.user and self.is_registration: + update_admin_log( + user_id=auth.user.id, + object_id=self._id, + object_repr=f'Registration {self.title}', + message=f'DOI metadata update failed: {str(e)}. DataCite may be unavailable.', + action_flag=DOI_UPDATE_FAILED + ) if log: action = NodeLog.MADE_PUBLIC if permissions == 'public' else NodeLog.MADE_PRIVATE diff --git a/osf_tests/test_registrations.py b/osf_tests/test_registrations.py index 690fca63dbf..cb23fdc74ac 100644 --- a/osf_tests/test_registrations.py +++ b/osf_tests/test_registrations.py @@ -6,6 +6,7 @@ from django.utils import timezone from framework.auth.core import Auth from framework.exceptions import PermissionsError +from osf.exceptions import NodeStateError from osf.models import Node, Registration, Sanction, RegistrationSchema, NodeLog, GuidMetadataRecord from addons.wiki.models import WikiPage from osf.utils.permissions import ADMIN @@ -373,7 +374,7 @@ def test_legacy_private_registrations_can_be_made_public(self, registration, aut registration.set_privacy(Node.PUBLIC, auth=auth) assert registration.is_public - def test_registration_becomes_public_even_when_doi_creation_fails(self, registration, auth): + def test_registration_cannot_become_public_when_doi_creation_fails(self, registration, auth): registration.is_public = False existing_doi = registration.get_identifier('doi') if existing_doi: @@ -387,10 +388,11 @@ def test_registration_becomes_public_even_when_doi_creation_fails(self, registra mock_client.create_identifier.side_effect = Exception('DataCite API unavailable') mock_get_client.return_value = mock_client - result = registration.set_privacy(Node.PUBLIC, auth=auth, log=False) + with pytest.raises(NodeStateError) as exc_info: + registration.set_privacy(Node.PUBLIC, auth=auth, log=False) - assert registration.is_public is True - assert result is True + assert 'Unable to make registration public: DOI creation failed' in str(exc_info.value) + assert registration.is_public is False mock_client.create_identifier.assert_called_once()