Как изменить request.user в django в Middleware?

Что я пытаюсь сделать, так это определить тип вошедшего пользователя и затем установить параметр .profile в request.user, чтобы я мог использовать его, вызывая request.user.profile в моих представлениях.

Для этого я написал Middleware следующим образом:

class SetProfileMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):

        user, token = JWTAuthentication().authenticate(request)
        profile_type = token.payload.get("profile_type", None)

        request.user.profile = User.get_profile(profile_type, request.user)
        request.user.profile_type = profile_type

        response = self.get_response(request)

        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print(request.user.profile) # Works here

Теперь я могу получить доступ к request.user.profile в process_view, однако он не существует в моих представлениях и вызывает AttributeError, утверждая, что 'User' object has no attribute 'profile'.

Похоже, что мой request.user где-то перезаписывается, прежде чем попасть в представление.

Вот мой settings.py:

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

LOCAL_MIDDLEWARE = [
    "users.middleware.SetProfileMiddleware",
]

MIDDLEWARE = MIDDLEWARE + LOCAL_MIDDLEWARE

У меня уже была подобная проблема, и я решил ее с помощью декораторов.

Вы можете рассматривать декораторы как "локальное промежуточное ПО", которое, по сути, является функцией, выполняемой перед представлением запроса.

Это даже лучше, чем использование промежуточного ПО, потому что в то время как промежуточное ПО применяется к каждому представлению, декораторы применяются только к тем представлениям, которые вы хотите, что дает вам больше гибкости и контроля.

def set_profile(view_function):
    
    def decorated_function(request, *args, **kwargs):

        user, token = JWTAuthentication().authenticate(request)
        profile_type = token.payload.get("profile_type", None)

        request.user.profile = User.get_profile(profile_type, request.user)
        request.user.profile_type = profile_type

        return view_function(request, *args, **kwargs)

    return decorated_function # No invocation here

Затем в вашем представлении, основанном на функциях:

@api_view(["GET", "PUT"])
@set_profile
def my_view(request):
    request.user.profile # Will not throw attribute error
    ...

Единственная разница между представлением на основе функций и представлением на основе классов заключается в том, что декоратор будет принимать аргумент request вместо self.

def set_profile(view_function):
    
    def decorated_function(self, *args, **kwargs):

        user, token = JWTAuthentication().authenticate(self.request)
        profile_type = token.payload.get("profile_type", None)

        self.request.user.profile = User.get_profile(profile_type, self.request.user)
        self.request.user.profile_type = profile_type

        return view_function(self, *args, **kwargs)

    return decorated_function # No invocation here

Ваш класс должен выглядеть следующим образом:

class ProfileAPIView(generics.RetrieveUpdateAPIView):
serializer_class = ProfileSerializer

@set_profile
def get_object(self):
    obj = self.request.user.profile
    self.check_object_permissions(self.request, obj)
    return obj

После нескольких часов, потраченных на выяснение того, что происходит, оказалось, что метод SimpleJWT JWTAuthentication.authenticate() вызывается непосредственно перед тем, как запрос попадает в View, перезаписывая атрибут request.user.

Поэтому вместо того, чтобы пытаться добавить профиль в request.user с помощью промежуточного ПО, я в итоге настроил метод JWTAuthentication.authentication():

class CustomAuth(JWTAuthentication):
    def authenticate(self, request):

        user, token = super().authenticate(request)

        profile_type = token.payload.get("profile_type", None)
        user.profile = User.get_profile((profile_type, user)
        user.profile_type = profile_type

        return user, token

settings.py:

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "users.authentication.CustomAuth"
    ],
}
Вернуться на верх