Django – object level premission and class based generic views

Question:

This is the model:

class Car(models.Model):
    user = models.ForeignKey(User, related_name='cars')
    name = models.CharField(max_length=64)

Url pattern goes something like this:

url(r'^car/(?P<pk>d+)/$', login_required(CarDetails.as_view()), name='car_details)

And view:

class CarDetail(DetailView):
    context_object_name = 'car'
    template_name = 'my_app/car_details.html'
    model = models.Car

    def get_object(self, *args, **kwargs):
        car = super(CarDetail, self).get_object(*args, **kwargs)
        if car.user != self.request.user:
            raise PermissionDenied()
        else:
            return car

This works fine, but in every class I have to override get_object to prevent user to mess with someone else’s objects. This includes editing and deleting for every model I have and this is serious violation of DRY principle.

Is there a better way to do this? Something like login_required decorator maybe?

Asked By: del-boy

||

Answers:

What about definig base class (or mixin) and using inheritance?

class CurUserOnlyDetailView(DetailView):
    def get_object(self, *args, **kwargs):
        obj = super(CurUserOnlyDetailView, self).get_object(*args, **kwargs)
        if obj.user != self.request.user:
            raise PermissionDenied()
        else:
            return obj

class CarDetail(CurUserOnlyDetailView):
    context_object_name = 'car'
    template_name = 'my_app/car_details.html'
    model = models.Car

# another view, no DRY violation
class BikeDetail(CurUserOnlyDetailView):
    context_object_name = 'bike'
    template_name = 'my_app/bike_details.html'
    model = models.Bike
Answered By: DrTyrsa

The solution was more-or-less simple as DrTyrsa proposed in his answer, with one little difference. I created base class CurUserOnly that inherits object, instead of DetailView (I wanted to use this class with DeleteView and UpdateView, too) and now CarDetail inherits CurUserOnly and DetailView, CarDelete inherits CurUserOnly and DeleteView and so on…

Funny thing is that I tried this before, but it didn’t work because I forgot python’s MRO and DetailView was first in inheritance list when CurUserOnly should be!

In the end, here is CurUserOnly class:

class CurUserOnly(object):
    def get_object(self, *args, **kwargs):
        obj = super(CurUserOnly, self).get_object(*args, **kwargs)
        user_attribute = getattr(self, 'user_attribute', 'user')
        user = obj
        for part in user_attribute.split('.'):
            user = getattr(user, part, None)
        if user != self.request.user:
            raise PermissionDenied()
        else:
            return obj

And if I have a model that does not have direct contact to user all I need to do is add user_attribute field. For example, if I have model Tyre with ForeignKey to Car its DeleteView would look like this:

class TyreDelete(CurUserOnly, DeleteView):
    model = models.Tyre
    user_attribute = 'car.user'

This answer was posted as an edit to the question Django – object level premission and class based generic views by the OP del-boy under CC BY-SA 3.0.

Answered By: vvvvv
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.