What I'm trying to do is to detect the type of logged-in user and then setting a .profile
parameter to request.user
, so I can use it by calling request.user.profile
in my views.
To do this, I've wrote a Middleware
as follows:
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
# Works Here
print("-" * 20)
print(type(request.user)) # <class 'django.utils.functional.SimpleLazyObject'>
print('Process Request ->', request.user.profile)
response = self.get_response(request)
# Does not work here
print("-" * 20)
print(type(request.user)) # <class 'users.models.User'>
print('Process Response ->', request.user.profile)
return response
def process_view(self, request, view_func, view_args, view_kwargs):
# Works here
print("-" * 20)
print(type(request.user)) # <class 'django.utils.functional.SimpleLazyObject'>
print('Process View ->', request.user.profile)
Now I can access request.user.profile
in process_view
however it does not exists in my views and is causing an AttributeError
stating that 'User' object has no attribute 'profile'
.
Seems my request.user
is being overwritten somewhere before hitting the view.
Note that I'm using Django Rest Framework, here is my view:
class ProfileAPIView(generics.RetrieveUpdateAPIView):
serializer_class = ProfileSerializer
def get_object(self):
obj = self.request.user.profile # Raise the `AttributeError`
self.check_object_permissions(self.request, obj)
return obj
Here is my 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
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
"DEFAULT_RENDERER_CLASSES": (
"rest_framework.renderers.JSONRenderer",
"rest_framework.renderers.BrowsableAPIRenderer",
),
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
}
SIMPLE_JWT = {
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(minutes=45),
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.SlidingToken",),
}
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
AUTH_USER_MODEL = "users.User"
LOGIN_REDIRECT_URL = "admin/"
CodePudding user response:
The problem is that you cannot add new properties to the User
class.
instead try to add the property directly to the request like this
request.user_profile = User.get_profile(profile_type, request.user)
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
Then in your function based view:
@api_view(["GET", "PUT"])
@set_profile
def my_view(request):
request.user_profile # Will not throw attribute error
...
The only difference between function based view and class based view is that the decorator will receive request
argument instead of 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
Your class should look like this:
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
CodePudding user response:
After spending hours to figure out what is going on, turned out that SimpleJWT's JWTAuthentication.authenticate()
method gets called just before the request hits the View, overwriting the request.user
attribute.
So instead of trying to add the profile to the request.user
using a middleware, I ended-up customizing JWTAuthentication.authentication()
method:
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"
],
}