mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-09-11 12:18:02 +00:00
Fix: batch document user_can_change checks to avoid per-row N+1 (#13204)
* Fix (beta): batch document user_can_change checks to avoid per-row N+1 DocumentSerializer.get_user_can_change() built a fresh ObjectPermissionChecker and issued a guardian permission-table query for every document row not owned by the requesting user -- correct, but O(N) per page load for any non-superuser viewing documents owned by others. BulkPermissionMixin already batches this exact lookup (2 queries total, regardless of page size) for Correspondent/Tag/DocumentType/CustomField, but was gated behind the rarely-used `full_perms` flag and DocumentViewSet didn't inherit it at all. Changed the gate to "any list action" (cheap: just two extra queries per page) and added BulkPermissionMixin to DocumentViewSet, then updated get_user_can_change to consult that batched context before falling back to a fresh guardian check. Preserves guardian's own superuser shortcut explicitly (has_perm() special- cases is_superuser without a query; the batched-context path doesn't, so it needed its own check) -- covered by a new regression test, since no existing test exercised a superuser viewing an other-owned document. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * Fix (beta): don't batch permissions for tantivy search results UnifiedSearchViewSet.list() returns SearchHit/dict-like objects for text/title/query/more_like_id search requests, not Document ORM instances. Adding BulkPermissionMixin to DocumentViewSet (previous commit) meant its get_serializer_context() ran for search responses too, and its _get_object_perms() -- which expects real model instances with .pk -- crashed on the dict-like hits with AttributeError, turning every search request into a 400. Skip the batching specifically for search requests (existing _is_search_request() check) by calling past BulkPermissionMixin in the MRO; non-search list() calls (which return a real Document queryset) are unaffected and still get the batching. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
97662b6c5c
commit
f86bc57880
+15
-7
@@ -431,14 +431,12 @@ class BulkPermissionMixin:
|
||||
This avoid fetching permissions object by object in database.
|
||||
"""
|
||||
context = super().get_serializer_context()
|
||||
try:
|
||||
full_perms = get_boolean(
|
||||
str(self.request.query_params.get("full_perms", "false")),
|
||||
)
|
||||
except ValueError:
|
||||
full_perms = False
|
||||
|
||||
if not full_perms:
|
||||
if getattr(self, "action", None) != "list":
|
||||
# Batching only pays off across a page of objects; for single-object
|
||||
# actions (retrieve, update, ...) the per-object fallback in
|
||||
# get_user_can_change()/_get_perms() is cheap and avoids scanning
|
||||
# the whole queryset here.
|
||||
return context
|
||||
|
||||
# Check which objects are being paginated
|
||||
@@ -943,6 +941,7 @@ class EmailDocumentDetailSchema(EmailSerializer):
|
||||
),
|
||||
)
|
||||
class DocumentViewSet(
|
||||
BulkPermissionMixin,
|
||||
PassUserMixin,
|
||||
RetrieveModelMixin,
|
||||
UpdateModelMixin,
|
||||
@@ -2291,6 +2290,15 @@ class UnifiedSearchViewSet(DocumentViewSet):
|
||||
return SearchResultSerializer
|
||||
return DocumentSerializer
|
||||
|
||||
def get_serializer_context(self):
|
||||
if self._is_search_request():
|
||||
# BulkPermissionMixin.get_serializer_context() (inherited via
|
||||
# DocumentViewSet) assumes it's batching permissions for a page of
|
||||
# real Document instances. Tantivy search results are SearchHit/
|
||||
# dict-like objects instead, so skip straight past it here.
|
||||
return super(BulkPermissionMixin, self).get_serializer_context()
|
||||
return super().get_serializer_context()
|
||||
|
||||
def _get_active_search_params(self, request: Request | None = None) -> list[str]:
|
||||
request = request or self.request
|
||||
return [
|
||||
|
||||
Reference in New Issue
Block a user