Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: a class based view with mixins and dispatch method

Normally, I use a dispatch method of a class based view to set some initial variables or add some logic based on user's permissions.

For example,

from django.views.generic import FormView
from braces.views import LoginRequiredMixin

class GenerateReportView(LoginRequiredMixin, FormView):
    template_name = 'reporting/reporting_form.html'
    form_class = ReportForm

    def get_form(self, form_class):
        form = form_class(**self.get_form_kwargs())
        if not self.request.user.is_superuser:
            form.fields['report_type'].choices = [
                choice for choice in form.fields['report_type'].choices
                if choice[0] != INVOICE_REPORT
            ]
        return form

It works as expected: when an anonymous user visits a pages, the dispatch method of LoginRequiredMixin is called, and then redirects the user to the login page.

But if I want to add some permissions for this view or set some initial variables, for example,

class GenerateReportView(LoginRequiredMixin, FormView):

    def dispatch(self, *args, **kwargs):
        if not (
            self.request.user.is_superuser or
            self.request.user.is_manager
        ):
            raise Http404
        return super(GenerateReportView, self).dispatch(*args, **kwargs)

in some cases it doesn't work, because dispatch methods of the mixins, that the view inherits, haven't been called yet. So, for example, to be able to ask for user's permissions, I have to repeat the validation from LoginRequiredMixin:

class GenerateReportView(LoginRequiredMixin, FormView):

    def dispatch(self, *args, **kwargs):
        if self.request.user.is_authenticated() and not (
            self.request.user.is_superuser or
            self.request.user.is_manager
        ):
            raise Http404
        return super(GenerateReportView, self).dispatch(*args, **kwargs)

This example is simple, but sometimes there are some more complicated logic in a mixin: it checks for permissions, makes some calculations and stores it in a class attribute, etc.

For now I solve it by copying some code from the mixin (like in the example above) or by copying the code from the dispatch method of the view to another mixin and inheriting it after the first one to execute them in order (which is not that pretty, because this new mixin is used only by one view).

Is there any proper way so solve these kind of problems?

like image 917
cansadadeserfeliz Avatar asked Sep 07 '25 09:09

cansadadeserfeliz


2 Answers

I would write custom class, which check all permissions

from django.views.generic import FormView
from braces.views import AccessMixin

class SuperOrManagerPermissionsMixin(AccessMixin):
    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated():
            return self.handle_no_permission(request)
        if self.user_has_permissions(request):
            return super(SuperOrManagerPermissionsMixin, self).dispatch(
                request, *args, **kwargs)
        raise Http404 #or return self.handle_no_permission

    def user_has_permissions(self, request):
        return self.request.user.is_superuser or self.request.user.is_manager

# a bit simplyfied, but with the same redirect for anonymous and logged users
# without permissions


class SuperOrManagerPermissionsMixin(AccessMixin):
    def dispatch(self, request, *args, **kwargs):
        if self.user_has_permissions(request):
            return super(SuperOrManagerPermissionsMixin, self).dispatch(
                request, *args, **kwargs)
        else:
            return self.handle_no_permission(request)

    def user_has_permissions(self, request):
        return request.user.is_authenticated() and (self.request.user.is_superuser
                                                    or self.request.user.is_manager)


class GenerateReportView(SuperOrManagerPermissionsMixin, FormView):
#Remove next two lines, don't need it
    def dispatch(self, *args, **kwargs):
        #or put some logic here
        return super(GenerateReportView, self).dispatch(*args, **kwargs)

And implementation of class GenerateReportView(SuperOrManagerPermissionsMixin, FormView) does not require overriding dispatch method

If you use multiple inheritance and one of the parent classes need some improvement, it's good to improve it first. It keeps code cleaner.

like image 139
KePe Avatar answered Sep 09 '25 03:09

KePe


this is an old post but other people might come across so here is my proposed solution.

When you say

"[...]I want to add some permissions for this view or set some initial variables, for example[...]"

Instead of setting those initial variables in the dispatch method of your view, you could write a separated method for setting up those variables, and then call that method in your get (and post if needed) method. They are called after dispatch, so setting up your initial variables wont clash with the dispatch in your mixins. So override the method

def set_initial_variables():
    self.hey = something
    return 

def get(blablabla):
    self.set_initial_variables()
    return super(blabla, self).get(blabla)

This probably is cleaner than copying and pasting the code of your mixin in your view's dispatch.

like image 45
andyk Avatar answered Sep 09 '25 05:09

andyk