Objects with permissions assigned by django-guardian not visible in admin
-
13-06-2021 - |
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
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 tasksjoe.user_permissions.add(delete_task)
joe could delete ALL of the tasksassign(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 objassign('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