Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions admin_tests/preprints/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1062,3 +1062,47 @@ def test_copies_primary_file_from_source_guid(self):
recovered = Preprint.load('abcde')
assert recovered.primary_file is not None
assert recovered.primary_file.copied_from_id == source_file.id

def _orphan_guid(self, provider):
# Leave a base Guid with a dangling referent (the deleted-preprint state we recover from).
# Detach the GenericRelation first so deleting the preprint doesn't cascade the Guid away.
from osf.models import Guid
pp = PreprintFactory(provider=provider)
guid_str = pp.get_guid()._id
Guid.objects.filter(_id=guid_str).update(object_id=None, content_type=None)
pp.versioned_guids.all().delete()
Preprint.objects.filter(id=pp.id).delete()
return guid_str

def test_recover_reuses_orphaned_guid(self):
from osf.models import Guid
guid_str = self._orphan_guid(self.provider)
assert Guid.objects.filter(_id=guid_str).exists()
assert Guid.objects.get(_id=guid_str).referent is None
assert Preprint.load(guid_str) is None

response = self._post(self._base_data(guid=guid_str))
assert response.status_code == 302

recovered = Preprint.load(guid_str)
assert recovered is not None
assert recovered._id == f'{guid_str}_v1'
assert recovered.title == 'Recovered Title'
assert Guid.objects.filter(_id=guid_str).count() == 1
assert Guid.objects.get(_id=guid_str).referent == recovered
assert AdminLogEntry.objects.filter(action_flag=PREPRINT_RECOVERED).exists()

def test_create_rejects_guid_pointing_at_live_preprint(self):
from django.core.exceptions import ValidationError
live = PreprintFactory(provider=self.provider)
guid_str = live.get_guid()._id
with pytest.raises(ValidationError, match='GUID cannot be manually assigned'):
Preprint.create(
provider=self.provider,
title='x',
creator=self.user,
description='y',
manual_guid=guid_str,
)
# The live preprint's guid still points at it, untouched.
assert Preprint.load(guid_str).id == live.id
12 changes: 10 additions & 2 deletions osf/models/preprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,17 @@ def create(cls, provider, title, creator, description, manual_guid=None, manual_
preprint.save(guid_ready=False)
# Step 2: Create the base guid obj
if manual_guid:
if not check_manually_assigned_guid(manual_guid):
existing_guid = Guid.objects.filter(_id=manual_guid).first()
if existing_guid is not None:
# Reuse an orphaned guid so deleted preprints can be recovered at their
# original id; a guid still pointing at a live object is a real collision.
if existing_guid.referent is not None:
raise ValidationError(f'GUID cannot be manually assigned: guid_str={manual_guid}.')
base_guid_obj = existing_guid
elif check_manually_assigned_guid(manual_guid):
base_guid_obj = Guid.objects.create(_id=manual_guid)
else:
raise ValidationError(f'GUID cannot be manually assigned: guid_str={manual_guid}.')
base_guid_obj = Guid.objects.create(_id=manual_guid)
else:
base_guid_obj = Guid.objects.create()
base_guid_obj.referent = preprint
Expand Down
Loading