[Django]-Row level permissions in django

27đź‘Ť

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

👤Paul Bormans

7đź‘Ť

The plumbing is there (this is from the bottom of the same page you linked):

Handling object permissions

Django’s permission framework has a
foundation for object permissions, though there is no implementation
for it in the core. That means that checking for object permissions
will always return False or an empty list (depending on the check
performed). An authentication backend will receive the keyword
parameters obj and user_obj for each object related authorization
method and can return the object level permission as appropriate.

But no default implementation is provided. Since this is a common topic; there are lots of answers on SO. Check to the right and you’ll see some listed.

The basis idea is to browse the django packages’ perm grid and pick an implementation of object level permissions. I personally like django-guardian.

👤Burhan Khalid

6đź‘Ť

The methods that the docs talk about will allow you to restrict access to particular objects in the admin. Each method is passed the object in play, which you can use to make determinations about whether a user can access it, by returning either True or False.

class MyModelAdmin(admin.ModelAdmin):
    ...
    def has_add_permission(self, request):
        # This one doesn't get an object to play with, because there is no
        # object yet, but you can still do things like:
        return request.user.is_superuser
        # This will allow only superusers to add new objects of this type

    def has_change_permission(self, request, obj=None):
        # Here you have the object, but this is only really useful if it has
        # ownership info on it, such as a `user` FK
        if obj is not None:
            return request.user.is_superuser or \
                   obj.user == request.user
            # Now only the "owner" or a superuser will be able to edit this object
        else:
            # obj == None when you're on the changelist page, so returning `False`
            # here will make the changelist page not even viewable, as a result,
            # you'd want to do something like:
            return request.user.is_superuser or \
                   self.model._default_manager.filter(user=request.user).exists()
            # Then, users must "own" *something* or be a superuser or they
            # can't see the changelist

    def has_delete_permission(self, request, obj=None):
        # This pretty much works the same as `has_change_permission` only
        # the obj == None condition here affects the ability to use the
        # "delete selected" action on the changelist
👤Chris Pratt

1đź‘Ť

I have rolled-out a solution to this kind of problem using Django Class Based Views.

Check out my article Django Generic Class Based Views with Object-Level Permissions Checking.

👤Darwin M.

0đź‘Ť

There are a large number of “permissions” apps for django available on PyPi
For example you could look at django-object-permission.

What the documentation is referring to is that the functionality is there to implement the permissions. And people have done just that by creating apps for this.

👤jdi

Leave a comment