Add object level permission to generic view
Asked Answered
H

2

12

The situation is pretty simple: I'm writing a multi-user blog system. The system should prevent non-owner to edit or delete a blog post. In my view I use generic view.

class BlogUpdateView(UpdateView): ...

I know I should use @method_decorator to decorate dispatch method. However, most example is just @method_decorator(login_required) or model level permission. How can apply object level permission to check whether request.user is the author of this blog post? For example, I tried to use django-authority apps, and I have a BlogPermission class in this file. and I tried to define a method in this class e.g.

def blog_edit(self, ??, ??)

what should I put into this method?

And then call this like: @method_decorator(permission_required('blog_permission.blog_edit(???)'))

What should I pass in here?

Update: After read method_decorator code, I find it can only accept function without argument. I think that's why permission_required doesn't work here. But what's the work around about this?

Update solution:

In dispatch method, I check the user permission and then return HttpResponseForbidden() if the user does not meet the permission.

Hauptmann answered 26/4, 2012 at 3:45 Comment(4)
You can check your permissions in get_object method. Decorators do not look good with CBV.Poulard
so you mean there is no easy way to apply object level decorator to Class based generic view? @PoulardHauptmann
I mean it will be easier to do in get_object. To make it more DRY you can make a Mixin with that get_object and use it.Poulard
Hi ilvar, would you provide a example to do this? Like if I want to write a blog updateview, in get_object if I find the request.user is not blog.author, what should I return to raise a 403 error? Thank you @Daniel RosemanHauptmann
P
16

You can do it using class-based-views:

class BlogEdit(UpdateView):
    model = Blog

    def dispatch(self, request, *args, **kwargs):
        if not request.user.has_perm('blog_permission.blog_edit'):
            return HttpResponseForbidden()
        return super(BlogEdit, self).dispatch(request, *args, **kwargs)

    # OR (for object-level perms)

    def get_object(self, *args, **kwargs):
        obj = super(BlogEdit, self).get_object(*args, **kwargs)
        if not obj.user == self.request.user:
            raise Http404 # maybe you'll need to write a middleware to catch 403's same way
        return obj
Poulard answered 28/4, 2012 at 5:11 Comment(1)
The built-in exception django.core.exceptions.PermissionDenied can be raised to show the 403 error page from get_object. You can then customize the error in the 403.html template, or override the view with handler403 in the URLconf (see docs.djangoproject.com/en/1.7/topics/http/views/…). Unfortunately you can't pass a message through the exception and the template context is not delivered the raw exception through the built-in behavior. If you want to show a special message you would need to create middleware.Varion
S
1

Another option is to use UserPassesTestMixin (or user_passes_test for function-based).

class UserPassesTestMixin

When using class-based views, you can use the UserPassesTestMixin to do this.

test_func()

You have to override the test_func() method of the class to provide the test that is performed. Furthermore, you can set any of the parameters of AccessMixin to customize the handling of unauthorized users:

from django.contrib.auth.mixins import UserPassesTestMixin

class MyView(UserPassesTestMixin, View):

    def test_func(self):
        return self.request.user.email.endswith('@example.com')

We can now check if the self.request.user is allowed to process the details passed into the self.request.GET or self.request.POST.

class MyView(UserPassesTestMixin, View):
    raise_exception = True  # To not redirect to the login url and just return 403. For the other settings, see https://docs.djangoproject.com/en/3.2/topics/auth/default/#django.contrib.auth.mixins.AccessMixin

    def test_func(self):
        return (
            self.request.user.is_staff
            or self.request.user.has_perm('app.change_blog')
            or self.request.user.email.endswith('@company.staff.com')
            or is_requested_object_accessible(self.request.user, self.request.GET, self.request.POST)  # If you have a custom checker
        )
    ...
Septic answered 24/8, 2021 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.