From 1b7a702f34a64230051bb8e4914aff098f9aa584 Mon Sep 17 00:00:00 2001 From: Vlad0n20 Date: Fri, 18 Sep 2026 17:57:50 +0200 Subject: [PATCH] Fix permission error for preprint recovering --- admin/preprints/forms.py | 7 ++++--- admin/preprints/views.py | 12 +++++++++--- admin_tests/preprints/test_views.py | 30 +++++++++++++++++++++++++++++ osf/models/preprint.py | 2 +- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/admin/preprints/forms.py b/admin/preprints/forms.py index 6c267bd4301..6d71e625ebb 100644 --- a/admin/preprints/forms.py +++ b/admin/preprints/forms.py @@ -15,9 +15,10 @@ class RecoverDeletedPreprintForm(forms.Form): description = forms.CharField(widget=forms.Textarea, required=False) file_guid = forms.CharField( required=False, - label='Source file GUID', - help_text='Optional: an existing file GUID; its latest version is copied into this ' - 'version as the primary file.', + label='Source file or preprint GUID', + help_text='Optional: an existing file GUID, or a preprint GUID (e.g. abcde or abcde_v1) whose ' + 'primary file is used. The latest version of the file is copied into this version ' + 'as the primary file.', ) ticket_reference = forms.CharField( max_length=255, diff --git a/admin/preprints/views.py b/admin/preprints/views.py index 09c7e7b4854..72c30675021 100644 --- a/admin/preprints/views.py +++ b/admin/preprints/views.py @@ -277,14 +277,20 @@ class RecoverDeletedPreprintView(PermissionRequiredMixin, FormView): form_class = RecoverDeletedPreprintForm def _copy_primary_file(self, preprint, file_guid): - source_file = getattr(Guid.load(file_guid), 'referent', None) + source, _ = Guid.load_referent(file_guid) + if isinstance(source, Preprint): + source_file = source.primary_file + if source_file is None: + raise ValueError(f'Preprint "{file_guid}" has no primary file to copy.') + else: + source_file = source if not isinstance(source_file, BaseFileNode): - raise ValueError(f'No file found for guid "{file_guid}".') + raise ValueError(f'No file or preprint found for guid "{file_guid}".') latest_version = source_file.versions.order_by('-created').first() if latest_version is None: raise ValueError(f'File "{file_guid}" has no versions to copy.') copied = copy_files(source_file, target_node=preprint, identifier=latest_version.identifier) - preprint.set_primary_file(copied, auth=self.request, save=True) + preprint.set_primary_file(copied, auth=self.request, save=True, ignore_permission=True) def form_valid(self, form): data = form.cleaned_data diff --git a/admin_tests/preprints/test_views.py b/admin_tests/preprints/test_views.py index 475de358bca..baea8f278c1 100644 --- a/admin_tests/preprints/test_views.py +++ b/admin_tests/preprints/test_views.py @@ -1085,6 +1085,36 @@ def test_copies_primary_file_from_source_guid(self): assert recovered.primary_file is not None assert recovered.primary_file.copied_from_id == source_file.id + def test_copies_primary_file_from_source_preprint_versioned_guid(self): + source = PreprintFactory(provider=self.provider) + source_file = source.primary_file + + response = self._post(self._base_data(file_guid=source._id)) + assert response.status_code == 302 + + recovered = Preprint.load('abcde') + assert recovered.primary_file.copied_from_id == source_file.id + + def test_copies_primary_file_when_admin_is_not_a_contributor(self): + source = PreprintFactory(provider=self.provider) + target = PreprintFactory(provider=self.provider) + guid_str = target._id.split('_v')[0] + target.deleted = timezone.now() + target.save() + assert not target.has_permission(self.user, 'write') + + response = self._post(self._base_data(guid=guid_str, file_guid=source._id)) + assert response.status_code == 302 + + recovered = Preprint.load(guid_str) + assert recovered.deleted is None + assert recovered.primary_file.copied_from_id == source.primary_file.id + + def test_unknown_source_guid_shows_error(self): + response = self._post(self._base_data(file_guid='zzzzz_v1')) + assert response.status_code == 302 + assert Preprint.load('abcde') is None + 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. diff --git a/osf/models/preprint.py b/osf/models/preprint.py index 578a87d0a9f..24616e83a76 100644 --- a/osf/models/preprint.py +++ b/osf/models/preprint.py @@ -913,7 +913,7 @@ def set_primary_file(self, preprint_file, auth, save=False, **kwargs): action=PreprintLog.FILE_UPDATED, params={ 'preprint': self._id, - 'file': self.primary_file._id + 'file': str(self.primary_file._id) }, auth=auth, save=False