row level permissions in django

9-bits picture 9-bits · Aug 9, 2012 · Viewed 11.9k times · Source

Is there a way to do row level permissions in django? I thought there wasn't but just noticed this in the docs:

Permissions can be set not only per type of object, but also per specific object instance. By using the has_add_permission(), has_change_permission() and has_delete_permission() methods provided by the ModelAdmin class, it is possible to customize permissions for different object instances of the same type.

https://docs.djangoproject.com/en/dev/topics/auth/

But i don't see any documentation on how to actually implement per instance permissions

Answer

Paul Bormans picture Paul Bormans · Dec 2, 2012

For an application i'm building i want to provide row level permission through a simple decorator. I can do this because the condition is just whether the request.user is the owner of the model object.

Following seems to work:

from functools import wraps
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist

def is_owner_permission_required(model, pk_name='pk'):
    def decorator(view_func):
        def wrap(request, *args, **kwargs):
            pk = kwargs.get(pk_name, None)
            if pk is None:
                raise RuntimeError('decorator requires pk argument to be set (got {} instead)'.format(kwargs))
            is_owner_func = getattr(model, 'is_owner', None)
            if is_owner_func is None:
                raise RuntimeError('decorator requires model {} to provide is_owner function)'.format(model))
            o=model.objects.get(pk=pk) #raises ObjectDoesNotExist
            if o.is_owner(request.user):
                return view_func(request, *args, **kwargs)
            else:
                raise PermissionDenied
        return wraps(view_func)(wrap)
    return decorator

The view:

@login_required
@is_owner_permission_required(Comment)
def edit_comment(request, pk):
    ...

Urls:

url(r'^comment/(?P<pk>\d+)/edit/$', 'edit_comment'),

The model:

class Comment(models.Model):
    user = models.ForeignKey(User, ...
    <...>
    def is_owner(self, user):
        return self.user == user

Any feedback or remarks are appreciated.

Paul Bormans