Home > OS >  How to modify django's request.user in a Middleware?
How to modify django's request.user in a Middleware?

Time:11-18

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"
    ],
}
  • Related