Django: a class based view with mixins and dispatch method
Question:
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?
Answers:
For the example you gave, I would use UserPassesTestMixin
from django-braces.
class GenerateReportView(UserPassesTestMixin, FormView):
def test_func(self, user):
return user.is_superuser or user.is_manager
If that isn’t suitable for your more complicated logic, then creating a separate mixin sounds like an OK approach, as it encapsulates the complicated logic nicely.
EDIT
As of django 1.9, the UserPassesTestMixin is now included in django: https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.mixins.UserPassesTestMixin
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.
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.
It can be done with Django UserPassesTestMixin mixin or @user_passes_test decorator.
Example of UserPassesTestMixin
from django.contrib.auth.mixins import UserPassesTestMixin
class SuperUserOrManagerRequiredMixin(UserPassesTestMixin):
def test_func(self):
if self.request.user.is_superuser or self.request.user.is_manager:
return True
return False
class MyView(LoginRequiredMixin, SuperUserOrManagerRequiredMixin, View):
...
If you override the dispatch
, like you’ve noticed, your LoginRequiredMixin
would stop working. While you could add a check to see if the user is authenticated, just that check wouldn’t be enough to handle the redirects too.
Just like mentioned in two other answers, I also prefer to use UserPassesTestMixin
with a test_func()
method containing the logic. The reason I’m writing this answer is to show it can be used
- together with the LoginRequiredMixin
- for more complex cases (with try/excepts for example)
Here’s what I’d do in your case
from django.views.generic.edit import FormView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class GenerateReportView(LoginRequiredMixin, UserPassesTestMixin, FormView):
template_name = 'reporting/reporting_form.html'
form_class = ReportForm
def test_func(self):
if not (
self.request.user.is_superuser or
self.request.user.is_manager
):
return False
try:
# some logic
return True
except:
return False
Notice the usage of LoginRequiredMixin
before UserPassesTestMixin
. The order is relevant. It means that it’ll check if the user is authenticated first, before checking the test_func()
. If the user is not authenticated, then will be redirected to the login page.
Also, I like how you use the if not (...)
, which was adapted to
if not (...):
return False
In addition to that, since you’re looking for some more logic, you can do that with a try/except
block (which could even call another function(s))
try:
# some logic
return True
except:
return False
return True
is to say that the user has then permissions to access the view, whereas return False
will give to the user a 403 status code.
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?
For the example you gave, I would use UserPassesTestMixin
from django-braces.
class GenerateReportView(UserPassesTestMixin, FormView):
def test_func(self, user):
return user.is_superuser or user.is_manager
If that isn’t suitable for your more complicated logic, then creating a separate mixin sounds like an OK approach, as it encapsulates the complicated logic nicely.
EDIT
As of django 1.9, the UserPassesTestMixin is now included in django: https://docs.djangoproject.com/en/1.11/topics/auth/default/#django.contrib.auth.mixins.UserPassesTestMixin
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.
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.
It can be done with Django UserPassesTestMixin mixin or @user_passes_test decorator.
Example of UserPassesTestMixin
from django.contrib.auth.mixins import UserPassesTestMixin
class SuperUserOrManagerRequiredMixin(UserPassesTestMixin):
def test_func(self):
if self.request.user.is_superuser or self.request.user.is_manager:
return True
return False
class MyView(LoginRequiredMixin, SuperUserOrManagerRequiredMixin, View):
...
If you override the dispatch
, like you’ve noticed, your LoginRequiredMixin
would stop working. While you could add a check to see if the user is authenticated, just that check wouldn’t be enough to handle the redirects too.
Just like mentioned in two other answers, I also prefer to use UserPassesTestMixin
with a test_func()
method containing the logic. The reason I’m writing this answer is to show it can be used
- together with the LoginRequiredMixin
- for more complex cases (with try/excepts for example)
Here’s what I’d do in your case
from django.views.generic.edit import FormView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class GenerateReportView(LoginRequiredMixin, UserPassesTestMixin, FormView):
template_name = 'reporting/reporting_form.html'
form_class = ReportForm
def test_func(self):
if not (
self.request.user.is_superuser or
self.request.user.is_manager
):
return False
try:
# some logic
return True
except:
return False
Notice the usage of LoginRequiredMixin
before UserPassesTestMixin
. The order is relevant. It means that it’ll check if the user is authenticated first, before checking the test_func()
. If the user is not authenticated, then will be redirected to the login page.
Also, I like how you use the if not (...)
, which was adapted to
if not (...):
return False
In addition to that, since you’re looking for some more logic, you can do that with a try/except
block (which could even call another function(s))
try:
# some logic
return True
except:
return False
return True
is to say that the user has then permissions to access the view, whereas return False
will give to the user a 403 status code.