Handle permission cache in django user model
Asked Answered
P

2

8

I stumbled upon a weird behaviour: I add a permission to a user object but the permission check fails.

permission = Permission.objects.get_by_natural_key(app_label='myapp', codename='my_codename', model='mymodel')
user.user_permissions.add(permission)

user.has_permission('myapp.my_codename') # this is False!

I found some posts about user permission caching here and here and the solution seems to be to completely reload the object from the database.

# Request new instance of User
user = get_object_or_404(pk=user_id)

# Now note how the permissions have been updated
user.has_perms('myapp.my_codename') # now it's True

This seems really overkill for me and very un-django-like. Is there really no way to either clear the permission cache oder reload the foreign keys like you can do for an object with refresh_from_db()?

Thanks in advance!
Ronny

Parity answered 19/8, 2021 at 7:9 Comment(4)
You can delete the cache of the user object for the perms, to force recalculation of the permissions: del user._perm_cache; del user._user_perm_cache. Or you can write your own model backend that doesn't cache the perms, but you probably don't want that. IMO I think reloading is more readable/maintainableChittagong
@bdbd You should write this as an answer, I'll test it and if it works, I'll accept it.Parity
Since you've shared that the context for this need is unit test, can you share those? There might be a better solution depending on how the tests are writtenChittagong
There is not much to show. Fetch a permission, assign permission to test user and call methoden and assert result. But if it helps, I can post it.Parity
C
7

You can force the recalculation by deleting the user object's _perm_cache and _user_perm_cache.

permission = Permission.objects.get_by_natural_key(app_label='myapp', codename='my_codename', model='mymodel')
user.user_permissions.add(permission)
user.has_permission('myapp.my_codename') # returns False

del user._perm_cache
del user._user_perm_cache
user.has_permission('myapp.my_codename') # should be True

But this will essentially hit the database again to fetch the updated permissions. Since these are based on internal workings in django and not part of the public API, these cache keys might change in the future, so I would still suggest to just fetch the user again. But that's totally up to you.

Chittagong answered 23/8, 2021 at 6:39 Comment(1)
I had to do del user._group_perm_cache too because I was adding a group permissionStroman
S
2

The caching is only an issue for you because of the has_perms method. If you follow it all the way down the stack, you eventually end up here. You'll see this method explicitly checking the cache before proceeding.

If you really need to know this user's permissions at this point in time and really don't want to refresh from the DB, then you can check more manually/directly without the has_perm helper method, since this permission .add() is a standard m2m operation and the model field has been updated.

In practice, it's unlikely you'll check a permission right after it is added, while the object is in scope, and while the permissions are cached since it's a bit redundant. I'm sure the Django devs considered this, and decided the benefits of caching it for you by default was the right call.

Spermophyte answered 19/8, 2021 at 16:6 Comment(1)
Thanks for your reply! Actually I have the problem in unit-tests where I add a permission for test case a and want directly afterwards if the to-be-tested method reacts accordingly.Parity

© 2022 - 2024 — McMap. All rights reserved.