Skip to content

Commit ef3f2a6

Browse files
committed
Merge tag '26.8.1' into develop
Hotfix for registration failing to become public after approval
2 parents 3f36856 + 9bff958 commit ef3f2a6

9 files changed

Lines changed: 75 additions & 29 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.8.1 (2026-04-30)
6+
===================
7+
8+
- Hotfix for registrations sometimes fail to become public after approval
9+
510
26.8.0 (2026-04-23)
611
===================
712

api/users/views.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1480,7 +1480,6 @@ def post(self, request, *args, **kwargs):
14801480

14811481
user.date_last_logged_in = timezone.now()
14821482
user.external_identity[provider][provider_id] = 'VERIFIED'
1483-
user.social[provider.lower()] = provider_id
14841483
del user.email_verifications[token]
14851484
user.verification_key = generate_verification_key()
14861485
user.save()

osf/models/node.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1239,7 +1239,7 @@ def set_privacy(self, permissions, auth=None, log=True, save=True, meeting_creat
12391239
action_flag=DOI_CREATION_FAILED
12401240
)
12411241
raise NodeStateError(
1242-
'Unable to make registration public: DOI creation failed. '
1242+
f'Unable to make registration {self._id} public: DOI creation failed. '
12431243
'This may be due to a temporary DataCite service outage. '
12441244
'Please try again later or contact support if the issue persists.'
12451245
)

osf/models/sanctions.py

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.apps import apps
22
from django.utils import timezone
33
from django.conf import settings
4-
from django.db import models
4+
from django.db import models, transaction
55

66
from osf.utils.fields import NonNaiveDateTimeField
77

@@ -951,31 +951,32 @@ def _on_complete(self, event_data):
951951
if registration.is_spammy:
952952
raise NodeStateError('Cannot approve a spammy registration')
953953

954-
super()._on_complete(event_data)
955-
self.save()
956-
registered_from = registration.registered_from
957-
# Pass auth=None because the registration initiator may not be
958-
# an admin on components (component admins had the opportunity
959-
# to disapprove the registration by this point)
960-
registration.set_privacy('public', auth=None, log=False)
961-
for child in registration.get_descendants_recursive(primary_only=True):
962-
child.set_privacy('public', auth=None, log=False)
963-
# Accounts for system actions where no `User` performs the final approval
964-
auth = Auth(user) if user else None
965-
registered_from.add_log(
966-
action=NodeLog.REGISTRATION_APPROVAL_APPROVED,
967-
params={
968-
'node': registered_from._id,
969-
'registration': registration._id,
970-
'registration_approval_id': self._id,
971-
},
972-
auth=auth,
973-
)
974-
for node in registration.root.node_and_primary_descendants():
975-
self._add_success_logs(node, user)
976-
node.update_search() # update search if public
954+
with transaction.atomic():
955+
super()._on_complete(event_data)
956+
self.save()
957+
registered_from = registration.registered_from
958+
# Pass auth=None because the registration initiator may not be
959+
# an admin on components (component admins had the opportunity
960+
# to disapprove the registration by this point)
961+
registration.set_privacy('public', auth=None, log=False)
962+
for child in registration.get_descendants_recursive(primary_only=True):
963+
child.set_privacy('public', auth=None, log=False)
964+
# Accounts for system actions where no `User` performs the final approval
965+
auth = Auth(user) if user else None
966+
registered_from.add_log(
967+
action=NodeLog.REGISTRATION_APPROVAL_APPROVED,
968+
params={
969+
'node': registered_from._id,
970+
'registration': registration._id,
971+
'registration_approval_id': self._id,
972+
},
973+
auth=auth,
974+
)
975+
for node in registration.root.node_and_primary_descendants():
976+
self._add_success_logs(node, user)
977+
node.update_search() # update search if public
977978

978-
self.save()
979+
self.save()
979980

980981
def _on_reject(self, event_data):
981982
user = event_data.kwargs.get('user')

osf_tests/test_registrations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ def test_registration_cannot_become_public_when_doi_creation_fails(self, registr
395395
with pytest.raises(NodeStateError) as exc_info:
396396
registration.set_privacy(Node.PUBLIC, auth=auth, log=False)
397397

398-
assert 'Unable to make registration public: DOI creation failed' in str(exc_info.value)
398+
assert f'Unable to make registration {registration._id} public: DOI creation failed' in str(exc_info.value)
399399
assert registration.is_public is False
400400

401401
mock_client.create_identifier.assert_called_once()

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.8.0",
3+
"version": "26.8.1",
44
"description": "Facilitating Open Science",
55
"repository": "https://github.com/CenterForOpenScience/osf.io",
66
"author": "Center for Open Science",

scripts/approve_registrations.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ def approve_past_pendings(approvals_past_pending, dry_run=True):
3838
f'RegistrationApproval {registration_approval._id} is not attached to a registration'
3939
)
4040
continue
41+
42+
if pending_registration.is_spammy:
43+
logger.warning(
44+
f'Skipping RegistrationApproval {registration_approval._id} for '
45+
f'spammy registration {pending_registration._id}.'
46+
)
47+
continue
48+
4149
logger.warning(
4250
'RegistrationApproval {} automatically approved by system. Making registration {} public.'
4351
.format(registration_approval._id, pending_registration._id)

scripts/tests/test_approve_registrations.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from tests.base import OsfTestCase
66
from osf.models import NodeLog
7+
from osf.models.spam import SpamStatus
78
from osf_tests.factories import RegistrationFactory, UserFactory
89

910
from scripts.approve_registrations import main
@@ -73,3 +74,16 @@ def test_registration_adds_to_parent_projects_log(self):
7374
assert self.registration.registered_from.logs.filter(
7475
action=NodeLog.PROJECT_REGISTERED
7576
).exists()
77+
78+
def test_spammy_registration_is_not_auto_approved_or_made_public(self):
79+
self.registration.registration_approval.initiation_date = timezone.now() - timedelta(days=365)
80+
self.registration.is_public = False
81+
self.registration.spam_status = SpamStatus.FLAGGED
82+
self.registration.save()
83+
84+
main(dry_run=False)
85+
self.registration.registration_approval.reload()
86+
self.registration.reload()
87+
88+
assert not self.registration.is_registration_approved
89+
assert not self.registration.is_public

tests/test_registrations/test_registration_approvals.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,3 +318,22 @@ def test_accept_raises_error_if_project_is_spam(self):
318318
with pytest.raises(NodeStateError):
319319
self.registration.registration_approval.accept()
320320
assert mock_notify.call_count == 0
321+
322+
def test_accept_rolls_back_approval_if_publish_fails(self):
323+
self.registration.require_approval(self.user)
324+
self.registration.save()
325+
assert self.registration.is_pending_registration
326+
327+
with mock.patch.object(
328+
self.registration.__class__,
329+
'set_privacy',
330+
side_effect=NodeStateError('forced publish failure')
331+
):
332+
with pytest.raises(NodeStateError):
333+
self.registration.registration_approval.accept()
334+
335+
self.registration.registration_approval.reload()
336+
self.registration.reload()
337+
assert self.registration.is_pending_registration
338+
assert not self.registration.is_registration_approved
339+
assert not self.registration.is_public

0 commit comments

Comments
 (0)