Reputation: 53
I have some troubles understanding how Django 3.1 checks permissions, especially how model permissions versus default permissions are handled.
I have the following model:
from django.db import models
class MyModel(models.Model):
class Meta:
permissions = [("finalize", "Can finalize"),]
is_final = models.BooleanField(default = False)
which defines a new finalize
permission for this model. With the model along we have a model admin:
from django.contrib import admin
from django.contrib.auth import get_permission_codename
from .models import *
class MyModelAdmin(admin.ModelAdmin):
actions = ['set_final', 'remove_final']
def set_final(self, request, queryset):
queryset.update(is_final=True)
set_final.allowed_permissions = ('finalize',)
def remove_final(self, request, queryset):
queryset.update(is_final=False)
remove_final.allowed_permissions = ('finalize',)
# Permissions
def has_change_permission(self, request, obj = None):
if obj and obj.is_final:
return False
codename = get_permission_codename('change', self.opts)
print("has_perm(change) : %s.%s : %s" % (self.opts.app_label, 'change', request.user.has_perm('%s.%s' % (self.opts.app_label, 'change'))))
print("has_perm(codename): %s.%s : %s" % (self.opts.app_label, codename, request.user.has_perm('%s.%s' % (self.opts.app_label, codename))))
return request.user.has_perm('%s.%s' % (self.opts.app_label, codename))
def has_finalize_permission(self, request, obj = None):
codename = get_permission_codename('finalize', self.opts)
print("has_perm(finalize): %s.%s : %s" % (self.opts.app_label, 'finalize', request.user.has_perm('%s.%s' % (self.opts.app_label, 'finalize'))))
print("has_perm(codename): %s.%s : %s" % (self.opts.app_label, codename, request.user.has_perm('%s.%s' % (self.opts.app_label, codename))))
return request.user.has_perm('%s.%s' % (self.opts.app_label, 'finalize'))
admin.site.register(MyModel, MyModelAdmin)
which defines two actions: setting and removing the is_final
flag from objects. Along with it, the has_change_permission
is redefined because the object should be displayed as "view"-only if the is_final
flag is set.
Now I create a new staff-level user which receives all permissions for the model (view, add, change, finalize) via the Django admin interface. And here comes the conundrum. When logging in with that user, the above print
statements evaluate to the following:
has_perm(finalize): myapp.finalize : True
has_perm(codename): myapp.finalize_mymodel : False
has_perm(change) : myapp.change : False
has_perm(codename): myapp.change_mymodel : True
(bear in mind that the user has received all permissions on the model selectable from the list in the user admin interface). So somehow
get_permission_codename()
evaluates to false
myapp.change
evaluates to false
Could someone explain to me why this behavior is expected and how to properperly implement checking for permissions?
Upvotes: 3
Views: 287
Reputation: 26
I don't know if this is a bug in Django 3.1 or not. You are following the recommendation in the docs. But Django contrib.auth get_permission_codename() returns the permission in the form app.permission_modelname. When you set the allowed permission "finalize", it gets added into the Permission model as app.finalize. But when you use get_permission_codename(), that returns a string as app.finalize_mymodel. When the authenticator backend compares the two, obviously they do not match, and returns False. The "change" permission is different - that is built into Django (all admin views default to add, change, delete permissions), which get set as Django wants them to be. It is only user-supplied (for non-superusers) that experience this issue. I worked around this issue by not using get_permission_codename(). Instead, just pass opts.app_label and your permission str into request.user.has_perm(). Again - I don't know if this has changed in future releases or not.
Upvotes: 1