diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index cd1cee6b3..ee3b44e0c 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -385,10 +385,10 @@ class Command(CryptMixin, PaperlessCommand): "workflow_webhook_actions": WorkflowActionWebhook.objects.all(), "workflows": Workflow.objects.all(), "custom_fields": CustomField.objects.all(), - "custom_field_instances": CustomFieldInstance.objects.all(), + "custom_field_instances": CustomFieldInstance.global_objects.all(), "app_configs": ApplicationConfiguration.objects.all(), - "notes": Note.objects.all(), - "documents": Document.objects.order_by("id").all(), + "notes": Note.global_objects.all(), + "documents": Document.global_objects.order_by("id").all(), "social_accounts": SocialAccount.objects.all(), "social_apps": SocialApp.objects.all(), "social_tokens": SocialToken.objects.all(), @@ -443,7 +443,7 @@ class Command(CryptMixin, PaperlessCommand): writer.write_batch(batch) document_map: dict[int, Document] = { - d.pk: d for d in Document.objects.order_by("id") + d.pk: d for d in Document.global_objects.order_by("id") } # 3. Export files from each document @@ -619,12 +619,15 @@ class Command(CryptMixin, PaperlessCommand): """Write per-document manifest file for --split-manifest mode.""" content = [document_dict] content.extend( - serializers.serialize("python", Note.objects.filter(document=document)), + serializers.serialize( + "python", + Note.global_objects.filter(document=document), + ), ) content.extend( serializers.serialize( "python", - CustomFieldInstance.objects.filter(document=document), + CustomFieldInstance.global_objects.filter(document=document), ), ) manifest_name = base_name.with_name(f"{base_name.stem}-manifest.json") diff --git a/src/documents/management/commands/document_importer.py b/src/documents/management/commands/document_importer.py index c0056c062..4572b4617 100644 --- a/src/documents/management/commands/document_importer.py +++ b/src/documents/management/commands/document_importer.py @@ -125,7 +125,7 @@ class Command(CryptMixin, PaperlessCommand): "Found existing user(s), this might indicate a non-empty installation", ), ) - if Document.objects.count() != 0: + if Document.global_objects.count() != 0: self.stdout.write( self.style.WARNING( "Found existing documents(s), this might indicate a non-empty installation", @@ -376,7 +376,7 @@ class Command(CryptMixin, PaperlessCommand): ] for record in self.track(document_records, description="Copying files..."): - document = Document.objects.get(pk=record["pk"]) + document = Document.global_objects.get(pk=record["pk"]) doc_file = record[EXPORTER_FILE_NAME] document_path = self.source / doc_file diff --git a/src/documents/tests/test_management_exporter.py b/src/documents/tests/test_management_exporter.py index fb9effa0e..a214ef51d 100644 --- a/src/documents/tests/test_management_exporter.py +++ b/src/documents/tests/test_management_exporter.py @@ -389,7 +389,7 @@ class TestExportImport( self.assertIsFile( str(self.target / doc_from_manifest[EXPORTER_FILE_NAME]), ) - self.d3.delete() + self.d3.hard_delete() manifest = self._do_export() self.assertRaises( @@ -868,6 +868,52 @@ class TestExportImport( for obj in manifest: self.assertNotEqual(obj["model"], "auditlog.logentry") + def test_export_import_soft_deleted_document(self) -> None: + """ + GIVEN: + - A document with a note and custom field instance has been soft-deleted + WHEN: + - Export and re-import are performed + THEN: + - The soft-deleted document, note, and custom field instance + survive the round-trip with deleted_at preserved + """ + shutil.rmtree(Path(self.dirs.media_dir) / "documents") + shutil.copytree( + Path(__file__).parent / "samples" / "documents", + Path(self.dirs.media_dir) / "documents", + ) + + # d1 has self.note and self.cfi1 attached via setUp + self.d1.delete() + + self._do_export() + + with paperless_environment(): + Document.global_objects.all().hard_delete() + Correspondent.objects.all().delete() + DocumentType.objects.all().delete() + Tag.objects.all().delete() + + call_command( + "document_importer", + "--no-progress-bar", + self.target, + skip_checks=True, + ) + + self.assertEqual(Document.global_objects.count(), 4) + reimported_doc = Document.global_objects.get(pk=self.d1.pk) + self.assertIsNotNone(reimported_doc.deleted_at) + + self.assertEqual(Note.global_objects.count(), 1) + reimported_note = Note.global_objects.get(pk=self.note.pk) + self.assertIsNotNone(reimported_note.deleted_at) + + self.assertEqual(CustomFieldInstance.global_objects.count(), 1) + reimported_cfi = CustomFieldInstance.global_objects.get(pk=self.cfi1.pk) + self.assertIsNotNone(reimported_cfi.deleted_at) + def test_export_data_only(self) -> None: """ GIVEN: