From b3bc5d2ce6792dbc536ec256de4fa6a59e9593b8 Mon Sep 17 00:00:00 2001 From: shamoon <4887959+shamoon@users.noreply.github.com> Date: Tue, 25 Feb 2025 23:58:28 -0800 Subject: [PATCH] Cleanup custom fields from saved views on deletion --- src/documents/signals/handlers.py | 28 ++++++++++++++++++ src/documents/tests/test_api_documents.py | 36 +++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/documents/signals/handlers.py b/src/documents/signals/handlers.py index 4345e04d5..b3f029da7 100644 --- a/src/documents/signals/handlers.py +++ b/src/documents/signals/handlers.py @@ -36,6 +36,7 @@ from documents.models import Document from documents.models import DocumentType from documents.models import MatchingModel from documents.models import PaperlessTask +from documents.models import SavedView from documents.models import Tag from documents.models import Workflow from documents.models import WorkflowAction @@ -549,6 +550,33 @@ def check_paths_and_prune_custom_fields(sender, instance: CustomField, **kwargs) update_filename_and_move_files(sender, cf_instance) +@receiver(models.signals.post_delete, sender=CustomField) +def cleanup_custom_field_deletion(sender, instance: CustomField, **kwargs): + """ + When a custom field is deleted, ensure no saved views reference it. + """ + field_identifier = SavedView.DisplayFields.CUSTOM_FIELD % instance.pk + # remove field from display_fields of all saved views + for view in SavedView.objects.filter(display_fields__isnull=False).distinct(): + if field_identifier in view.display_fields: + logger.debug( + f"Removing custom field {instance} from view {view}", + ) + view.display_fields.remove(field_identifier) + view.save() + + # remove from sort_field of all saved views + views_with_sort_updated = SavedView.objects.filter( + sort_field=field_identifier, + ).update( + sort_field=SavedView.DisplayFields.CREATED, + ) + if views_with_sort_updated > 0: + logger.debug( + f"Removing custom field {instance} from sort field of {views_with_sort_updated} views", + ) + + def add_to_index(sender, document, **kwargs): from documents import index diff --git a/src/documents/tests/test_api_documents.py b/src/documents/tests/test_api_documents.py index 28261b392..cd923b281 100644 --- a/src/documents/tests/test_api_documents.py +++ b/src/documents/tests/test_api_documents.py @@ -1925,6 +1925,42 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase): ) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + def test_saved_view_cleanup_after_custom_field_deletion(self): + """ + GIVEN: + - Saved view with custom field in display fields and as sort field + WHEN: + - Custom field is deleted + THEN: + - Custom field is removed from display fields and sort field + """ + custom_field = CustomField.objects.create( + name="stringfield", + data_type=CustomField.FieldDataType.STRING, + ) + + view = SavedView.objects.create( + owner=self.user, + name="test", + sort_field=SavedView.DisplayFields.CUSTOM_FIELD % custom_field.id, + show_on_dashboard=True, + show_in_sidebar=True, + display_fields=[ + SavedView.DisplayFields.TITLE, + SavedView.DisplayFields.CREATED, + SavedView.DisplayFields.CUSTOM_FIELD % custom_field.id, + ], + ) + + custom_field.delete() + + view.refresh_from_db() + self.assertEqual(view.sort_field, SavedView.DisplayFields.CREATED) + self.assertEqual( + view.display_fields, + [str(SavedView.DisplayFields.TITLE), str(SavedView.DisplayFields.CREATED)], + ) + def test_get_logs(self): log_data = "test\ntest2\n" with (Path(settings.LOGGING_DIR) / "mail.log").open("w") as f: