Search code examples
djangodjango-authentication

Why does `user.has_perm(Model, obj)` return False although `user.has_perm(Model)` returns True?


In a Django project and application just created with django-admin startproject and ./manage.py startapp, I've created this model:

class Book(models.Model):
    author = models.CharField(max_length=50)

Then I run this code with ./manage.py shell:

from django.contrib.auth.models import Permission, User
from django.test import TestCase
from myapp.models import Book

myuser = User.objects.create_user(username="myuser")
myuser.user_permissions.add(Permission.objects.get(codename="change_book"))

mybook = Book(author="Joe Author")
mybook.save()

myuser.has_perm("myapp.change_book"))  # The result is True
myuser.has_perm("myapp.change_book", mybook))  # The result is False

Why is this? The user does have permission to edit mybook, doesn't he? How is has_perm() supposed to work? Is this documented somewhere?


Solution

  • The has_perm() API is designed to work with both model-level permissions (second parameter is None) and object-level permissions. It's up to individual authentication backends, however, to determine what to support.

    In the case of Django's default ModelBackend there is no support for object-level 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.

    This is also noted in the ModelBackend documentation.

    Note that the backend must return False here since the results from individual backends are, in essence, OR'ed together. If this backend returned True it wouldn't be possible to respect the finer-grained results from other backends.

    And there are backends that implement object-level permissions, django-guardian being perhaps the best known. See how it documents has_perm():

    Main difference between Django's ModelBackend is that we can pass obj instance here.