From 702f7ea57a79b319144d8aa777d546f52b85be82 Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Mon, 13 Apr 2026 15:36:47 -0700 Subject: [PATCH] feat: update DocumentVersionInfoSerializer and get_versions for DocumentVersion - Replace old Document-row-based version info with DocumentVersion-based fields - Add version_number field; derive is_root from version_number == 1 - Rewrite get_versions() to query DocumentVersion model instead of Document - Remove root_document field from DocumentSerializer and Meta.fields - Remove root_document__isnull filter from _get_viewable_duplicates - Simplify to_representation: remove effective_content block, fix truncation Co-Authored-By: Claude Sonnet 4.6 --- src/documents/serialisers.py | 75 +++++++++++++++++------------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/src/documents/serialisers.py b/src/documents/serialisers.py index 9a026ba54..b39fac98c 100644 --- a/src/documents/serialisers.py +++ b/src/documents/serialisers.py @@ -92,8 +92,6 @@ if TYPE_CHECKING: from collections.abc import Iterable from django.db.models.query import QuerySet - from rest_framework.relations import ManyRelatedField - from rest_framework.relations import RelatedField logger = logging.getLogger("paperless.serializers") @@ -948,7 +946,6 @@ def _get_viewable_duplicates( duplicates = Document.global_objects.filter( Q(checksum__in=checksums) | Q(archive_checksum__in=checksums), ).exclude(pk=document.pk) - duplicates = duplicates.filter(root_document__isnull=True) duplicates = duplicates.order_by("-created") allowed = get_objects_for_user_owner_aware( user, @@ -966,11 +963,16 @@ class DuplicateDocumentSummarySerializer(serializers.Serializer): class DocumentVersionInfoSerializer(serializers.Serializer): - id = serializers.IntegerField() - added = serializers.DateTimeField() - version_label = serializers.CharField(required=False, allow_null=True) - checksum = serializers.CharField(required=False, allow_null=True) - is_root = serializers.BooleanField() + id = serializers.IntegerField(read_only=True) + added = serializers.DateTimeField(read_only=True) + version_label = serializers.CharField( + required=False, + allow_null=True, + read_only=True, + ) + checksum = serializers.CharField(required=False, allow_null=True, read_only=True) + version_number = serializers.IntegerField(read_only=True) + is_root = serializers.BooleanField(read_only=True) class _DocumentVersionInfo(TypedDict): @@ -978,6 +980,7 @@ class _DocumentVersionInfo(TypedDict): added: datetime version_label: str | None checksum: str | None + version_number: int is_root: bool @@ -1001,9 +1004,6 @@ class DocumentSerializer( duplicate_documents = SerializerMethodField() notes = NotesSerializer(many=True, required=False, read_only=True) - root_document: RelatedField[Document, Document, Any] | ManyRelatedField = ( - serializers.PrimaryKeyRelatedField(read_only=True) - ) versions = SerializerMethodField() custom_fields = CustomFieldInstanceSerializer( @@ -1039,41 +1039,41 @@ class DocumentSerializer( return list(duplicates.values("id", "title", "deleted_at")) @extend_schema_field(DocumentVersionInfoSerializer(many=True)) - def get_versions(self, obj): - root_doc = obj if obj.root_document_id is None else obj.root_document - if root_doc is None: - return [] - + def get_versions(self, obj: Document): prefetched_cache = getattr(obj, "_prefetched_objects_cache", None) - prefetched_versions = ( + prefetched = ( prefetched_cache.get("versions") if isinstance(prefetched_cache, dict) else None ) - versions: list[Document] - if prefetched_versions is not None: - versions = [*prefetched_versions, root_doc] + if prefetched is not None: + versions: list = list(prefetched) else: - versions_qs = Document.objects.filter(root_document=root_doc).only( - "id", - "added", - "checksum", - "version_label", - ) - versions = [*versions_qs, root_doc] + from documents.models import DocumentVersion - def build_info(doc: Document) -> _DocumentVersionInfo: + versions = list( + DocumentVersion.objects.filter(document=obj).only( + "id", + "added", + "checksum", + "version_label", + "version_number", + ), + ) + + def build_info(v: DocumentVersion) -> _DocumentVersionInfo: return { - "id": doc.id, - "added": doc.added, - "version_label": doc.version_label, - "checksum": doc.checksum, - "is_root": doc.id == root_doc.id, + "id": v.id, + "added": v.added, + "version_label": v.version_label, + "checksum": v.checksum, + "version_number": v.version_number, + "is_root": v.version_number == 1, } - info = [build_info(doc) for doc in versions] - info.sort(key=lambda item: item["id"], reverse=True) + info = [build_info(v) for v in versions] + info.sort(key=lambda item: item["version_number"], reverse=True) return info def get_original_file_name(self, obj) -> str | None: @@ -1087,10 +1087,8 @@ class DocumentSerializer( def to_representation(self, instance): doc = super().to_representation(instance) - if "content" in self.fields and hasattr(instance, "effective_content"): - doc["content"] = getattr(instance, "effective_content") or "" if self.truncate_content and "content" in self.fields: - doc["content"] = doc.get("content")[0:550] + doc["content"] = (doc.get("content") or "")[0:550] return doc def to_internal_value(self, data): @@ -1250,7 +1248,6 @@ class DocumentSerializer( "remove_inbox_tags", "page_count", "mime_type", - "root_document", "versions", ) list_serializer_class = OwnedObjectListSerializer