From e40bced16b1b8e001964b204baddc68fe54be3d5 Mon Sep 17 00:00:00 2001 From: Fazliddin Abdurahimov Date: Thu, 7 Aug 2025 10:47:03 +0500 Subject: [PATCH] fix: Swagger schema fix, comments add --- core/apps/accounts/views/me.py | 17 +-- core/apps/companies/views/accounts.py | 2 +- core/apps/companies/views/companies.py | 2 +- core/apps/companies/views/folders.py | 4 +- core/apps/contracts/urls.py | 59 ++++++--- core/apps/contracts/views/attached_files.py | 15 ++- core/apps/contracts/views/contracts.py | 139 ++++++++++++-------- core/apps/contracts/views/file_contents.py | 15 ++- core/apps/contracts/views/owners.py | 109 +++++---------- core/utils/router.py | 54 ++++++++ 10 files changed, 241 insertions(+), 175 deletions(-) create mode 100644 core/utils/router.py diff --git a/core/apps/accounts/views/me.py b/core/apps/accounts/views/me.py index 74a313a..9d55eea 100644 --- a/core/apps/accounts/views/me.py +++ b/core/apps/accounts/views/me.py @@ -1,28 +1,25 @@ +from django.db import transaction + +from drf_spectacular.utils import extend_schema + from rest_framework.generics import GenericAPIView # type: ignore -from rest_framework.decorators import action # type: ignore from rest_framework import status # type: ignore from rest_framework.request import HttpRequest # type: ignore from rest_framework.response import Response # type: ignore -from rest_framework.permissions import ( # type: ignore - IsAuthenticated -) +from rest_framework.permissions import IsAuthenticated # type: ignore from core.utils.views import BaseApiViewMixin from core.apps.companies.serializers import ( RetrieveCompanySerializer, CreateCompanySerializer, ) -from core.apps.companies.models import ( - CompanyModel, - CompanyAccountModel -) - -from django.db import transaction +from core.apps.companies.models import CompanyModel, CompanyAccountModel ###################################################################### # @api-view | POST, GET - me/companies ###################################################################### +@extend_schema(tags=["Me"]) class MeCompanyApiView(BaseApiViewMixin, GenericAPIView): # type: ignore permission_classes = [IsAuthenticated] diff --git a/core/apps/companies/views/accounts.py b/core/apps/companies/views/accounts.py index 90d6fce..1d36741 100644 --- a/core/apps/companies/views/accounts.py +++ b/core/apps/companies/views/accounts.py @@ -15,7 +15,7 @@ from core.apps.companies.serializers.accounts import ( ################################################################################### # @view-set | ALL - /company-accounts ################################################################################### -@extend_schema(tags=["CompanyAccount"]) +@extend_schema(tags=["Company Accounts"]) class CompanyAccountCrudViewSet(BaseViewSetMixin, ModelViewSet): queryset = CompanyAccountModel.objects.all() serializer_class = ListCompanyAccountSerializer diff --git a/core/apps/companies/views/companies.py b/core/apps/companies/views/companies.py index 8d3a79a..7188d46 100644 --- a/core/apps/companies/views/companies.py +++ b/core/apps/companies/views/companies.py @@ -38,7 +38,7 @@ UserModel = get_user_model() ################################################################################### # @view-set | ALL - /companies ################################################################################### -@extend_schema(tags=["Company"]) +@extend_schema(tags=["Companies"]) class CompanyCrudViewSet(BaseViewSetMixin, ModelViewSet): queryset = CompanyModel.objects.all() serializer_class = ListCompanySerializer diff --git a/core/apps/companies/views/folders.py b/core/apps/companies/views/folders.py index 4c9a86d..db1e647 100644 --- a/core/apps/companies/views/folders.py +++ b/core/apps/companies/views/folders.py @@ -27,7 +27,7 @@ from core.apps.companies.serializers.folders import ( ################################################################################### # @view-set | ALL - /company-folders ################################################################################### -@extend_schema(tags=["CompanyFolder"]) +@extend_schema(tags=["Company Folders"]) class CompanyFolderCrudViewSet(BaseViewSetMixin, ModelViewSet): queryset = CompanyFolderModel.objects.all() permission_classes = [AllowAny] @@ -51,7 +51,7 @@ class CompanyFolderCrudViewSet(BaseViewSetMixin, ModelViewSet): ################################################################################### # @api-view | POST - /company-folders//contracts ################################################################################### -@extend_schema(tags=["CompanyFolder Contracts"]) +@extend_schema(tags=["Company Folder Contracts"]) class ContractFolderApiView(BaseApiViewMixin, GenericAPIView): # type: ignore queryset = CompanyFolderModel.objects.all() permission_classes = [IsFolderOwner] diff --git a/core/apps/contracts/urls.py b/core/apps/contracts/urls.py index 17e8841..d14182e 100644 --- a/core/apps/contracts/urls.py +++ b/core/apps/contracts/urls.py @@ -1,37 +1,62 @@ from django.urls import path, include -from rest_framework.routers import DefaultRouter # type: ignore +from core.utils.router import TypedRouter from . import views -router = DefaultRouter() -router.register(r"contract-attached-files", views.ContractAttachedFileView, "contract-attached-files") # type: ignore -router.register(r"contracts", views.ContractView, "contracts") # type: ignore -router.register(r"contracts", views.ContractRelationsViewSet, "contract-relations") # type: ignore -router.register(r"contract-file-contents", views.ContractFileContentView, "contract-file-contents") # type: ignore -router.register(r"contract-owners", views.ContractOwnerView, "contract-owners") # type: ignore -# router.register(r"contract-owners", views.CompanyFolderCrudViewSet, "contract-owner-files") # type: ignore +router = TypedRouter() -urlpatterns = [ # type: ignore +router.register( + r"contract-attached-files", + views.ContractAttachedFileViewSet, + "contract-attached-files-view-set" +) +router.register( + r"contract-file-contents", + views.ContractFileContentViewSet, + "contract-file-contents-view-set" +) +router.register( + r"contract-owners", + views.ContractOwnerViewSet, + "contract-owners-view-set" +) +router.register( + r"contracts", + views.ContractViewSet, + "contracts" +) + +urlpatterns: list[object] = [ path("", include(router.urls)), # type: ignore path( r"contract-owners//contract", - views.ContractDetailView.as_view(), - name="contract-detail" + views.ContractDetailApiView.as_view(), + name="contract-detail-api-view" ), path( "contract-owners//files/", - views.ContractAttachedFileDeleteView.as_view(), - name="contract-file-delete" + views.ContractOwnerAttachedFileApiView.as_view(), + name="contract-file-api-view" ), path( r"contract-owners//files//upload", - views.UploadFileContentView.as_view(), - name="upload-file-content" + views.UploadFileContentApiView.as_view(), + name="upload-file-content-api-view" ), path( r"folders//contracts", - views.ListFolderContractsView.as_view(), - name="list-folder-contracts" + views.ListFolderContractsApiView.as_view(), + name="list-folder-contracts-api-view" + ), + path( + r"contracts//owners", + views.ContractOwnerApiView.as_view(), + name="contract-owners-api-view" + ), + path( + r"/contract-owners//files/", + views.ContractAttachedFileApiView.as_view(), + name="contract-attached-files-api-view" ) ] diff --git a/core/apps/contracts/views/attached_files.py b/core/apps/contracts/views/attached_files.py index b52c8be..68266da 100644 --- a/core/apps/contracts/views/attached_files.py +++ b/core/apps/contracts/views/attached_files.py @@ -1,7 +1,7 @@ -from django_core.mixins import BaseViewSetMixin +from django_core.mixins import BaseViewSetMixin # type: ignore from drf_spectacular.utils import extend_schema -from rest_framework.permissions import AllowAny, IsAdminUser -from rest_framework.viewsets import ModelViewSet +from rest_framework.permissions import AllowAny, IsAdminUser # type: ignore +from rest_framework.viewsets import ModelViewSet # type: ignore from core.apps.contracts.models import ContractAttachedFileModel from core.apps.contracts.serializers.attached_files import ( @@ -13,8 +13,11 @@ from core.apps.contracts.serializers.attached_files import ( ) -@extend_schema(tags=["ContractAttachedFile"]) -class ContractAttachedFileView(BaseViewSetMixin, ModelViewSet): +################################################################################### +# @view-set | ALL - /contract-attached-files +################################################################################### +@extend_schema(tags=["Contract Attached Files"]) +class ContractAttachedFileViewSet(BaseViewSetMixin, ModelViewSet): queryset = ContractAttachedFileModel.objects.all() serializer_class = ListContractAttachedFileSerializer permission_classes = [AllowAny] @@ -26,7 +29,7 @@ class ContractAttachedFileView(BaseViewSetMixin, ModelViewSet): "update": [IsAdminUser], "destroy": [IsAdminUser], } - action_serializer_class = { + action_serializer_class = { # type: ignore "list": ListContractAttachedFileSerializer, "retrieve": RetrieveContractAttachedFileSerializer, "create": CreateContractAttachedFileSerializer, diff --git a/core/apps/contracts/views/contracts.py b/core/apps/contracts/views/contracts.py index 3b26d0e..4ce0fc7 100644 --- a/core/apps/contracts/views/contracts.py +++ b/core/apps/contracts/views/contracts.py @@ -4,11 +4,11 @@ from drf_spectacular.utils import extend_schema from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated # type: ignore from rest_framework.viewsets import ModelViewSet # type: ignore from rest_framework.views import APIView # type: ignore +from rest_framework.generics import GenericAPIView # type: ignore +from core.utils.views import BaseApiViewMixin from rest_framework.request import HttpRequest # type: ignore from rest_framework.response import Response # type: ignore -from rest_framework.decorators import action # type: ignore -from rest_framework.generics import get_object_or_404 # type: ignore from rest_framework import status # type: ignore from django_core.mixins import BaseViewSetMixin # type: ignore @@ -29,8 +29,11 @@ from core.apps.contracts.serializers import ( ) -@extend_schema(tags=["Contract"]) -class ContractView(BaseViewSetMixin, ModelViewSet): +################################################################################### +# @view-set | ALL - /contracts +################################################################################### +@extend_schema(tags=["Contracts"]) +class ContractViewSet(BaseViewSetMixin, ModelViewSet): queryset = ContractModel.objects.all() serializer_class = ListContractSerializer permission_classes = [AllowAny] @@ -60,83 +63,103 @@ class ContractView(BaseViewSetMixin, ModelViewSet): return super().create(request, *args, **kwargs) # type: ignore -class ContractRelationsViewSet(BaseViewSetMixin, ModelViewSet): +################################################################################### +# @api-view | GET - /contracts/{id}/owners +################################################################################### +@extend_schema(tags=["Contract Owners"]) +class ContractOwnerApiView(BaseApiViewMixin, GenericAPIView): # type: ignore queryset = ContractModel.objects.all() permission_classes = [AllowAny] - action_permission_classes = { - "list_file": [AllowAny], - "list_owner": [AllowAny], + method_permission_classes = { + "get": [AllowAny] } - action_serializer_class = { # type: ignore - "list_file": ListContractAttachedFileSerializer, - "list_owner": RetrieveContractOwnerSerializer, + method_serializer_class = { + "get": RetrieveContractOwnerSerializer, + } + + @extend_schema( + summary="Get List Of Owners", + description="Get list of owners" + ) + def get(self, *args: object, **kwargs: object) -> Response: + contract = self.get_object() + owners = ContractOwnerModel.objects.filter(contract=contract) + ser = self.get_serializer(instance=owners, many=True) + return Response(ser.data, status.HTTP_200_OK) + + +################################################################################### +# @api-view | GET - /contracts/{id}/files +################################################################################### +@extend_schema(tags=["Contract Attached Files"]) +class ContractAttachedFileApiView(BaseApiViewMixin, GenericAPIView): # type: ignore + queryset = ContractModel.objects.all() + permission_classes = [AllowAny] + + method_permission_classes = { + "get": [AllowAny] + } + method_serializer_class = { + "get": ListContractAttachedFileSerializer, } @extend_schema( summary="Get List Of Files", description="Get List Of Files" ) - @action(url_path="files", detail=True, methods=["GET"]) - def list_file( - self, - request: HttpRequest, - pk: uuid.UUID, - *args: object, - **kwargs: object - ) -> Response: - contract = get_object_or_404(ContractModel, pk=pk) + def get(self, *args: object, **kwargs: object) -> Response: + contract = self.get_object() files = ContractAttachedFileModel.objects.filter(contract=contract) - ser = self.get_serializer(instance=files, many=True) # type: ignore + ser = self.get_serializer(instance=files, many=True) return Response(data=ser.data, status=status.HTTP_200_OK) - @extend_schema( - summary="Get List Of Owners", - description="Get list of owners" - ) - @action(url_path="owners", detail=True, methods=["GET"]) - def list_owner( - self, - request: HttpRequest, - pk: uuid.UUID, - *args: object, - **kwargs: object - ) -> Response: - contract = get_object_or_404(ContractModel, pk=pk) - owners = ContractOwnerModel.objects.filter(contract=contract) - ser = self.get_serializer(instance=owners, many=True) # type: ignore - return Response(ser.data, status.HTTP_200_OK) - -class ContractDetailView(APIView): +################################################################################### +# @api-view | GET - /contract-owners/{id}/contract +################################################################################### +@extend_schema(tags=["Contract Owners"]) +class ContractDetailApiView(BaseApiViewMixin, GenericAPIView): # type: ignore + queryset = ContractOwnerModel.objects.all() permission_classes = [AllowAny] + method_permission_classes = { + "get": [AllowAny] + } + method_serializer_class = { + "get": RetrieveContractSerializer + } + @extend_schema( summary="Uploads a file for contract attached files", description="Creates a file for contract attached files.", ) - def get( - self, - request: HttpRequest, - owner_id: uuid.UUID, - *args: object, - **kwargs: object - ) -> Response: - contract = ContractModel.objects.filter(owners__id=owner_id)[0] - ser = RetrieveContractSerializer(instance=contract) + def get(self, request: HttpRequest, pk: uuid.UUID, *args: object, **kwargs: object) -> Response: + contract = ContractModel.objects.filter(owners__id=pk)[0] + ser = self.get_serializer(instance=contract) # type: ignore return Response(ser.data, status=status.HTTP_200_OK) -class ListFolderContractsView(APIView): - permission_classes = [IsAdminUser] +################################################################################### +# @api-view | GET - /folders/{id}/contracts +################################################################################### +@extend_schema(tags=["Contracts"]) +class ListFolderContractsApiView(BaseApiViewMixin, GenericAPIView): # type: ignore + permission_classes = [AllowAny] + queryset = ContractOwnerModel.objects.all() + + method_permission_classes = { + "get": [AllowAny] + } + method_serializer_class = { + "get": ListContractSerializer + } - def get( - self, - request: HttpRequest, - pk: uuid.UUID, - *args: object, - **kwargs: object - ) -> Response: + @extend_schema( + summary="Get Contracts related to folders", + description="Get Contracts related to folders", + ) + def get(self, request: HttpRequest, pk: uuid.UUID, *args: object, **kwargs: object) -> Response: contracts = ContractModel.objects.filter(folders__id=pk) - ser = ListContractSerializer(instance=contracts, many=True) + ser = self.get_serializer(instance=contracts, many=True) # type: ignore return Response(ser.data, status.HTTP_200_OK) diff --git a/core/apps/contracts/views/file_contents.py b/core/apps/contracts/views/file_contents.py index f049f8c..fa7b130 100644 --- a/core/apps/contracts/views/file_contents.py +++ b/core/apps/contracts/views/file_contents.py @@ -1,7 +1,7 @@ -from django_core.mixins import BaseViewSetMixin +from django_core.mixins import BaseViewSetMixin # type: ignore from drf_spectacular.utils import extend_schema -from rest_framework.permissions import AllowAny, IsAdminUser -from rest_framework.viewsets import ModelViewSet +from rest_framework.permissions import AllowAny, IsAdminUser # type: ignore +from rest_framework.viewsets import ModelViewSet # type: ignore from core.apps.contracts.models import ContractFileContentModel from core.apps.contracts.serializers.file_contents import ( @@ -13,8 +13,11 @@ from core.apps.contracts.serializers.file_contents import ( ) -@extend_schema(tags=["ContractFileContent"]) -class ContractFileContentView(BaseViewSetMixin, ModelViewSet): +################################################################################### +# @view-set | ALL - /contract-file-contents +################################################################################### +@extend_schema(tags=["Contract File Contents"]) +class ContractFileContentViewSet(BaseViewSetMixin, ModelViewSet): queryset = ContractFileContentModel.objects.all() serializer_class = ListContractFileContentSerializer permission_classes = [AllowAny] @@ -26,7 +29,7 @@ class ContractFileContentView(BaseViewSetMixin, ModelViewSet): "update": [IsAdminUser], "destroy": [IsAdminUser], } - action_serializer_class = { + action_serializer_class = { # type: ignore "list": ListContractFileContentSerializer, "retrieve": RetrieveContractFileContentSerializer, "create": CreateContractFileContentSerializer, diff --git a/core/apps/contracts/views/owners.py b/core/apps/contracts/views/owners.py index e5767ca..73a1f4e 100644 --- a/core/apps/contracts/views/owners.py +++ b/core/apps/contracts/views/owners.py @@ -3,18 +3,18 @@ from typing import cast from django_core.mixins import BaseViewSetMixin # type: ignore from django.utils.translation import gettext as _ -from drf_spectacular.utils import extend_schema, OpenApiResponse +from drf_spectacular.utils import extend_schema from rest_framework.exceptions import PermissionDenied # type: ignore -from rest_framework.decorators import action # type: ignore from rest_framework.permissions import AllowAny, IsAdminUser # type: ignore from rest_framework.viewsets import ModelViewSet # type: ignore from rest_framework.request import HttpRequest # type: ignore from rest_framework.response import Response # type: ignore from rest_framework import status # type: ignore -from rest_framework.views import APIView # type: ignore +from rest_framework.generics import GenericAPIView # type: ignore from rest_framework.parsers import MultiPartParser, FormParser # type: ignore from rest_framework.generics import get_object_or_404 # type: ignore +from core.utils.views import BaseApiViewMixin from core.apps.contracts.models import ( ContractOwnerModel, ContractAttachedFileModel, @@ -26,14 +26,15 @@ from core.apps.contracts.serializers import ( UpdateContractOwnerSerializer, DestroyContractOwnerSerializer, - RetrieveContractAttachedFileSerializer, - CreateContractAttachedFileSerializer, CreateContractFileContentFromOwnerSerializer, ) -@extend_schema(tags=["ContractOwner"]) -class ContractOwnerView(BaseViewSetMixin, ModelViewSet): +################################################################################### +# @view-set | ALL - /contract-owners +################################################################################### +@extend_schema(tags=["Contract Owners"]) +class ContractOwnerViewSet(BaseViewSetMixin, ModelViewSet): queryset = ContractOwnerModel.objects.all() serializer_class = ListContractOwnerSerializer permission_classes = [AllowAny] @@ -54,63 +55,20 @@ class ContractOwnerView(BaseViewSetMixin, ModelViewSet): } -# class ContractOwnerFileViewSet(BaseViewSetMixin, ModelViewSet): -# queryset = ContractOwnerModel.objects.all() -# serializer_class = RetrieveContractAttachedFileSerializer -# permission_classes = [AllowAny] - -# action_serializer_class = { # type: ignore -# "list_file": RetrieveContractAttachedFileSerializer, -# "create_file": CreateContractAttachedFileSerializer, -# } - -# @extend_schema( -# summary="Contract Files Related to Owner", -# description="Contract Files Related to Owner", -# ) -# @action(url_path="files", methods=["GET"], detail=True) -# def list_file( -# self, -# request: HttpRequest, -# *args: object, -# **kwargs: object, -# ) -> Response: -# owner = cast(ContractOwnerModel, self.get_object()) -# files = ContractAttachedFileModel.objects.filter( -# contract__owners=owner, contents__owner=owner -# ).select_related("contents") -# serializer = self.get_serializer(instance=files, many=True) -# return Response(serializer.data, status.HTTP_200_OK) - -# @extend_schema( -# summary="Create Contract Files Related to Owner", -# description="Create Contract Files Related to Owner" -# ) -# @action(url_path="files", methods=["GET"], detail=True) -# def create_file( -# self, -# request: HttpRequest, -# *args: object, -# **kwargs: object, -# ) -> Response: -# owner = cast( -# ContractOwnerModel, -# self.get_queryset().select_related("contract") -# ) -# if not owner.contract.allow_add_files: -# raise PermissionDenied(_("Attaching new files was restricted for this contract.")) -# ser = self.get_serializer(data=request.data) -# ser.is_valid(raise_exception=True) -# ser.save() # type: ignore -# return Response(ser.data, status.HTTP_201_CREATED) - - -class ContractAttachedFileDeleteView(APIView): +################################################################################### +# @api-view | DELETE - /contract-owners/{owner_id}/files/{file_id} +################################################################################### +@extend_schema(tags=["Contract Files"]) +class ContractOwnerAttachedFileApiView(BaseApiViewMixin, GenericAPIView): # type: ignore permission_classes = [AllowAny] + queryset = ContractOwnerModel.objects.all() + + method_permission_classes = { + "delete": [AllowAny] + } + method_serializer_class = {} @extend_schema( - # request=ContractFileDeleteRequestSerializer, - responses={204: OpenApiResponse(description="File successfully deleted.")}, summary="Delete a file from contract", description="Deletes a contract-attached file if contract allows file deletion.", ) @@ -127,25 +85,30 @@ class ContractAttachedFileDeleteView(APIView): ) if not owner.contract.allow_delete_files: raise PermissionDenied(_("Deleting attached files was restricted for this contract")) + file = get_object_or_404( - ContractAttachedFileModel.objects.all().select_related("contract"), - pk=file_id + ContractAttachedFileModel.objects.all().select_related("contract"), pk=file_id ) if owner.contract.pk != file.contract.pk: raise PermissionDenied(_("Contract have no such file attached")) file.delete() return Response(status=status.HTTP_204_NO_CONTENT) - -class UploadFileContentView(APIView): + +################################################################################### +# @api-view | POST - /contract-owners/{owner_id}/files/{file_id}/upload +################################################################################### +@extend_schema(tags=["Contract File contents"]) +class UploadFileContentApiView(BaseApiViewMixin, GenericAPIView): # type: ignore permission_classes = [AllowAny] parser_classes = [MultiPartParser, FormParser] # type: ignore + method_permission_classes = {"post": [AllowAny]} + method_serializer_class = {"post": CreateContractFileContentFromOwnerSerializer} + @extend_schema( summary="Uploads a file for contract attached files", description="Creates a file for contract attached files.", - request=CreateContractFileContentFromOwnerSerializer, - responses={201: CreateContractFileContentFromOwnerSerializer}, ) def post( self, @@ -155,13 +118,11 @@ class UploadFileContentView(APIView): *args: object, **kwargs: object ) -> Response: - serializer = CreateContractFileContentFromOwnerSerializer( - data=request.data, # type: ignore - context={ - "file_id": file_id, - "contract_owner_id": owner_id, - } + serializer_context = dict(file_id=file_id, contract_owner_id=owner_id) + serializer = cast( + CreateContractFileContentFromOwnerSerializer, + self.get_serializer(data=request.data, context=serializer_context) # type: ignore ) serializer.is_valid(raise_exception=True) - serializer.save() # type: ignore + serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) diff --git a/core/utils/router.py b/core/utils/router.py new file mode 100644 index 0000000..61d6779 --- /dev/null +++ b/core/utils/router.py @@ -0,0 +1,54 @@ +from typing import Type, Optional, Self, override + +from rest_framework.viewsets import ViewSetMixin # type: ignore +from rest_framework.routers import DefaultRouter # type: ignore + + +class TypedRouter(DefaultRouter): + """ + A subclass of DRF's `rest_framework.routers.DefaultRouter` that adds proper static + type annotations to certain methods — particularly the `register` method. + + This resolves issues with static analysis tools such as MyPy and Pyright, + which normally raise warnings or require `# type: ignore` comments due to the + original method's lack of strict typing. + + Benefits: + - Eliminates the need to use `# type: ignore` when calling `.register(...)` + - Improves type safety by enforcing that only subclasses of `ViewSetMixin` are passed + - Enhances developer experience (DX) in statically typed projects + - Promotes DRY principles by reducing repetitive ignores across the codebase + + This class does not modify any runtime behavior of `DefaultRouter`. It is purely a DX + and tooling improvement for typed Python projects using Django REST Framework (DRF). + + Example: + from my_project.routers import TypedRouter + from my_app.api import UserViewSet + + router = TypedRouter() + router.register("users", UserViewSet, basename="user") + """ + + @override + def register( # type: ignore + self, + prefix: str, + viewset: Type[ViewSetMixin], + basename: Optional[str] = None, + **kwargs: object + ) -> Self: + """ + Registers a viewset with a given URL prefix and optional basename, with proper type hints + to improve static analysis (e.g., with MyPy or Pyright) when using DRF's DefaultRouter. + + This method overrides `DefaultRouter.register()` to annotate the expected `viewset` type + as a subclass of `ViewSetMixin`, which reflects how DRF expects viewsets to behave. + + Useful for eliminating `# type: ignore` comments and improving autocompletion. + + See also: + - DRF Router documentation: https://www.django-rest-framework.org/api-guide/routers/ + """ + super().register(prefix=prefix, viewset=viewset, basename=basename, **kwargs) # type: ignore + return self