Frage

I'm using django-guardian in order to manage per object permission.

For a given user I give permission all permission on one object:

joe = User.objects.get(username="joe")

mytask = Task.objects.get(pk=1)

assign('add_task', joe, mytask)
assign('change_task', joe, mytask)
assign('delete_task', joe, mytask)

and I get, as expected:

In [57]: joe.has_perm("add_task", mytask)
Out[57]: True

In [58]: joe.has_perm("change_task", mytask)
Out[58]: True

In [59]: joe.has_perm("delete_task", mytask)
Out[59]: True

In admin.py I also make TaskAdmin inherit from GuardedModelAdmin instead of admin.ModelAdmin

Now when I connect to my site with joe, on the admin I get:

You don't have permission to edit anything 

Am I not supposed to be able to edit the object mytask?

Do I have to set some permissions using the built-in model-based permission system?

Am I missing anything?

EDIT

I tried to add the option user_can_access_owned_objects_only, which is supposed to deal with my issue, but I still can't see anything in my admin...

class TaskAdmin(GuardedModelAdmin):

    user_can_access_owned_objects_only = True

    pass

admin.site.register(Task, TaskAdmin)

Thank you

War es hilfreich?

Lösung

In order to only see the instances owned by the current user, I give him all permission

add_task=Permission.objects.get(codename="add_task")
change_task=Permission.objects.get(codename="change_task")
delete_task=Permission.objects.get(codename="delete_task")

joe.user_permissions.add(add_task)
joe.user_permissions.add(change_task)
joe.user_permissions.add(delete_task)

then I set the permission on a few instances using guardian.shortcuts.assign, and I filter the queryset in the admin:

class TaskAdmin(admin.ModelAdmin):

    def queryset(self, request):
            if request.user.is_superuser:
                return super(TaskAdmin, self).queryset(request)
            return get_objects_for_user(user=request.user, perms=['add_task', 'change_task', 'delete_task'], klass=Task)

It's far from perfect, but I can't find any other solution.

Andere Tipps

The Django Admin, especially it's dealing w/ change permission, is a bit coarse-grained. The internal method ModelAdmin.has_change_permission() covers the checking of view permission which is lacked actually.

Second, the GuardedModelAdmin brings ownership-checking (through user_can_access_owned_objects_only) and forms for managing row-level permissions. It does not provide any other row-level accessing policy to the Django Admin.

For the typical row-level permission scene in the Django Admin, I would like to suggest the code, here I've introduced an optional 'view' permission:

class ExtendedGuardedModelAdmin(GuardedModelAdmin):
    def queryset(self, request):
        qs = super(ExtendedGuardedModelAdmin, self).queryset(request)
        # Check global permission
        if super(ExtendedGuardedModelAdmin, self).has_change_permission(request) \
            or (not self.list_editable and self.has_view_permission(request)):
                return qs
        # No global, filter by row-level permissions. also use view permission if the changelist is not editable
        if self.list_editable:
            return get_objects_for_user(request.user, [self.opts.get_change_permission()], qs)
        else:
            return get_objects_for_user(request.user, [self.opts.get_change_permission(), self.get_view_permission(
)], qs, any_perm=True)

    def has_change_permission(self, request, obj=None):
        if super(ExtendedGuardedModelAdmin, self).has_change_permission(request, obj):
            return True
        if obj is None:
            # Here check global 'view' permission or if there is any changeable items
            return self.has_view_permission(request) or self.queryset(request).exists()
        else:
            # Row-level checking
            return request.user.has_perm(self.opts.get_change_permission(), obj)

    def get_view_permission(self):
        return 'view_%s' % self.opts.object_name.lower()

    def has_view_permission(self, request, obj=None):
        return request.user.has_perm(self.opts.app_label + '.' + self.get_view_permission(), obj)

    def has_delete_permission(self, request, obj=None):
        return super(ExtendedGuardedModelAdmin, self).has_delete_permission(request, obj) \
                or (obj is not None and request.user.has_perm(self.opts.get_delete_permission(), obj))

In this way, you could achieve more-flexible permission checking, user-permissions now are global, user-obj-permissions are row-level based:

  • joe.user_permissions.add(add_task)
    joe could add new tasks (there is no row-level 'add' permission)
  • joe.user_permissions.add(change_task)
    joe could change ALL of the tasks
  • joe.user_permissions.add(delete_task)
    joe could delete ALL of the tasks
  • assign(Task._meta.get_change_permission(), joe, obj)
    joe could change the Task obj, see the changelist containing the obj as well as other changeable tasks.
  • assign(Task._meta.get_delete_permission(), joe, obj)
    joe could delete the Task obj
  • assign('view_task', joe, obj)
    [optional] joe could view the Task obj(you might want to check this permission at customized Admin view page)
  • joe.user_permissions.add(Permission.objects.get(codename='view_task', ...))
    [optional] joe could view All the tasks in the changelist, as long as the changelist is not inline-editable. This is useful if joe could pick up items from raw_id_fields w/o having change permission.
  • ...

You could ignore 'view' permission safely if it's useless for you.

Currently, django.contrib.admin.util.get_deleted_objects does not honor the obj during the checking of permission, if you need to check row-level permission during delete, patch the get_deleted_objects by modifying the if not user.has_perm(p): line to if not user.has_perm(p, obj):. The relative tickets are 13539 & 16862

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top