diff --git a/.mypy-baseline.txt b/.mypy-baseline.txt index 3fc953358..a63eed9ac 100644 --- a/.mypy-baseline.txt +++ b/.mypy-baseline.txt @@ -435,7 +435,6 @@ src/documents/permissions.py:0: error: Function is missing a type annotation [n src/documents/permissions.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/permissions.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/permissions.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def] -src/documents/permissions.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def] src/documents/permissions.py:0: error: Item "list[str]" of "Any | list[str] | QuerySet[User, User]" has no attribute "exclude" [union-attr] src/documents/permissions.py:0: error: Item "list[str]" of "Any | list[str] | QuerySet[User, User]" has no attribute "exists" [union-attr] src/documents/permissions.py:0: error: Item "list[str]" of "Any | list[str] | QuerySet[User, User]" has no attribute "exists" [union-attr] @@ -589,7 +588,6 @@ src/documents/serialisers.py:0: error: Function is missing a type annotation for src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def] src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def] src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def] -src/documents/serialisers.py:0: error: Function is missing a type annotation for one or more arguments [no-untyped-def] src/documents/serialisers.py:0: error: Incompatible type for lookup 'pk': (got "str | None", expected "str | int") [misc] src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "PrimaryKeyRelatedField[Tag]", base class "Field" defined the type as "BaseSerializer[Any]") [assignment] src/documents/serialisers.py:0: error: Incompatible types in assignment (expression has type "Sequence[str] | tuple[Lower]", variable has type "Sequence[str] | None") [assignment] @@ -1559,7 +1557,6 @@ src/documents/views.py:0: error: Function is missing a return type annotation [ src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] -src/documents/views.py:0: error: Function is missing a return type annotation [no-untyped-def] src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] src/documents/views.py:0: error: Function is missing a type annotation [no-untyped-def] diff --git a/src/documents/permissions.py b/src/documents/permissions.py index de6fff1fb..140293887 100644 --- a/src/documents/permissions.py +++ b/src/documents/permissions.py @@ -1,3 +1,5 @@ +from typing import Any + from django.contrib.auth.models import Group from django.contrib.auth.models import Permission from django.contrib.auth.models import User @@ -199,12 +201,12 @@ def get_document_count_filter_for_user(user): def annotate_document_count_for_related_queryset( - queryset, - through_model, + queryset: QuerySet[Any], + through_model: Any, related_object_field: str, target_field: str = "document_id", - user=None, -): + user: User | None = None, +) -> QuerySet[Any]: """ Annotate a queryset with permissions-aware document counts using a subquery against a relation table. @@ -234,11 +236,11 @@ def annotate_document_count_for_related_queryset( def get_objects_for_user_owner_aware( - user, - perms, - Model, + user: User | None, + perms: str | list[str], + Model: Any, *, - include_deleted=False, + include_deleted: bool = False, ) -> QuerySet: """ Returns objects the user owns, are unowned, or has explicit perms. @@ -314,7 +316,7 @@ class AcknowledgeTasksPermissions(BasePermission): "POST": ["documents.change_paperlesstask"], } - def has_permission(self, request, view): + def has_permission(self, request: Any, view: Any) -> bool: if not request.user or not request.user.is_authenticated: # pragma: no cover return False diff --git a/src/documents/views.py b/src/documents/views.py index a2bceb02c..73b54e562 100644 --- a/src/documents/views.py +++ b/src/documents/views.py @@ -185,6 +185,7 @@ from documents.serialisers import PostDocumentSerializer from documents.serialisers import RunTaskViewSerializer from documents.serialisers import SavedViewSerializer from documents.serialisers import SearchResultSerializer +from documents.serialisers import SerializerWithPerms from documents.serialisers import ShareLinkBundleSerializer from documents.serialisers import ShareLinkSerializer from documents.serialisers import StoragePathSerializer @@ -287,17 +288,22 @@ class PassUserMixin(GenericAPIView): """ def get_serializer(self, *args, **kwargs): - kwargs.setdefault("user", self.request.user) - try: - full_perms = get_boolean( - str(self.request.query_params.get("full_perms", "false")), + serializer_class = self.get_serializer_class() + if isinstance(serializer_class, type) and issubclass( + serializer_class, + SerializerWithPerms, + ): + kwargs.setdefault("user", self.request.user) + try: + full_perms = get_boolean( + str(self.request.query_params.get("full_perms", "false")), + ) + except ValueError: + full_perms = False + kwargs.setdefault( + "full_perms", + full_perms, ) - except ValueError: - full_perms = False - kwargs.setdefault( - "full_perms", - full_perms, - ) return super().get_serializer(*args, **kwargs) @@ -404,8 +410,17 @@ class PermissionsAwareDocumentCountMixin(BulkPermissionMixin, PassUserMixin): """ # Default is simple relation path, override for through-table/count specialization. - document_count_through = None - document_count_source_field = None + document_count_through: type[Model] | None = None + document_count_source_field: str | None = None + + def _get_document_count_source_field(self) -> str: + if self.document_count_source_field is None: + msg = ( + "document_count_source_field must be set when " + "document_count_through is configured" + ) + raise ValueError(msg) + return self.document_count_source_field def get_document_count_filter(self): request = getattr(self, "request", None) @@ -421,7 +436,7 @@ class PermissionsAwareDocumentCountMixin(BulkPermissionMixin, PassUserMixin): return annotate_document_count_for_related_queryset( base_qs, through_model=self.document_count_through, - related_object_field=self.document_count_source_field, + related_object_field=self._get_document_count_source_field(), user=user, ) @@ -523,7 +538,7 @@ class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet): .select_related("owner") .order_by(*ordering), through_model=self.document_count_through, - related_object_field=self.document_count_source_field, + related_object_field=self._get_document_count_source_field(), user=user, ), ) diff --git a/src/locale/en_US/LC_MESSAGES/django.po b/src/locale/en_US/LC_MESSAGES/django.po index f71a0dff9..907333f0c 100644 --- a/src/locale/en_US/LC_MESSAGES/django.po +++ b/src/locale/en_US/LC_MESSAGES/django.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: paperless-ngx\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-02-16 17:04+0000\n" +"POT-Creation-Date: 2026-02-16 17:32+0000\n" "PO-Revision-Date: 2022-02-17 04:17\n" "Last-Translator: \n" "Language-Team: English\n" @@ -1323,7 +1323,7 @@ msgstr "" msgid "Duplicate document identifiers are not allowed." msgstr "" -#: documents/serialisers.py:2331 documents/views.py:2864 +#: documents/serialisers.py:2331 documents/views.py:2879 #, python-format msgid "Documents not found: %(ids)s" msgstr "" @@ -1587,20 +1587,20 @@ msgstr "" msgid "Unable to parse URI {value}" msgstr "" -#: documents/views.py:2876 +#: documents/views.py:2891 #, python-format msgid "Insufficient permissions to share document %(id)s." msgstr "" -#: documents/views.py:2919 +#: documents/views.py:2934 msgid "Bundle is already being processed." msgstr "" -#: documents/views.py:2976 +#: documents/views.py:2991 msgid "The share link bundle is still being prepared. Please try again later." msgstr "" -#: documents/views.py:2986 +#: documents/views.py:3001 msgid "The share link bundle is unavailable." msgstr ""