djangorestframework-stubs icon indicating copy to clipboard operation
djangorestframework-stubs copied to clipboard

request.user does not use custom User model

Open ollejernstrom opened this issue 1 year ago • 3 comments

Bug report

What's wrong

These are from the Request object in the stubbs.

def user(self) -> AbstractBaseUser | AnonymousUser: ...
@user.setter
def user(self, value: AbstractBaseUser | AnonymousUser) -> None: ...

However i have a different auth model defined in my settings.py called CustomUser

AUTH_USER_MODEL = "base.CustomUser"

Is there a way to get this typing correct.

Use case error:

user = self.request.user
if user.can_manage():
    return queryset
products/views.py:145: error: Item "AnonymousUser" of "AbstractBaseUser | AnonymousUser" has no attribute "can_manage"  [union-attr]

Furthermore, if the permission classes dont allow "AnonymousUser" can we type narrow that somehow? (Like in django-stubs

How can I create a HttpRequest that's guaranteed to have an authenticated user?

How is that should be

It should be narrowed to just my CustomUser

TL;DR:

  • How can i only see CustomUser instead of AbstractBaseUser | AnonymousUser

System information

  • OS: MAC
  • python version: 3.11
  • django version: 4.2
  • mypy version: 1.7.1
  • django-stubs version: (latest)

ollejernstrom avatar Dec 04 '23 12:12 ollejernstrom

I found a solution, if anyone has a better, please tell me:

from rest_framework.request import Request, wrap_attributeerrors
from django.db.models import Model
from django.http import HttpRequest
from rest_framework.generics import (
    CreateAPIView,
    DestroyAPIView,
    GenericAPIView,
    ListAPIView,
    ListCreateAPIView,
    RetrieveAPIView,
    RetrieveDestroyAPIView,
    RetrieveUpdateAPIView,
    RetrieveUpdateDestroyAPIView,
    UpdateAPIView,
)
from rest_framework.request import Request
from rest_framework.views import APIView
from base.db.models import CustomUser # my user model


class AuthenticatedApiRequest(Request):
    @property
    def user(self) -> CustomUser:
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user

    @user.setter
    def user(self, value: CustomUser) -> None:
        self._user = value
        self._request.user = value
        
T = TypeVar('T', bound=Model)


class AuthedViewMixin(APIView):
    """Mixin that types user as authenticated"""

    request: AuthenticatedApiRequest
    permission_classes = [IsAuthenticated]
    
    @override
    def initialize_request(self, request: HttpRequest, *args: Any, **kwargs: Any) -> Request:
        """Override to use AuthenticatedApiRequest"""
        return AuthenticatedApiRequest(request, *args, **kwargs)


class ListCreateAuthedAPIView(AuthedViewMixin, ListCreateAPIView[T]):
    pass

...

Using the ListCreateAuthedAPIView now types the user correctly

ollejernstrom avatar Dec 05 '23 09:12 ollejernstrom

Thanks!

django-stubs also has a similar common issue and they suggest something analogous: https://github.com/typeddjango/django-stubs#how-can-i-create-a-httprequest-thats-guaranteed-to-have-an-authenticated-user

Personally, I've opted to just use assert isinstance(request.user, CustomUser) in functions where I need to access the user.

intgr avatar Dec 08 '23 12:12 intgr

I wonder if it is possible to solve it at all. All I can think of are workarounds. Trying to inherit from Request when there are imported stubs for DRF won't work becuause it produces override type error.

The only crutch we are left with is an assertion.

ysmolski avatar Feb 05 '24 16:02 ysmolski