Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django: DRY principle and UserPassesTestMixin

I have a model named Post and have a field there called owner (foreign key to User). Of course, only owners can update or delete their own posts.

That being said, I use login_required decorator in the views to make sure the user is logged in but then, I also need to make sure the user trying to update/delete the question is the owner.

As I'm using Django: Generic Editing Views the documentation says I need to use Django: UserPassesTestMixin.

This validation will be done for the update and delete views. DRY, what is the way to go about this? should I create a class named TestUserOwnerOfPost and create a test_func() and then make the update and delete views inherit from it?

Cause that's what I have tried and didn't work, code below:

from django.views.generic.edit import UpdateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import UserPassesTestMixin

class TestUserOwnerOfPost(UserPassesTestMixin):                         
    def test_func(self):                                                       
        return self.request.user == self.post.owner

class EditPost(UpdateView, TestUserOwnerOfPost):                         
    model = Post                                                                         
    @method_decorator(login_required)                                          
    def dispatch(self, *args, **kwargs):                                       
        return super(EditPost, self).dispatch(*args, **kwargs)

With the code above, every logged-in user in the system can edit/delete any post. What am I doing wrong? am I missing something? thanks.

like image 859
gglasses Avatar asked Jul 23 '16 17:07

gglasses


2 Answers

The first problem is that the order of the classes you inherit is incorrect, as @rafalmp says.

However, fixing that doesn't solve the problem, because the UserPassesTest mixin performs the test before running the view. This means that it's not really suitable to check the owner of self.object, because self.object has not been set yet. Note I'm using self.object instead of self.post -- I'm don't think that the view ever sets self.post but I might be wrong about that.

One option is to call self.get_object() inside the test function. This is a bit inefficient because your view will fetch the object twice, but in practice it probably doesn't matter.

def test_func(self):
    self.object = self.get_object()
    return self.request.user == self.object.owner

Another approach is to override get_queryset, to restrict it to objects owned by the user. This means the user will get a 404 error if they do not own the object. This behaviour is not exactly the same as the UserPassesTestMixin, which will redirect to a login page, but it might be ok for you.

class OwnerQuerysetMixin(object):                         
    def get_queryset(self):
        queryset = super(OwnerQuerysetMixin, self).get_queryset()                                                   
        # perhaps handle the case where user is not authenticated
        queryset = queryset.filter(owner=self.request.user)
        return queryset
like image 166
Alasdair Avatar answered Nov 17 '22 05:11

Alasdair


The order of the classes you inherit from matters. For your access control to work, it must be enforced before UpdateView is executed:

class EditPost(TestUserOwnerOfPost, UpdateView):
like image 3
rafalmp Avatar answered Nov 17 '22 04:11

rafalmp