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?
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
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.
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?
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
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.