mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-08 03:43:20 +00:00
perf: check permitted document IDs via DB-side exclude/exists instead of materializing the full set
email_documents, _has_document_permissions, TrashView.post, and validate_documentlink_targets each resolved permitted_document_ids() into a full Python set just to check membership for a small, bounded batch of request document IDs. For a user with broad permitted access that pulls their entire visible/editable document count into memory and across the wire regardless of how many documents the request actually touches. Pushing the membership check into the DB via exclude(...).exists() scales with the request's batch size instead, without reintroducing the per-row guardian join pathology from #13276 (confirmed via EXPLAIN ANALYZE: the permission subplans are hashed once, not re-executed per outer row). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a047d0e39d
commit
018c448477
@@ -864,8 +864,11 @@ def validate_documentlink_targets(user, doc_ids):
|
||||
if user is None:
|
||||
return
|
||||
|
||||
permitted_change_ids = set(permitted_document_ids(user, perm="change_document"))
|
||||
if not set(doc_ids) <= permitted_change_ids:
|
||||
if (
|
||||
Document.objects.filter(id__in=doc_ids)
|
||||
.exclude(id__in=permitted_document_ids(user, perm="change_document"))
|
||||
.exists()
|
||||
):
|
||||
raise PermissionDenied(
|
||||
_("Insufficient permissions."),
|
||||
)
|
||||
|
||||
+17
-11
@@ -1930,10 +1930,13 @@ class DocumentViewSet(
|
||||
use_archive_version = validated_data.get("use_archive_version", True)
|
||||
|
||||
documents = Document.objects.filter(pk__in=document_ids)
|
||||
if request.user is not None:
|
||||
permitted_ids = set(permitted_document_ids(request.user))
|
||||
if not all(document.pk in permitted_ids for document in documents):
|
||||
return HttpResponseForbidden("Insufficient permissions")
|
||||
if (
|
||||
request.user is not None
|
||||
and documents.exclude(
|
||||
pk__in=permitted_document_ids(request.user),
|
||||
).exists()
|
||||
):
|
||||
return HttpResponseForbidden("Insufficient permissions")
|
||||
|
||||
try:
|
||||
attachments: list[EmailAttachment] = []
|
||||
@@ -2786,9 +2789,13 @@ class DocumentOperationPermissionMixin(PassUserMixin, DocumentSelectionMixin):
|
||||
)
|
||||
|
||||
# check global and object permissions for all documents
|
||||
permitted_change_ids = set(permitted_document_ids(user, perm="change_document"))
|
||||
has_perms = user.has_perm("documents.change_document") and all(
|
||||
doc.pk in permitted_change_ids for doc in document_objs
|
||||
has_perms = (
|
||||
user.has_perm(
|
||||
"documents.change_document",
|
||||
)
|
||||
and not document_objs.exclude(
|
||||
pk__in=permitted_document_ids(user, perm="change_document"),
|
||||
).exists()
|
||||
)
|
||||
|
||||
# check ownership for methods that change original document
|
||||
@@ -5314,14 +5321,13 @@ class TrashView(ListModelMixin, PassUserMixin):
|
||||
if doc_ids is not None
|
||||
else self.filter_queryset(self.get_queryset()).all()
|
||||
)
|
||||
permitted_ids = set(
|
||||
permitted_document_ids(
|
||||
if docs.exclude(
|
||||
pk__in=permitted_document_ids(
|
||||
request.user,
|
||||
perm="delete_document",
|
||||
include_deleted=True,
|
||||
),
|
||||
)
|
||||
if not all(doc.pk in permitted_ids for doc in docs):
|
||||
).exists():
|
||||
return HttpResponseForbidden("Insufficient permissions")
|
||||
action = serializer.validated_data.get("action")
|
||||
if action == "restore":
|
||||
|
||||
Reference in New Issue
Block a user