Chore: add generic type params to view classes

Add model type parameters to ModelViewSet, GenericViewSet,
ReadOnlyModelViewSet, and Any to GenericAPIView subclasses in
documents/views.py, paperless/views.py, and paperless_mail/views.py.

Also reorder mixin/ModelViewSet base class order to mixin-first
convention in ShareLink* and MailAccount/MailRule viewsets.

Removes 26 [type-arg] errors from the mypy baseline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Trenton H
2026-04-11 13:41:01 -07:00
parent 054e56941d
commit 999a61bc9e
4 changed files with 41 additions and 60 deletions
-26
View File
@@ -1805,28 +1805,6 @@ src/documents/views.py:0: error: Item "None" of "Any | None" has no attribute "g
src/documents/views.py:0: error: Item "None" of "ApplicationConfiguration | None" has no attribute "app_logo" [union-attr]
src/documents/views.py:0: error: Item "None" of "dict[str, _PingReply] | None" has no attribute "keys" [union-attr]
src/documents/views.py:0: error: Missing positional argument "request" in call to "email_documents" [call-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "GenericViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "Serializer" [type-arg]
src/documents/views.py:0: error: Missing type arguments for generic type "list" [type-arg]
src/documents/views.py:0: error: Need type annotation for "authentication_classes" (hint: "authentication_classes: list[<type>] = ...") [var-annotated]
@@ -2629,7 +2607,3 @@ src/paperless_mail/views.py:0: error: Function is missing a type annotation [no
src/paperless_mail/views.py:0: error: Function is missing a type annotation [no-untyped-def]
src/paperless_mail/views.py:0: error: Function is missing a type annotation [no-untyped-def]
src/paperless_mail/views.py:0: error: Incompatible types in assignment (expression has type "tuple[type[IsAuthenticated]]", variable has type "tuple[type[IsAuthenticated], type[PaperlessObjectPermissions]]") [assignment]
src/paperless_mail/views.py:0: error: Missing type arguments for generic type "GenericAPIView" [type-arg]
src/paperless_mail/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/paperless_mail/views.py:0: error: Missing type arguments for generic type "ModelViewSet" [type-arg]
src/paperless_mail/views.py:0: error: Missing type arguments for generic type "ReadOnlyModelViewSet" [type-arg]
+8 -8
View File
@@ -11048,8 +11048,8 @@
"path": "src/documents/views.py",
"code": -2,
"name": "bad-argument-type",
"description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Unknown, @_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`",
"concise_description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Unknown, @_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`",
"description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Any, @_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`",
"concise_description": "Argument `TantivyRelevanceList` is not assignable to parameter `queryset` with type `QuerySet[Any, @_]` in function `rest_framework.generics.GenericAPIView.paginate_queryset`",
"severity": "error"
},
{
@@ -16009,9 +16009,9 @@
"severity": "error"
},
{
"line": 267,
"line": 268,
"column": 28,
"stop_line": 267,
"stop_line": 268,
"stop_column": 34,
"path": "src/paperless_mail/views.py",
"code": -2,
@@ -16021,9 +16021,9 @@
"severity": "error"
},
{
"line": 273,
"line": 274,
"column": 29,
"stop_line": 273,
"stop_line": 274,
"stop_column": 40,
"path": "src/paperless_mail/views.py",
"code": -2,
@@ -16033,9 +16033,9 @@
"severity": "error"
},
{
"line": 277,
"line": 278,
"column": 26,
"stop_line": 277,
"stop_line": 278,
"stop_column": 34,
"path": "src/paperless_mail/views.py",
"code": -2,
+28 -22
View File
@@ -291,7 +291,7 @@ class IndexView(TemplateView):
return context
class PassUserMixin(GenericAPIView):
class PassUserMixin(GenericAPIView[Any]):
"""
Pass a user object to serializer
"""
@@ -457,7 +457,10 @@ class PermissionsAwareDocumentCountMixin(BulkPermissionMixin, PassUserMixin):
@extend_schema_view(**generate_object_with_permissions_schema(CorrespondentSerializer))
class CorrespondentViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
class CorrespondentViewSet(
PermissionsAwareDocumentCountMixin,
ModelViewSet[Correspondent],
):
model = Correspondent
queryset = Correspondent.objects.select_related("owner").order_by(Lower("name"))
@@ -494,7 +497,7 @@ class CorrespondentViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
@extend_schema_view(**generate_object_with_permissions_schema(TagSerializer))
class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[Tag]):
model = Tag
serializer_class = TagSerializer
document_count_through = Document.tags.through
@@ -573,7 +576,10 @@ class TagViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
@extend_schema_view(**generate_object_with_permissions_schema(DocumentTypeSerializer))
class DocumentTypeViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
class DocumentTypeViewSet(
PermissionsAwareDocumentCountMixin,
ModelViewSet[DocumentType],
):
model = DocumentType
queryset = DocumentType.objects.select_related("owner").order_by(Lower("name"))
@@ -808,7 +814,7 @@ class DocumentViewSet(
UpdateModelMixin,
DestroyModelMixin,
ListModelMixin,
GenericViewSet,
GenericViewSet[Document],
):
model = Document
queryset = Document.objects.all()
@@ -1952,7 +1958,7 @@ class ChatStreamingSerializer(serializers.Serializer):
],
name="dispatch",
)
class ChatStreamingView(GenericAPIView):
class ChatStreamingView(GenericAPIView[Any]):
permission_classes = (IsAuthenticated,)
serializer_class = ChatStreamingSerializer
@@ -2278,7 +2284,7 @@ class LogViewSet(ViewSet):
@extend_schema_view(**generate_object_with_permissions_schema(SavedViewSerializer))
class SavedViewViewSet(BulkPermissionMixin, PassUserMixin, ModelViewSet):
class SavedViewViewSet(BulkPermissionMixin, PassUserMixin, ModelViewSet[SavedView]):
model = SavedView
queryset = SavedView.objects.select_related("owner").prefetch_related(
@@ -2756,7 +2762,7 @@ class RemovePasswordDocumentsView(DocumentOperationPermissionMixin):
},
),
)
class PostDocumentView(GenericAPIView):
class PostDocumentView(GenericAPIView[Any]):
permission_classes = (IsAuthenticated,)
serializer_class = PostDocumentSerializer
parser_classes = (parsers.MultiPartParser,)
@@ -2877,7 +2883,7 @@ class PostDocumentView(GenericAPIView):
},
),
)
class SelectionDataView(GenericAPIView):
class SelectionDataView(GenericAPIView[Any]):
permission_classes = (IsAuthenticated,)
serializer_class = DocumentListSerializer
parser_classes = (parsers.MultiPartParser, parsers.JSONParser)
@@ -2981,7 +2987,7 @@ class SelectionDataView(GenericAPIView):
},
),
)
class SearchAutoCompleteView(GenericAPIView):
class SearchAutoCompleteView(GenericAPIView[Any]):
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
@@ -3262,7 +3268,7 @@ class GlobalSearchView(PassUserMixin):
},
),
)
class StatisticsView(GenericAPIView):
class StatisticsView(GenericAPIView[Any]):
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):
@@ -3364,7 +3370,7 @@ class StatisticsView(GenericAPIView):
)
class BulkDownloadView(DocumentSelectionMixin, GenericAPIView):
class BulkDownloadView(DocumentSelectionMixin, GenericAPIView[Any]):
permission_classes = (IsAuthenticated,)
serializer_class = BulkDownloadSerializer
parser_classes = (parsers.JSONParser,)
@@ -3417,7 +3423,7 @@ class BulkDownloadView(DocumentSelectionMixin, GenericAPIView):
@extend_schema_view(**generate_object_with_permissions_schema(StoragePathSerializer))
class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[StoragePath]):
model = StoragePath
queryset = StoragePath.objects.select_related("owner").order_by(
@@ -3481,7 +3487,7 @@ class StoragePathViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
return Response(result)
class UiSettingsView(GenericAPIView):
class UiSettingsView(GenericAPIView[Any]):
queryset = UiSettings.objects.all()
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = UiSettingsViewSerializer
@@ -3579,7 +3585,7 @@ class UiSettingsView(GenericAPIView):
},
),
)
class RemoteVersionView(GenericAPIView):
class RemoteVersionView(GenericAPIView[Any]):
cache_key = "remote_version_view_latest_release"
def get(self, request, format=None):
@@ -3656,7 +3662,7 @@ class RemoteVersionView(GenericAPIView):
),
],
)
class TasksViewSet(ReadOnlyModelViewSet):
class TasksViewSet(ReadOnlyModelViewSet[PaperlessTask]):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = TasksViewSerializer
filter_backends = (
@@ -3730,7 +3736,7 @@ class TasksViewSet(ReadOnlyModelViewSet):
)
class ShareLinkViewSet(ModelViewSet, PassUserMixin):
class ShareLinkViewSet(PassUserMixin, ModelViewSet[ShareLink]):
model = ShareLink
queryset = ShareLink.objects.all()
@@ -3747,7 +3753,7 @@ class ShareLinkViewSet(ModelViewSet, PassUserMixin):
ordering_fields = ("created", "expiration", "document")
class ShareLinkBundleViewSet(ModelViewSet, PassUserMixin):
class ShareLinkBundleViewSet(PassUserMixin, ModelViewSet[ShareLinkBundle]):
model = ShareLinkBundle
queryset = ShareLinkBundle.objects.all()
@@ -4104,7 +4110,7 @@ class BulkEditObjectsView(PassUserMixin):
return Response({"result": "OK"})
class WorkflowTriggerViewSet(ModelViewSet):
class WorkflowTriggerViewSet(ModelViewSet[WorkflowTrigger]):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = WorkflowTriggerSerializer
@@ -4122,7 +4128,7 @@ class WorkflowTriggerViewSet(ModelViewSet):
return super().partial_update(request, *args, **kwargs)
class WorkflowActionViewSet(ModelViewSet):
class WorkflowActionViewSet(ModelViewSet[WorkflowAction]):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = WorkflowActionSerializer
@@ -4147,7 +4153,7 @@ class WorkflowActionViewSet(ModelViewSet):
return super().partial_update(request, *args, **kwargs)
class WorkflowViewSet(ModelViewSet):
class WorkflowViewSet(ModelViewSet[Workflow]):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = WorkflowSerializer
@@ -4165,7 +4171,7 @@ class WorkflowViewSet(ModelViewSet):
)
class CustomFieldViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet):
class CustomFieldViewSet(PermissionsAwareDocumentCountMixin, ModelViewSet[CustomField]):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = CustomFieldSerializer
+5 -4
View File
@@ -1,6 +1,7 @@
import datetime
import logging
from datetime import timedelta
from typing import Any
from django.http import HttpResponseBadRequest
from django.http import HttpResponseForbidden
@@ -65,7 +66,7 @@ from paperless_mail.tasks import process_mail_accounts
},
),
)
class MailAccountViewSet(ModelViewSet, PassUserMixin):
class MailAccountViewSet(PassUserMixin, ModelViewSet[MailAccount]):
model = MailAccount
queryset = MailAccount.objects.all().order_by("pk")
@@ -159,7 +160,7 @@ class MailAccountViewSet(ModelViewSet, PassUserMixin):
return Response({"result": "OK"})
class ProcessedMailViewSet(ReadOnlyModelViewSet, PassUserMixin):
class ProcessedMailViewSet(PassUserMixin, ReadOnlyModelViewSet[ProcessedMail]):
permission_classes = (IsAuthenticated, PaperlessObjectPermissions)
serializer_class = ProcessedMailSerializer
pagination_class = StandardPagination
@@ -187,7 +188,7 @@ class ProcessedMailViewSet(ReadOnlyModelViewSet, PassUserMixin):
return Response({"result": "OK", "deleted_mail_ids": mail_ids})
class MailRuleViewSet(ModelViewSet, PassUserMixin):
class MailRuleViewSet(PassUserMixin, ModelViewSet[MailRule]):
model = MailRule
queryset = MailRule.objects.all().order_by("order")
@@ -203,7 +204,7 @@ class MailRuleViewSet(ModelViewSet, PassUserMixin):
responses={200: None},
),
)
class OauthCallbackView(GenericAPIView):
class OauthCallbackView(GenericAPIView[Any]):
permission_classes = (IsAuthenticated,)
def get(self, request, format=None):