sosa5001
sosa5001

Reputation: 53

Understanding Model Permissions in Django 3.1

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

Could someone explain to me why this behavior is expected and how to properperly implement checking for permissions?

Upvotes: 3

Views: 287

Answers (1)

RedmanFromMA
RedmanFromMA

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

Related Questions