Skip to content

Commit 88e2ecf

Browse files
committed
fix state divergence for registration approval
1 parent a1aa899 commit 88e2ecf

5 files changed

Lines changed: 67 additions & 26 deletions

File tree

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')

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: 13 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,15 @@ 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.spam_status = SpamStatus.FLAGGED
81+
self.registration.save()
82+
83+
main(dry_run=False)
84+
self.registration.registration_approval.reload()
85+
self.registration.reload()
86+
87+
assert not self.registration.is_registration_approved
88+
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)