diefenbach / django-lfs

An online-shop based on Django
http://www.getlfs.com
BSD 3-Clause "New" or "Revised" License
622 stars 222 forks source link

serious bug in lfs_get_object_or_404 and lfs_get_object (cache related) #46

Closed baffolobill closed 7 years ago

baffolobill commented 11 years ago

Lets assume I have next model:

class MyModel(...):
    slug = SlugField()
    active = BooleanField()

and I will do next:

def my_view(request, slug):
    obj = lfs_get_object_or_404(MyModel, slug=slug, active=True)

If a cache is disabled, nothing bad, or if only the slug is used to filter, otherwise, regardless of the value slug, lfs_get_object_or_404 will return the same value - the object to which the first time has been accessed, because the cache_key in this case always will be prefix-mymodel-True.

Sorry, but I'm too lazy to do a pull request, but here is my solution:

# lfs/caching/utils.py
def lfs_get_object_or_404(klass, *args, **kwargs):
    ...
    ck_kwargs = '-'.join(['%s|%s'%kw for kw in kwargs.items()]).lower()
    cache_key = "%s-%s-%s" % (settings.CACHE_MIDDLEWARE_KEY_PREFIX, klass.__name__.lower(), ck_kwargs)
    ...
yks-uno commented 11 years ago

In my opinion, the LFS cache framework needs refactoring. I have several propositions including:

baffolobill commented 11 years ago

yks-uno, could you share your cache related code?

yks-uno commented 11 years ago

Sorry for the delay,

I actually implemented caching as a Manager subclass which can be added to any model that could need caching. The basic cache lookup by instance's PK can be done by the manager's get() or by a new get_cached() method (both have some advantages and disadvantages) This also stores the fetched instance to cache (if not already there). Caching of complex querysets requires apparently separate methods for each queryset, the cache keys are dynamically generated from the parameters and stored in the Manager class to be deleted on a modification signal. On model instance modification, Django emits signals which are listened to and forwarded to the model's manager which then deletes from cache the instance keys and the stored complex queryset keys.

My rationale was, all caching code must be located in one place, cache should be easily disabled and managed, and the place is obviously the Manager class which is responsible for fetching/deleting objects. But this implementation requires changes to a good half of LFS so I'm unsure if this can be accepted. There's no problem for me to share the code if someone likes, though it's nothing novel.

Note on these pesky CACHE_MIDDLEWARE_KEY_PREFIX: As far as I can tell, the Cache key prefixing appeared in Django 1.3, so in old installations it is sort of required to use them in every cache call. Since 1.3, they are added automatically. But in any case, moving all cache-related code into one location would be a solution for both getting rid of CACHE_MIDDLEWARE_KEY_PREFIX abuse and making the code overall more pleasant and modular.

On 20.12.2012 17:23, baffolobill wrote:

yks-uno, could you share your cache related code?

— Reply to this email directly or view it on GitHub https://github.com/diefenbach/django-lfs/issues/46#issuecomment-11573179.

Best Regards, Yuri aka Y.K.S.