diff --git a/addons/osfstorage/tests/test_models.py b/addons/osfstorage/tests/test_models.py index 5e45c1d35f1..63552a4b574 100644 --- a/addons/osfstorage/tests/test_models.py +++ b/addons/osfstorage/tests/test_models.py @@ -266,6 +266,22 @@ def test_delete_root_node(self): assert BaseFileNode.objects.get(_id=folder._id).type == 'osf.trashedfolder' assert BaseFileNode.objects.get(_id=file._id).type == 'osf.trashedfile' + def test_restore_deleted_file_without_deleted_field(self): + assert models.TrashedFileNode.objects.exists() is False + + child = self.node_settings.get_root().append_file('Test') + child.delete() + + trashed_file = models.TrashedFileNode.objects.first() + restored_file = trashed_file.restore() + + assert restored_file.deleted is None + # None because we do not set deleted_by when delete the child + assert restored_file.deleted_by is None + assert restored_file.deleted_on is not None + + assert models.TrashedFileNode.objects.exists() is False + def test_delete_file(self): child = self.node_settings.get_root().append_file('Test') field_names = [f.name for f in child._meta.get_fields() if not f.is_relation and f.name not in ['id', 'content_type_pk']] diff --git a/admin/management/urls.py b/admin/management/urls.py index 2f77bfc5c8a..cce6e013fcb 100644 --- a/admin/management/urls.py +++ b/admin/management/urls.py @@ -24,4 +24,6 @@ name='remove_orcid_from_user_social'), re_path(r'^migrate_funder_names_to_ror', views.MigrateFunderNamesToRor.as_view(), name='migrate_funder_names_to_ror'), + re_path(r'^fix_restored_trashed_files', views.FixRestoredTrashedFiles.as_view(), + name='fix_restored_trashed_files'), ] diff --git a/admin/management/views.py b/admin/management/views.py index 3c552822b8f..363ca1c154d 100644 --- a/admin/management/views.py +++ b/admin/management/views.py @@ -228,3 +228,11 @@ def post(self, request): for _line in _out_io.getvalue().split('\n'): messages.info(request, _line) return redirect(reverse('management:commands')) + + +class FixRestoredTrashedFiles(ManagementCommandPermissionView): + + def post(self, request): + call_command('fix_restored_trashed_files') + messages.success(request, 'Restored trashed files have been successfully fixed.') + return redirect(reverse('management:commands')) diff --git a/admin/templates/management/commands.html b/admin/templates/management/commands.html index aa7ec517b8f..0b3bf9669e5 100644 --- a/admin/templates/management/commands.html +++ b/admin/templates/management/commands.html @@ -226,6 +226,19 @@

Update ROR funder names to be consistent and proper.

+
+

Fix restored files

+

+ Use this management command to fix restored files that were previously trashed. +

+
+ {% csrf_token %} + +
+
{% endblock %} diff --git a/osf/management/commands/fix_restored_trashed_files.py b/osf/management/commands/fix_restored_trashed_files.py new file mode 100644 index 00000000000..48afcabe920 --- /dev/null +++ b/osf/management/commands/fix_restored_trashed_files.py @@ -0,0 +1,41 @@ +""" +Clears deleted field value for all restored OsfStorageFileNode objects +as restore() method did not remove it and such files after restore from TrashedFileNode are not shown on UI +""" +import logging + +from django.db import transaction +from django.core.management.base import BaseCommand + +from addons.osfstorage.models import OsfStorageFileNode + + +logger = logging.getLogger(__name__) + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument( + '--dry-run', + action='store_true', + help='Run without making changes', + ) + + def handle(self, *args, **options): + files_to_fix = OsfStorageFileNode.objects.filter(deleted__isnull=False) + file_ids = list(files_to_fix.values_list('_id', flat=True)) + dry_run = options.get('dry_run', False) + + if dry_run: + logger.info(f'Running in dry-run mode, the following files would be fixed: {file_ids}') + self.stdout.write(f'Running in dry-run mode, the following files would be fixed: {file_ids}') + return + + with transaction.atomic(): + for file in files_to_fix: + file.deleted = None + + OsfStorageFileNode.objects.bulk_update(files_to_fix, ['deleted'], batch_size=1000) + + logger.info(f'The following files have been fixed: {file_ids}') + self.stdout.write(f'The following files have been fixed: {file_ids}') diff --git a/osf/models/files.py b/osf/models/files.py index 0609f801034..db1b85f2e60 100644 --- a/osf/models/files.py +++ b/osf/models/files.py @@ -671,6 +671,8 @@ def restore(self, recursive=True, parent=None, save=True, deleted_on=None, clien type_cls = File if self.is_file else Folder + self.deleted = None + self.recast(self._resolve_class(type_cls)._typedmodels_type) if save: