dfunckt / django-rules

Awesome Django authorization, without the database

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Please consider using user._group_names_cache again

afinsterb opened this issue · comments

Hi,

You've removed using the group_names_cache here because of #43.

As far as I can tell, that cache was only valid for the lifetime of one request and saves many duplicate DB queries. It wasn't 'undesired', in fact it was very desirable, imo. I have multiple {% has_perm %} on most pages, all based on group membership. Another example: I have a list of objects which many groups can view, but one part of determining whether an 'edit' link should be displayed for an object from that list is based on group membership.
What used to be 9 queries for this page is now over 60, all of them are fetching the group names for the same user again and again.

I'm not exactly sure what ljsjl's use case was, but I think that changing group membership and then checking it again for that same user within one single request is not the typical use case. Even the Django docs regarding Permission Caching say that it's fine to cache them by default.

If you decide against using the cache by default again, what would be the best way forward for people who want to use it? Right now I've basically copied the predefined is_group_member predicate and modified it. Is that the best solution? Or would you be willing to add another predicate to your package, something like cached_is_group_member? Or change the signature to

def is_group_member(*groups, cache=False):
    ...
    @predicate(name)
    def fn(user):
        if cache:
            # Use cache.
        else:
            # Refetch them every time.

and use it like this is_admin = is_group_member('admin', cache=True)? Not a big fan of the last one, but it would work.

I tend to agree with you now that I see how Django handles permission caching -- and that's probably why I added caching in the first place, though I can't really remember my rationale!

I'll leave this open for a few days in case people want to chime in arguing for or against your proposal to bring caching back.

Also cc'ing @ljsjl to bring this to your attention in case you depend on the new behaviour.

commented

Yeah I had been reviewing my request in the light of further research, didn't quite think it through enough first time around, and on my todo for this week was to suggest you revert the change.

Cheers

You want to cache to prevent multiple queries, but you need to properly invalidate the cache to ensure it doesn't miss an update. Normally signals work well for this sort of thing.

@audiolion I respectfully disagree. Would it be an improvement? Absolutely. But for this use case you don't need to invalidate the cache. That's the whole point of this ticket, that you can (pretty) safely cache the group names for one single request. It's not stored in a memcached instance somewhere.

Btw, and I admit I haven't fully thought it through yet, but from the looks of it, it might be quite hard to clear that cache without resorting to some weird trickery. The signal, m2m_changed 'post_add' or whatever, is getting a user model instance. The _group_names_cache however can be attached to request.user, because this is what the predicate is being passed and checking against when I only care about what to show based on the current user and doing {% has_perm 'books.add_book' request.user as can_add_book %}. So... ¯\_(ツ)_/¯

@afinsterb you are correct, I had only glanced at this before and I thought he was setting the cache beyond one request/response cycle.

I agree with you though that this change should be reversed, Django caches permissions in the same way, the issue the guy in #43 ran into is the same issue you would find if you tried updating permissions inside a request/response cycle and the unit test would fail, the developer in the unit test needs to just make an additional get() request for the object to bust the cache and get the updated values for the unit test to pass.

@dfunckt Just a friendly ping.

Any news here? Is there anything I can do to help move this along?

My guess is that it's as simple as reverting 5c58706 in a new PR. Bonus points for adding a test (maybe related to number of DB queries, comment linking to this issue).

I just reverted the previous commit and released 1.2.1. Apologies for the delay.