Getting the value on every page usertype in Django

Viewed 246

I don't know how can I prevent using Global variable to pass data to another function. It's too much hassle to create request.session on every page just to get the value set by another function. I know there's a way e.g using decorators etc. but I haven't found yet the best way on how to implement it. Can anyone help me? Below I been using Global variable in usertype request.session['type_user'] = usertype. Thanks for the help.

def example_login(request):
   usertype = "Admin"
   request.session['type_user'] = usertype 
   return redirect ('dashboard')

def dashboard(request):
   value_usertype = {'usertype_var':request.session['type_user']}
   return render(request,'dashboard.html', value_usertype)
   
def anotherview(request):  # too hassle to create another request.session in every page
   value_usertype = {'usertype_var':request.session['type_user']}
   return render(request,'dashboard.html', value_usertype)
2 Answers

Function-based view

You can construct a decorator that will pass the usertype_var to the view function:

from functools import wraps

def with_usertype(f):
    @wraps(f)
    def wrapped(request, *args, **kwargs):
        return f(request, *args, **kwargs, usertype=request.session.get('type_user'))
    return wrapped

You can then use the decorator as:

@with_usertype
def dashboard(request, usertype):
   return render(request,'dashboard.html', {'usertype_var': usertype})

@with_usertype
def anotherview(request, usertype):
   return render(request,'dashboard.html', , {'usertype_var': usertype})

You can also restrict it with:

from functools import wraps
from django.core.exceptions import PermissionDenied

def restrict_usertype(*restrictions):
    def mydecorator(f):
        @wraps(f)
        def wrapped(request, *args, **kwargs):
            usertype = request.session.get('type_user')
            if usertyp in restrictions:
                return f(request, *args, **kwargs, usertype=usertype)
            else:
                raise PermissionDenied
        return wrapped
    return mydecorator

then you can restrict this with:

@restrict_usertype('Admin')
def dashboard(request, usertype):
   return render(request,'dashboard.html', {'usertype_var': usertype})

@restrict_usertype('Admin', 'Other')
def anotherview(request, usertype):
   return render(request,'dashboard.html', , {'usertype_var': usertype})

Class-based views

For class-based views, since you can make use of the setup(…) method [Django-doc]. This method normally sets the request, args and kwargs to the view object:

# since Django-2.2
class UsertypeMixin:

    def setup(self, request, *args, **kwargs):
        self.usertype = request.session.get('type_user')
        return super().setup(*args, **kwargs)

    def get_context_data(self, *args, **kwargs):
        return super().get_context_data(
            *args,
            **kwargs,
            usertype=self.usertype
        )

Then the mixin can be used, for example in a ListView with:

class MyListView(UserTypeMixin, ListView):
    
    def get_queryset(self, *args, **kwargs):
        # use self.usertype
        # …
        return super().get_queryset(*args, **kwargs)

we can restrict this as well with:

# since Django-2.2

from django.core.exceptions import PermissionDenied

class UsertypeRestrictMixin(UsertypeMixin):
    required_usertype = ()

    def setup(self, request, *args, **kwargs):
        result = super().setup(*args, **kwargs)
        if self.usertype not in self.required_usertype:
            raise PermissionDenied
        return result

and then use this as:

class MyListView(UsertypeRestrictMixin, ListView):
    required_usertype = ('Admin',)

Django's groups and permissions

However often a better modeling is using groups, since a user can often have multiple roles. It can be a commentator, administrator, author, etc. and often after a while users can have multiple roles. Django has a system of groups and permissions [Django-doc] for this in place.

The advantage of using permissions is that this is more fine-grained, so one can make permissions for each (type of) view, and furthermore it allows a person to be a member of multiple groups, and also thus obtain the permissions of all these groups. This makes it more flexible since sometimes you want to give a small set of users an extra permission.

I suggest you to rely on Django's auth system for both authentication (username + password = user1) and authorization (can user1 user this view?)

Using Django's auth system:

  • Login:
from django.contrib.auth import authenticate, login

def my_view(request):
    username = request.POST['username']
    password = request.POST['password']
    user = authenticate(request, username=username, password=password)
    if user is not None:
        login(request, user)
        # Redirect to a success page.
        ...
    else:
        # Return an 'invalid login' error message.
        ...
  • Logout:
from django.contrib.auth import logout

def logout_view(request):
    logout(request)
    # Redirect to a success page.
  • Limiting access for authenticated users

    • redirecting to login
    from django.contrib.auth.decorators import login_required
    
    @login_required
    def my_view(request):
        ...
    
    • redirecting to custom template
    from django.contrib.auth.decorators import login_required
    
    @login_required(redirect_field_name='my_redirect_field')
    def my_view(request):
        ...
    
  • Limiting access for authorized users

    • based on roles (Django's recommended approach)
    from django.contrib.auth.decorators import permission_required
    
    @permission_required('your_app.admin_rigths')
    def my_view(request):
        ...
    
    • based on groups (custom way)
    from django.contrib.auth.decorators import user_passes_test
    
    def is_admin(user):
        if your_custom_verification_for_admin_rights:
          return True
        return Fa
    
    @user_passes_test(email_check)
    def my_view(request):
        ...
    

See more in https://docs.djangoproject.com/en/3.1/topics/auth/default/#auth-web-requests

Custom auth approach

You can also not make use of Django's approach, as I needed once. I did the following:

  • Create an authentication middleware
class CustomAuthMiddleware:
  def __init__(self, get_response):
    self.get_response = get_response

  def __call__(self, request):
    # do your custom verification
    user_type = get_user_type_function(request)
    
    # add its result to the request object
    request.__setattr__('type_user', user_type)

    # go to normal request flow
    return self.get_response(request)
  • Add it to settings
MIDDLEWARE = [
  ...django's middleware

  'your_auth_app.path.to.middleware.CustomAuthMiddleware',
  • Create a decorator for authorization enforcement
from django.core.exceptions import PermissionDenied

def needs_user_type(user_type):
  def auth_decorator(function):
        def wrapper(request, *args, **kwargs):
            authorized = False
            if hatattr(request, 'type_user'):
              request_user_type = request.__getattribute__('type_user')
              authorized = request_user_type == user_type

            if authorized:
                return function(*args, **kwargs)
            else:
                return PermissionDenied()
        
        return wrapper

    return auth_decorator
  • Use the decorator in your views
@needs_user_type('Admin')
def dashboard(request):
   # do stuff

@needs_user_type('Admin')
def anotherview(request):
   # do stuff

If you want to access user_type in your views, see Django's Request Context

Related