10

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.

gglasses
  • 826
  • 11
  • 30

2 Answers2

14

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
Alasdair
  • 298,606
  • 55
  • 578
  • 516
  • 1
    Thank you a lot for your extended explanation. And to me makes sense to restrict the objects to the ones owned by the user. Just one quick question, for this to work I need to inherit `OwnerQuerysetMixin` before `UpdateView`. Why is that? I thought `get_queryset` was a method from `UpdateView` and should be overwritten by the one in `OwnerQuerysetMixin`, with that line of thought, to me (pardon me if I'm missing something) makes more sense to inherit `OwnerQuerysetMixin` after `UpdateView`. Sigh, I just want to be clear about how the inheritance thing works in here. – gglasses Jul 23 '16 at 18:42
  • 1
    [This question](http://stackoverflow.com/questions/10018757/how-does-the-order-of-mixins-affect-the-derived-class) might help you understand the ordering. – Alasdair Jul 23 '16 at 20:35
3

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):
rafalmp
  • 3,988
  • 3
  • 28
  • 34
  • Indeed, but then I get a `'AttributeError: EditPost' object has no attribute 'post'` :/ – gglasses Jul 23 '16 at 18:18
  • Well, I actually get this is because UpdateView is the object that actually gets the object `post` and your way I'm importing `TestUserOwnerOfPost` first. The thing is, how can I accomplish my validation? – gglasses Jul 23 '16 at 18:19