How to use permission_required decorators on django class-based views
I'm having a bit of trouble understanding how the new CBVs work. My question is this, I need to require login in all the views, and in some of them, specific permissions. In function-based views I do that with @permission_required() and the login_required attribute in the view, but I don't know how to do this on the new views. Is there some section in the django docs explaining this? I didn't found anything. What is wrong in my code?
I tried to use the @method_decorator but it replies "TypeError at /spaces/prueba/ _wrapped_view() takes at least 1 argument (0 given)"
Here is the code (GPL):
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required, permission_required
class ViewSpaceIndex(DetailView):
"""
Show the index page of a space. Get various extra contexts to get the
information for that space.
The get_object method searches in the user 'spaces' field if the current
space is allowed, if not, he is redirected to a 'nor allowed' page.
"""
context_object_name = 'get_place'
template_name = 'spaces/space_index.html'
@method_decorator(login_required)
def get_object(self):
space_name = self.kwargs['space_name']
for i in self.request.user.profile.spaces.all():
if i.url == space_name:
return get_object_or_404(Space, url = space_name)
self.template_name = 'not_allowed.html'
return get_object_or_404(Space, url = space_name)
# Get extra context data
def get_context_data(self, **kwargs):
context = super(ViewSpaceIndex, self).get_context_data(**kwargs)
place = get_object_or_404(Space, url=self.kwargs['space_name'])
context['entities'] = Entity.objects.filter(space=place.id)
context['documents'] = Document.objects.filter(space=place.id)
context['proposals'] = Proposal.objects.filter(space=place.id).order_by('-pub_date')
context['publication'] = Post.objects.filter(post_space=place.id).order_by('-post_pubdate')
return context
There are a few strategies listed in the CBV docs:
Decorate the view when you instantiate it in your urls.py
(docs)
urlpatterns = [
path('view/',login_required(ViewSpaceIndex.as_view(..)),
...
]
The decorator is applied on a per-instance basis, so you can add it or remove it in different urls.py
routes as needed.
Decorate your class so every instance of your view is wrapped (docs)
There's two ways to do this:
-
Apply
method_decorator
to your CBV dispatch method e.g.,from django.utils.decorators import method_decorator @method_decorator(login_required, name='dispatch') class ViewSpaceIndex(TemplateView): template_name = 'secret.html'
If you're using Django < 1.9 (which you shouldn't, it's no longer supported) you can't use method_decorator
on the class, so you have to override the dispatch
method manually:
class ViewSpaceIndex(TemplateView):
@method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(ViewSpaceIndex, self).dispatch(*args, **kwargs)
-
Use a mixin like django.contrib.auth.mixins.LoginRequiredMixin outlined well in the other answers here:
from django.contrib.auth.mixins import LoginRequiredMixin class MyView(LoginRequiredMixin, View): login_url = '/login/' redirect_field_name = 'redirect_to'
Make sure you place the mixin class first in the inheritance list (so Python's Method Resolution Order algorithm picks the Right Thing).
The reason you're getting a TypeError
is explained in the docs:
Note: method_decorator passes *args and **kwargs as parameters to the decorated method on the class. If your method does not accept a compatible set of parameters it will raise a TypeError exception.
Here is my approach, I create a mixin that is protected (this is kept in my mixin library):
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
class LoginRequiredMixin(object):
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
Whenever you want a view to be protected you just add the appropriate mixin:
class SomeProtectedViewView(LoginRequiredMixin, TemplateView):
template_name = 'index.html'
Just make sure that your mixin is first.
Update: I posted this in way back in 2011, starting with version 1.9 Django now includes this and other useful mixins (AccessMixin, PermissionRequiredMixin, UserPassesTestMixin) as standard!
Here's an alternative using class based decorators:
from django.utils.decorators import method_decorator
def class_view_decorator(function_decorator):
"""Convert a function based decorator into a class based decorator usable
on class based Views.
Can't subclass the `View` as it breaks inheritance (super in particular),
so we monkey-patch instead.
"""
def simple_decorator(View):
View.dispatch = method_decorator(function_decorator)(View.dispatch)
return View
return simple_decorator
This can then be used simply like this:
@class_view_decorator(login_required)
class MyView(View):
# this view now decorated
For those of you who use Django >= 1.9, it's already included in django.contrib.auth.mixins
as AccessMixin
, LoginRequiredMixin
, PermissionRequiredMixin
and UserPassesTestMixin
.
So to apply LoginRequired to CBV(e.g. DetailView
):
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.detail import DetailView
class ViewSpaceIndex(LoginRequiredMixin, DetailView):
model = Space
template_name = 'spaces/space_index.html'
login_url = '/login/'
redirect_field_name = 'redirect_to'
It's also good to keep in mind GCBV Mixin order: Mixins must go on the left side, and the base view class must go in the right side. If the order is different you can get broken and unpredictable results.