diff --git a/admin/nodes/urls.py b/admin/nodes/urls.py index 1d5f6e0bac9..cf00bf463ad 100644 --- a/admin/nodes/urls.py +++ b/admin/nodes/urls.py @@ -46,4 +46,5 @@ re_path(r'^(?P[a-z0-9]+)/update_moderation_state/$', views.NodeUpdateModerationStateView.as_view(), name='node-update-mod-state'), re_path(r'^(?P[a-z0-9]+)/resync_datacite/$', views.NodeResyncDataCiteView.as_view(), name='resync-datacite'), re_path(r'^(?P[a-z0-9]+)/revert/$', views.NodeRevertToDraft.as_view(), name='revert-to-draft'), + re_path(r'^(?P[a-z0-9]+)/remove_file/$', views.NodeRemoveFileView.as_view(), name='remove-file'), ] diff --git a/admin/nodes/views.py b/admin/nodes/views.py index db0f0119f18..1df1a6c1f3a 100644 --- a/admin/nodes/views.py +++ b/admin/nodes/views.py @@ -6,6 +6,7 @@ from django.utils import timezone from django.core.exceptions import PermissionDenied, ValidationError from django.urls import NoReverseMatch +from django.db import transaction from django.db.models import F, Case, When, IntegerField from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin @@ -27,14 +28,17 @@ from api.caching.tasks import update_storage_usage_cache from osf.exceptions import NodeStateError, RegistrationStuckError +from osf.management.commands.change_node_region import _update_schema_meta from osf.models import ( + Guid, OSFUser, NodeLog, AbstractNode, Registration, RegistrationProvider, RegistrationApproval, - SpamStatus + SpamStatus, + TrashedFile ) from osf.models.admin_log_entry import ( update_admin_log, @@ -697,6 +701,36 @@ def post(self, request, *args, **kwargs): return redirect(self.get_success_url()) +class NodeRemoveFileView(NodeMixin, View): + """ Allows an authorized user to remove file from node. + """ + permission_required = 'osf.change_node' + + def post(self, request, *args, **kwargs): + def _remove_file_from_schema_response_blocks(registration, removed_file_id): + file_input_keys = registration.registration_schema.schema_blocks.filter( + block_type='file-input' + ).values_list('registration_response_key', flat=True) + for schema_response in registration.schema_responses.all(): + for block in schema_response.response_blocks.filter(schema_key__in=file_input_keys): + if not block.response: + continue + block.response = [entry for entry in block.response if entry.get('file_id') not in removed_file_id] + block.save() + + node = self.get_object() + guid_id = request.POST.get('remove-file-guid', '').strip() + guid = Guid.load(guid_id) + + # delete file from registration and metadata and keep it for original project + if guid and (file := guid.referent) and (file.target == node) and not isinstance(file, TrashedFile): + with transaction.atomic(): + file.delete() + _update_schema_meta(file.target) + _remove_file_from_schema_response_blocks(node, [file._id, file.copied_from._id]) + return redirect(self.get_success_url()) + + class RemoveStuckRegistrationsView(NodeMixin, View): """ Allows an authorized user to remove a registrations if it's stuck in the archiving process. """ diff --git a/admin/templates/nodes/node.html b/admin/templates/nodes/node.html index caf8bd5ebc6..4b112fb6832 100644 --- a/admin/templates/nodes/node.html +++ b/admin/templates/nodes/node.html @@ -17,6 +17,7 @@ View Logs {% include "nodes/remove_node.html" with node=node %} + {% include "nodes/remove_file.html" with node=node %} {% include "nodes/registration_force_archive.html" with node=node %} {% include "nodes/make_private.html" with node=node %} {% include "nodes/make_public.html" with node=node %} diff --git a/admin/templates/nodes/remove_file.html b/admin/templates/nodes/remove_file.html new file mode 100644 index 00000000000..0f50e39750e --- /dev/null +++ b/admin/templates/nodes/remove_file.html @@ -0,0 +1,36 @@ +{% if node.is_registration and node.archived %} + + Delete File + + +{% endif %} \ No newline at end of file diff --git a/osf/management/commands/change_node_region.py b/osf/management/commands/change_node_region.py index abce28672bf..e02e90feae4 100644 --- a/osf/management/commands/change_node_region.py +++ b/osf/management/commands/change_node_region.py @@ -39,11 +39,14 @@ def _update_blocks(file_block_map, original_id, cloned_id): block.save() def _update_schema_meta(node): - logger.info('Updating legacy schema information...') - node.registration_responses = node.schema_responses.latest('-created').all_responses - node.registered_meta[node.registration_schema._id] = node.expand_registration_responses() - node.save() - logger.info('Updated legacy schema information.') + try: + logger.info('Updating legacy schema information...') + node.registration_responses = node.schema_responses.latest('-created').all_responses + node.registered_meta[node.registration_schema._id] = node.expand_registration_responses() + node.save() + logger.info('Updated legacy schema information.') + except Exception: + logger.error('There is no data in schema responses to update legacy schema information.') def _copy_and_clone_versions(original_file, cloned_file, src_bucket, dest_bucket, dest_bucket_name, dest_region): for v in original_file.versions.order_by('identifier').all():