Как изменить 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"
],
}