mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-04-04 07:08:51 +00:00
Compare commits
3 Commits
chore/impr
...
feature-ve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
559f824b85 | ||
|
|
c994841a75 | ||
|
|
2ea83374ed |
@@ -50,6 +50,7 @@ from documents.utils import compute_checksum
|
||||
from documents.utils import copy_basic_file_stats
|
||||
from documents.utils import copy_file_with_basic_stats
|
||||
from documents.utils import run_subprocess
|
||||
from documents.versioning import sync_root_latest_content
|
||||
from paperless.parsers import ParserContext
|
||||
from paperless.parsers import ParserProtocol
|
||||
from paperless.parsers.registry import get_parser_registry
|
||||
@@ -538,6 +539,8 @@ class ConsumerPlugin(
|
||||
else:
|
||||
original_document.save()
|
||||
|
||||
sync_root_latest_content(root_doc)
|
||||
|
||||
# Create a log entry for the version addition, if enabled
|
||||
if settings.AUDIT_LOG_ENABLED:
|
||||
from auditlog.models import ( # type: ignore[import-untyped]
|
||||
|
||||
23
src/documents/migrations/0017_document_latest_content.py
Normal file
23
src/documents/migrations/0017_document_latest_content.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.12 on 2026-03-31 18:52
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("documents", "0016_sha256_checksums"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="document",
|
||||
name="latest_content",
|
||||
field=models.TextField(
|
||||
blank=True,
|
||||
help_text="Materialized effective content for root documents. Uses the latest version content when available.",
|
||||
null=True,
|
||||
verbose_name="latest content",
|
||||
),
|
||||
),
|
||||
]
|
||||
@@ -196,6 +196,16 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
|
||||
),
|
||||
)
|
||||
|
||||
latest_content = models.TextField(
|
||||
_("latest content"),
|
||||
blank=True,
|
||||
null=True,
|
||||
help_text=_(
|
||||
"Materialized effective content for root documents. "
|
||||
"Uses the latest version content when available.",
|
||||
),
|
||||
)
|
||||
|
||||
content_length = models.GeneratedField(
|
||||
expression=Length("content"),
|
||||
output_field=PositiveIntegerField(default=0),
|
||||
@@ -375,27 +385,7 @@ class Document(SoftDeleteModel, ModelWithOwner): # type: ignore[django-manager-
|
||||
if self.root_document_id is not None or self.pk is None:
|
||||
return self.content
|
||||
|
||||
prefetched_cache = getattr(self, "_prefetched_objects_cache", None)
|
||||
prefetched_versions = (
|
||||
prefetched_cache.get("versions")
|
||||
if isinstance(prefetched_cache, dict)
|
||||
else None
|
||||
)
|
||||
if prefetched_versions:
|
||||
latest_prefetched = max(prefetched_versions, key=lambda doc: doc.id)
|
||||
return latest_prefetched.content
|
||||
|
||||
latest_version_content = (
|
||||
Document.objects.filter(root_document=self)
|
||||
.order_by("-id")
|
||||
.values_list("content", flat=True)
|
||||
.first()
|
||||
)
|
||||
return (
|
||||
latest_version_content
|
||||
if latest_version_content is not None
|
||||
else self.content
|
||||
)
|
||||
return self.latest_content if self.latest_content is not None else self.content
|
||||
|
||||
@property
|
||||
def suggestion_content(self):
|
||||
|
||||
@@ -15,49 +15,48 @@ from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestBulkEditAPI(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
super().setUpTestData()
|
||||
|
||||
cls.user = User.objects.create_superuser(username="temp_admin")
|
||||
cls.c1 = Correspondent.objects.create(name="c1")
|
||||
cls.c2 = Correspondent.objects.create(name="c2")
|
||||
cls.dt1 = DocumentType.objects.create(name="dt1")
|
||||
cls.dt2 = DocumentType.objects.create(name="dt2")
|
||||
cls.t1 = Tag.objects.create(name="t1")
|
||||
cls.t2 = Tag.objects.create(name="t2")
|
||||
cls.doc1 = Document.objects.create(checksum="A", title="A")
|
||||
cls.doc2 = Document.objects.create(
|
||||
checksum="B",
|
||||
title="B",
|
||||
correspondent=cls.c1,
|
||||
document_type=cls.dt1,
|
||||
page_count=5,
|
||||
)
|
||||
cls.doc3 = Document.objects.create(
|
||||
checksum="C",
|
||||
title="C",
|
||||
correspondent=cls.c2,
|
||||
document_type=cls.dt2,
|
||||
)
|
||||
cls.doc4 = Document.objects.create(checksum="D", title="D")
|
||||
cls.doc5 = Document.objects.create(checksum="E", title="E")
|
||||
cls.doc2.tags.add(cls.t1)
|
||||
cls.doc3.tags.add(cls.t2)
|
||||
cls.doc4.tags.add(cls.t1, cls.t2)
|
||||
cls.sp1 = StoragePath.objects.create(name="sp1", path="Something/{checksum}")
|
||||
cls.cf1 = CustomField.objects.create(name="cf1", data_type="string")
|
||||
cls.cf2 = CustomField.objects.create(name="cf2", data_type="string")
|
||||
|
||||
class TestBulkEditAPI(DirectoriesMixin, APITestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
user = User.objects.create_superuser(username="temp_admin")
|
||||
self.user = user
|
||||
self.client.force_authenticate(user=user)
|
||||
|
||||
patcher = mock.patch("documents.bulk_edit.bulk_update_documents.delay")
|
||||
self.async_task = patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
self.c1 = Correspondent.objects.create(name="c1")
|
||||
self.c2 = Correspondent.objects.create(name="c2")
|
||||
self.dt1 = DocumentType.objects.create(name="dt1")
|
||||
self.dt2 = DocumentType.objects.create(name="dt2")
|
||||
self.t1 = Tag.objects.create(name="t1")
|
||||
self.t2 = Tag.objects.create(name="t2")
|
||||
self.doc1 = Document.objects.create(checksum="A", title="A")
|
||||
self.doc2 = Document.objects.create(
|
||||
checksum="B",
|
||||
title="B",
|
||||
correspondent=self.c1,
|
||||
document_type=self.dt1,
|
||||
page_count=5,
|
||||
)
|
||||
self.doc3 = Document.objects.create(
|
||||
checksum="C",
|
||||
title="C",
|
||||
correspondent=self.c2,
|
||||
document_type=self.dt2,
|
||||
)
|
||||
self.doc4 = Document.objects.create(checksum="D", title="D")
|
||||
self.doc5 = Document.objects.create(checksum="E", title="E")
|
||||
self.doc2.tags.add(self.t1)
|
||||
self.doc3.tags.add(self.t2)
|
||||
self.doc4.tags.add(self.t1, self.t2)
|
||||
self.sp1 = StoragePath.objects.create(name="sp1", path="Something/{checksum}")
|
||||
self.cf1 = CustomField.objects.create(name="cf1", data_type="string")
|
||||
self.cf2 = CustomField.objects.create(name="cf2", data_type="string")
|
||||
|
||||
def setup_mock(self, m, method_name, return_value="OK") -> None:
|
||||
m.return_value = return_value
|
||||
|
||||
@@ -13,9 +13,10 @@ from rest_framework.test import APITestCase
|
||||
from documents.models import CustomField
|
||||
from documents.models import CustomFieldInstance
|
||||
from documents.models import Document
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestCustomFieldsAPI(APITestCase):
|
||||
class TestCustomFieldsAPI(DirectoriesMixin, APITestCase):
|
||||
ENDPOINT = "/api/custom_fields/"
|
||||
|
||||
def setUp(self) -> None:
|
||||
|
||||
@@ -136,6 +136,8 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
root_document=root,
|
||||
content="v2-content",
|
||||
)
|
||||
root.latest_content = v2.content
|
||||
root.save(update_fields=["latest_content"])
|
||||
|
||||
with (
|
||||
mock.patch("documents.index.remove_document_from_index"),
|
||||
@@ -148,6 +150,7 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(resp.data["current_version_id"], v1.id)
|
||||
root.refresh_from_db()
|
||||
self.assertEqual(root.content, "root-content")
|
||||
self.assertEqual(root.latest_content, "v1-content")
|
||||
|
||||
with (
|
||||
mock.patch("documents.index.remove_document_from_index"),
|
||||
@@ -160,6 +163,7 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(resp.data["current_version_id"], root.id)
|
||||
root.refresh_from_db()
|
||||
self.assertEqual(root.content, "root-content")
|
||||
self.assertIsNone(root.latest_content)
|
||||
|
||||
def test_delete_version_writes_audit_log_entry(self) -> None:
|
||||
root = Document.objects.create(
|
||||
@@ -695,6 +699,7 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
v2.refresh_from_db()
|
||||
self.assertEqual(v2.content, "edited-content")
|
||||
self.assertEqual(root.content, "root-content")
|
||||
self.assertEqual(root.latest_content, "edited-content")
|
||||
self.assertEqual(v1.content, "v1-content")
|
||||
|
||||
def test_patch_content_updates_selected_version_content(self) -> None:
|
||||
@@ -718,6 +723,8 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
root_document=root,
|
||||
content="v2-content",
|
||||
)
|
||||
root.latest_content = v2.content
|
||||
root.save(update_fields=["latest_content"])
|
||||
|
||||
resp = self.client.patch(
|
||||
f"/api/documents/{root.id}/?version={v1.id}",
|
||||
@@ -733,6 +740,28 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
self.assertEqual(v1.content, "edited-v1")
|
||||
self.assertEqual(v2.content, "v2-content")
|
||||
self.assertEqual(root.content, "root-content")
|
||||
self.assertEqual(root.latest_content, "v2-content")
|
||||
|
||||
def test_patch_root_content_without_versions_keeps_latest_content_null(
|
||||
self,
|
||||
) -> None:
|
||||
root = Document.objects.create(
|
||||
title="root",
|
||||
checksum="root",
|
||||
mime_type="application/pdf",
|
||||
content="root-content",
|
||||
)
|
||||
|
||||
resp = self.client.patch(
|
||||
f"/api/documents/{root.id}/",
|
||||
{"content": "edited-root"},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
root.refresh_from_db()
|
||||
self.assertEqual(root.content, "edited-root")
|
||||
self.assertIsNone(root.latest_content)
|
||||
|
||||
def test_retrieve_returns_latest_version_content(self) -> None:
|
||||
root = Document.objects.create(
|
||||
@@ -748,6 +777,8 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
root_document=root,
|
||||
content="v1-content",
|
||||
)
|
||||
root.latest_content = "v1-content"
|
||||
root.save(update_fields=["latest_content"])
|
||||
|
||||
resp = self.client.get(f"/api/documents/{root.id}/")
|
||||
|
||||
@@ -768,6 +799,8 @@ class TestDocumentVersioningApi(DirectoriesMixin, APITestCase):
|
||||
root_document=root,
|
||||
content="v1-content",
|
||||
)
|
||||
root.latest_content = v1.content
|
||||
root.save(update_fields=["latest_content"])
|
||||
|
||||
resp = self.client.get(f"/api/documents/{root.id}/?version={v1.id}")
|
||||
|
||||
|
||||
@@ -51,13 +51,10 @@ from documents.tests.utils import DocumentConsumeDelayMixin
|
||||
|
||||
|
||||
class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
super().setUpTestData()
|
||||
cls.user = User.objects.create_superuser(username="temp_admin")
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.user = User.objects.create_superuser(username="temp_admin")
|
||||
self.client.force_authenticate(user=self.user)
|
||||
cache.clear()
|
||||
|
||||
@@ -1359,6 +1356,8 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
|
||||
root_document=root,
|
||||
content="latest-version-content",
|
||||
)
|
||||
root.latest_content = version.content
|
||||
root.save(update_fields=["latest_content"])
|
||||
|
||||
response = self.client.get(
|
||||
"/api/documents/?content__icontains=latest-version-content",
|
||||
|
||||
@@ -16,9 +16,10 @@ from documents.models import Document
|
||||
from documents.models import DocumentType
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestApiObjects(APITestCase):
|
||||
class TestApiObjects(DirectoriesMixin, APITestCase):
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
@@ -161,7 +162,7 @@ class TestApiObjects(APITestCase):
|
||||
)
|
||||
|
||||
|
||||
class TestApiStoragePaths(APITestCase):
|
||||
class TestApiStoragePaths(DirectoriesMixin, APITestCase):
|
||||
ENDPOINT = "/api/storage_paths/"
|
||||
|
||||
def setUp(self) -> None:
|
||||
|
||||
@@ -19,9 +19,10 @@ from documents.models import DocumentType
|
||||
from documents.models import MatchingModel
|
||||
from documents.models import StoragePath
|
||||
from documents.models import Tag
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestApiAuth(APITestCase):
|
||||
class TestApiAuth(DirectoriesMixin, APITestCase):
|
||||
def test_auth_required(self) -> None:
|
||||
d = Document.objects.create(title="Test")
|
||||
|
||||
@@ -653,16 +654,13 @@ class TestApiAuth(APITestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
|
||||
class TestApiUser(APITestCase):
|
||||
class TestApiUser(DirectoriesMixin, APITestCase):
|
||||
ENDPOINT = "/api/users/"
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
super().setUpTestData()
|
||||
cls.user = User.objects.create_superuser(username="temp_admin")
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.user = User.objects.create_superuser(username="temp_admin")
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
def test_get_users(self) -> None:
|
||||
@@ -995,16 +993,13 @@ class TestApiUser(APITestCase):
|
||||
self.assertEqual(returned_user1.is_staff, True)
|
||||
|
||||
|
||||
class TestApiGroup(APITestCase):
|
||||
class TestApiGroup(DirectoriesMixin, APITestCase):
|
||||
ENDPOINT = "/api/groups/"
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
super().setUpTestData()
|
||||
cls.user = User.objects.create_superuser(username="temp_admin")
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.user = User.objects.create_superuser(username="temp_admin")
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
def test_get_groups(self) -> None:
|
||||
@@ -1102,24 +1097,21 @@ class TestApiGroup(APITestCase):
|
||||
|
||||
|
||||
class TestBulkEditObjectPermissions(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
super().setUpTestData()
|
||||
|
||||
cls.temp_admin = User.objects.create_superuser(username="temp_admin")
|
||||
cls.t1 = Tag.objects.create(name="t1")
|
||||
cls.t2 = Tag.objects.create(name="t2")
|
||||
cls.c1 = Correspondent.objects.create(name="c1")
|
||||
cls.dt1 = DocumentType.objects.create(name="dt1")
|
||||
cls.sp1 = StoragePath.objects.create(name="sp1")
|
||||
cls.user1 = User.objects.create(username="user1")
|
||||
cls.user2 = User.objects.create(username="user2")
|
||||
cls.user3 = User.objects.create(username="user3")
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.temp_admin = User.objects.create_superuser(username="temp_admin")
|
||||
self.client.force_authenticate(user=self.temp_admin)
|
||||
|
||||
self.t1 = Tag.objects.create(name="t1")
|
||||
self.t2 = Tag.objects.create(name="t2")
|
||||
self.c1 = Correspondent.objects.create(name="c1")
|
||||
self.dt1 = DocumentType.objects.create(name="dt1")
|
||||
self.sp1 = StoragePath.objects.create(name="sp1")
|
||||
self.user1 = User.objects.create(username="user1")
|
||||
self.user2 = User.objects.create(username="user2")
|
||||
self.user3 = User.objects.create(username="user3")
|
||||
|
||||
def test_bulk_object_set_permissions(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
@@ -1414,14 +1406,11 @@ class TestBulkEditObjectPermissions(APITestCase):
|
||||
|
||||
|
||||
class TestFullPermissionsFlag(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
super().setUpTestData()
|
||||
cls.admin = User.objects.create_superuser(username="admin")
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
|
||||
self.admin = User.objects.create_superuser(username="admin")
|
||||
|
||||
def test_full_perms_flag(self) -> None:
|
||||
"""
|
||||
GIVEN:
|
||||
|
||||
@@ -8,6 +8,8 @@ from rest_framework import status
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
# see allauth.socialaccount.providers.openid.provider.OpenIDProvider
|
||||
class MockOpenIDProvider:
|
||||
@@ -47,7 +49,7 @@ class MockOpenIDConnectProvider:
|
||||
return f"{self.app.provider_id}/login/?process=connect"
|
||||
|
||||
|
||||
class TestApiProfile(APITestCase):
|
||||
class TestApiProfile(DirectoriesMixin, APITestCase):
|
||||
ENDPOINT = "/api/profile/"
|
||||
|
||||
def setUp(self) -> None:
|
||||
|
||||
@@ -6,10 +6,11 @@ from django.test import override_settings
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
from paperless.version import __full_version_str__
|
||||
|
||||
|
||||
class TestApiUiSettings(APITestCase):
|
||||
class TestApiUiSettings(DirectoriesMixin, APITestCase):
|
||||
ENDPOINT = "/api/ui_settings/"
|
||||
|
||||
def setUp(self) -> None:
|
||||
|
||||
@@ -14,9 +14,10 @@ from documents.models import Tag
|
||||
from documents.models import Workflow
|
||||
from documents.models import WorkflowAction
|
||||
from documents.models import WorkflowTrigger
|
||||
from documents.tests.utils import DirectoriesMixin
|
||||
|
||||
|
||||
class TestApiWorkflows(APITestCase):
|
||||
class TestApiWorkflows(DirectoriesMixin, APITestCase):
|
||||
ENDPOINT = "/api/workflows/"
|
||||
ENDPOINT_TRIGGERS = "/api/workflow_triggers/"
|
||||
ENDPOINT_ACTIONS = "/api/workflow_actions/"
|
||||
|
||||
@@ -785,11 +785,13 @@ class TestConsumer(
|
||||
version = versions.first()
|
||||
assert version is not None
|
||||
assert version.original_filename is not None
|
||||
root_doc.refresh_from_db()
|
||||
self.assertEqual(version.version_index, 1)
|
||||
self.assertEqual(version.version_label, "v2")
|
||||
self.assertIsNone(version.archive_serial_number)
|
||||
self.assertEqual(version.original_filename, version_file.name)
|
||||
self.assertTrue(bool(version.content))
|
||||
self.assertEqual(root_doc.latest_content, version.content)
|
||||
|
||||
@override_settings(AUDIT_LOG_ENABLED=True)
|
||||
@mock.patch("documents.consumer.load_classifier")
|
||||
|
||||
@@ -172,6 +172,8 @@ class TestDocument(TestCase):
|
||||
root_document=root,
|
||||
content="latest version content",
|
||||
)
|
||||
root.latest_content = version.content
|
||||
root.save(update_fields=["latest_content"])
|
||||
|
||||
self.assertEqual(root.suggestion_content, version.content)
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ class TestMatching(_TestMatchingBase):
|
||||
root_document=root,
|
||||
content="latest version contains keyword",
|
||||
)
|
||||
root.latest_content = "latest version contains keyword"
|
||||
root.save(update_fields=["latest_content"])
|
||||
tag = Tag.objects.create(
|
||||
name="tag",
|
||||
match="keyword",
|
||||
@@ -86,6 +88,8 @@ class TestMatching(_TestMatchingBase):
|
||||
root_document=root,
|
||||
content="latest version without token",
|
||||
)
|
||||
root.latest_content = "latest version without token"
|
||||
root.save(update_fields=["latest_content"])
|
||||
tag = Tag.objects.create(
|
||||
name="tag",
|
||||
match="keyword",
|
||||
|
||||
@@ -76,27 +76,24 @@ class TestWorkflows(
|
||||
SampleDirMixin,
|
||||
APITestCase,
|
||||
):
|
||||
@classmethod
|
||||
def setUpTestData(cls) -> None:
|
||||
super().setUpTestData()
|
||||
|
||||
cls.c = Correspondent.objects.create(name="Correspondent Name")
|
||||
cls.c2 = Correspondent.objects.create(name="Correspondent Name 2")
|
||||
cls.dt = DocumentType.objects.create(name="DocType Name")
|
||||
cls.t1 = Tag.objects.create(name="t1")
|
||||
cls.t2 = Tag.objects.create(name="t2")
|
||||
cls.t3 = Tag.objects.create(name="t3")
|
||||
cls.sp = StoragePath.objects.create(path="/test/")
|
||||
cls.cf1 = CustomField.objects.create(name="Custom Field 1", data_type="string")
|
||||
cls.cf2 = CustomField.objects.create(
|
||||
def setUp(self) -> None:
|
||||
self.c = Correspondent.objects.create(name="Correspondent Name")
|
||||
self.c2 = Correspondent.objects.create(name="Correspondent Name 2")
|
||||
self.dt = DocumentType.objects.create(name="DocType Name")
|
||||
self.t1 = Tag.objects.create(name="t1")
|
||||
self.t2 = Tag.objects.create(name="t2")
|
||||
self.t3 = Tag.objects.create(name="t3")
|
||||
self.sp = StoragePath.objects.create(path="/test/")
|
||||
self.cf1 = CustomField.objects.create(name="Custom Field 1", data_type="string")
|
||||
self.cf2 = CustomField.objects.create(
|
||||
name="Custom Field 2",
|
||||
data_type="integer",
|
||||
)
|
||||
|
||||
cls.user2 = User.objects.create(username="user2")
|
||||
cls.user3 = User.objects.create(username="user3")
|
||||
cls.group1 = Group.objects.create(name="group1")
|
||||
cls.group2 = Group.objects.create(name="group2")
|
||||
self.user2 = User.objects.create(username="user2")
|
||||
self.user3 = User.objects.create(username="user3")
|
||||
self.group1 = Group.objects.create(name="group1")
|
||||
self.group2 = Group.objects.create(name="group2")
|
||||
|
||||
account1 = MailAccount.objects.create(
|
||||
name="Email1",
|
||||
@@ -107,7 +104,7 @@ class TestWorkflows(
|
||||
imap_security=MailAccount.ImapSecurity.SSL,
|
||||
character_set="UTF-8",
|
||||
)
|
||||
cls.rule1 = MailRule.objects.create(
|
||||
self.rule1 = MailRule.objects.create(
|
||||
name="Rule1",
|
||||
account=account1,
|
||||
folder="INBOX",
|
||||
@@ -125,8 +122,7 @@ class TestWorkflows(
|
||||
assign_owner_from_rule=False,
|
||||
)
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
return super().setUp()
|
||||
|
||||
def test_workflow_match(self) -> None:
|
||||
"""
|
||||
|
||||
@@ -55,6 +55,23 @@ def get_latest_version_for_root(
|
||||
return latest or root_doc
|
||||
|
||||
|
||||
def sync_root_latest_content(
|
||||
root_doc: Document,
|
||||
*,
|
||||
include_deleted: bool = False,
|
||||
) -> None:
|
||||
manager = _document_manager(include_deleted=include_deleted)
|
||||
latest_version_content = (
|
||||
manager.filter(root_document_id=root_doc.pk)
|
||||
.order_by("-id")
|
||||
.values_list("content", flat=True)
|
||||
.first()
|
||||
)
|
||||
Document.objects.filter(pk=root_doc.pk).update(
|
||||
latest_content=latest_version_content,
|
||||
)
|
||||
|
||||
|
||||
def resolve_requested_version_for_root(
|
||||
root_doc: Document,
|
||||
request: Any,
|
||||
|
||||
@@ -35,10 +35,8 @@ from django.db.models import F
|
||||
from django.db.models import IntegerField
|
||||
from django.db.models import Max
|
||||
from django.db.models import Model
|
||||
from django.db.models import OuterRef
|
||||
from django.db.models import Prefetch
|
||||
from django.db.models import Q
|
||||
from django.db.models import Subquery
|
||||
from django.db.models import Sum
|
||||
from django.db.models import When
|
||||
from django.db.models.functions import Coalesce
|
||||
@@ -220,6 +218,7 @@ from documents.versioning import get_latest_version_for_root
|
||||
from documents.versioning import get_request_version_param
|
||||
from documents.versioning import get_root_document
|
||||
from documents.versioning import resolve_requested_version_for_root
|
||||
from documents.versioning import sync_root_latest_content
|
||||
from paperless import version
|
||||
from paperless.celery import app as celery_app
|
||||
from paperless.config import AIConfig
|
||||
@@ -894,16 +893,11 @@ class DocumentViewSet(
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
latest_version_content = Subquery(
|
||||
Document.objects.filter(root_document=OuterRef("pk"))
|
||||
.order_by("-id")
|
||||
.values("content")[:1],
|
||||
)
|
||||
return (
|
||||
Document.objects.filter(root_document__isnull=True)
|
||||
.distinct()
|
||||
.order_by("-created")
|
||||
.annotate(effective_content=Coalesce(latest_version_content, F("content")))
|
||||
.annotate(effective_content=Coalesce(F("latest_content"), F("content")))
|
||||
.annotate(num_notes=Count("notes"))
|
||||
.select_related("correspondent", "storage_path", "document_type", "owner")
|
||||
.prefetch_related(
|
||||
@@ -1022,6 +1016,7 @@ class DocumentViewSet(
|
||||
str(updated_content) if updated_content is not None else ""
|
||||
)
|
||||
content_doc.save(update_fields=["content", "modified"])
|
||||
sync_root_latest_content(root_doc)
|
||||
|
||||
refreshed_doc = self.get_queryset().get(pk=root_doc.pk)
|
||||
response_data = self.get_serializer(refreshed_doc).data
|
||||
@@ -1825,6 +1820,7 @@ class DocumentViewSet(
|
||||
index.remove_document_from_index(version_doc)
|
||||
version_doc_id = version_doc.id
|
||||
version_doc.delete()
|
||||
sync_root_latest_content(root_doc)
|
||||
index.add_or_update_document(root_doc)
|
||||
if settings.AUDIT_LOG_ENABLED:
|
||||
actor = (
|
||||
|
||||
Reference in New Issue
Block a user