diff --git a/src/documents/filters.py b/src/documents/filters.py index a1c346e87..b6c1d76b1 100644 --- a/src/documents/filters.py +++ b/src/documents/filters.py @@ -50,7 +50,7 @@ from documents.models import ShareLinkBundle from documents.models import StoragePath from documents.models import Tag from documents.permissions import permitted_object_ids -from documents.versioning import ensure_effective_content +from documents.versioning import annotate_effective_content if TYPE_CHECKING: from collections.abc import Callable @@ -180,7 +180,7 @@ class TitleContentFilter(Filter): logger.warning( "Deprecated document filter parameter 'title_content' used; use `text` instead.", ) - return ensure_effective_content(qs).filter( + return annotate_effective_content(qs).filter( Q(title__icontains=value) | Q(effective_content__icontains=value), ) else: @@ -193,7 +193,7 @@ class EffectiveContentFilter(Filter): value = value.strip() if isinstance(value, str) else value if not value: return qs - return ensure_effective_content(qs).filter( + return annotate_effective_content(qs).filter( **{f"effective_content__{self.lookup_expr}": value}, ) diff --git a/src/documents/tests/test_api_document_versions.py b/src/documents/tests/test_api_document_versions.py index 5bda7d758..33c7e6757 100644 --- a/src/documents/tests/test_api_document_versions.py +++ b/src/documents/tests/test_api_document_versions.py @@ -958,6 +958,7 @@ class TestVersionAwareFilters(DjangoTestCase): annotated = annotate_effective_content( Document.objects.filter(root_document__isnull=True), ) + self.assertIs(annotate_effective_content(annotated), annotated) result = EffectiveContentFilter(lookup_expr="icontains").filter( annotated, diff --git a/src/documents/versioning.py b/src/documents/versioning.py index 853bfed98..758391a0b 100644 --- a/src/documents/versioning.py +++ b/src/documents/versioning.py @@ -27,10 +27,13 @@ def versions_newest_first(documents: QuerySet[Document]) -> QuerySet[Document]: def annotate_effective_content(documents: QuerySet[Document]) -> QuerySet[Document]: """ - Annotates documents with the content of their newest version, falling back - to their own, so get_effective_content() can answer from the row rather - than querying for the versions of each document + Annotates documents with the content of their newest version unless the + queryset already carries the annotation, falling back to their own, so + get_effective_content() can answer from the row rather than querying for + the versions of each document. """ + if "effective_content" in documents.query.annotations: + return documents return documents.annotate( effective_content=Coalesce( Subquery( @@ -43,21 +46,6 @@ def annotate_effective_content(documents: QuerySet[Document]) -> QuerySet[Docume ) -def ensure_effective_content(documents: QuerySet[Document]) -> QuerySet[Document]: - """ - Annotates effective_content unless the queryset already carries it. - - Lets a filter depend on effective_content without having to assume its - caller annotated one -- annotating twice under the same alias is an error, - and silently matching on the root document's own content instead is worse, - because the same filter then selects different documents depending on which - queryset it was handed. - """ - if "effective_content" in documents.query.annotations: - return documents - return annotate_effective_content(documents) - - def sort_versions_newest_first(documents: list[Document]) -> list[Document]: """ Same sorting as versions_newest_first()