row level permissions in django
Asked Answered
C

5

22

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

Complacency answered 9/8, 2012 at 20:40 Comment(0)
K
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

Kayseri answered 2/12, 2012 at 17:48 Comment(0)
F
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.

Frederiksberg answered 9/8, 2012 at 20:47 Comment(0)
C
7

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
Corollary answered 9/8, 2012 at 21:10 Comment(2)
It should be noted that there's a bug in Django (#11383) which prevents the check for object level permissions when the "delete selected" admin action is run.Cordelier
Do you know how to do the same thing with admin.TabularInline?Tholos
A
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.

Apprehensible answered 30/9, 2013 at 18:26 Comment(0)
S
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.

Shulem answered 9/8, 2012 at 20:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.