diff --git a/src/documents/management/commands/document_exporter.py b/src/documents/management/commands/document_exporter.py index cd1cee6b3..f640101fa 100644 --- a/src/documents/management/commands/document_exporter.py +++ b/src/documents/management/commands/document_exporter.py @@ -388,7 +388,7 @@ class Command(CryptMixin, PaperlessCommand): "custom_field_instances": CustomFieldInstance.objects.all(), "app_configs": ApplicationConfiguration.objects.all(), "notes": Note.objects.all(), - "documents": Document.objects.order_by("id").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 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..d77bea352 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,62 @@ class TestExportImport( for obj in manifest: self.assertNotEqual(obj["model"], "auditlog.logentry") + def test_export_import_soft_deleted_document(self) -> None: + """ + GIVEN: + - A document has been soft-deleted + WHEN: + - Export and re-import are performed + THEN: + - The soft-deleted document is included in the manifest + - The soft-deleted document is re-imported successfully + - The deleted_at timestamp is preserved after import + """ + shutil.rmtree(Path(self.dirs.media_dir) / "documents") + shutil.copytree( + Path(__file__).parent / "samples" / "documents", + Path(self.dirs.media_dir) / "documents", + ) + + # Soft-delete one document + self.d2.delete() + self.d2.refresh_from_db() + self.assertIsNotNone(self.d2.deleted_at) + + # Non-deleted documents visible via .objects should be 3 + self.assertEqual(Document.objects.count(), 3) + # All documents including soft-deleted should be 4 + self.assertEqual(Document.global_objects.count(), 4) + + manifest = self._do_export() + + # Manifest must contain all 4 documents, including the soft-deleted one + doc_records = [e for e in manifest if e["model"] == "documents.document"] + self.assertEqual(len(doc_records), 4) + + exported_pks = {r["pk"] for r in doc_records} + self.assertIn(self.d2.pk, exported_pks) + + # Re-import + with paperless_environment(): + Document.global_objects.all().hard_delete() + Correspondent.objects.all().delete() + DocumentType.objects.all().delete() + Tag.objects.all().delete() + self.assertEqual(Document.global_objects.count(), 0) + + call_command( + "document_importer", + "--no-progress-bar", + self.target, + skip_checks=True, + ) + + self.assertEqual(Document.global_objects.count(), 4) + # The soft-deleted document should still have deleted_at set + reimported = Document.global_objects.get(pk=self.d2.pk) + self.assertIsNotNone(reimported.deleted_at) + def test_export_data_only(self) -> None: """ GIVEN: