170 lines
6.5 KiB
Python
170 lines
6.5 KiB
Python
from django.db.models.signals import post_save, post_delete
|
|
from django.utils.decorators import decorator_from_middleware_with_args
|
|
|
|
from groupcache.middleware import CacheWithLocalVersionMiddleware
|
|
from groupcache.core import get_view_name, \
|
|
get_local_key_mapping, get_local_key_from_mapping, bump_local_prefix
|
|
|
|
import inspect, functools, logging
|
|
|
|
#-------------------------------------------------------------------------------
|
|
def to_fields(*local_keywords):
|
|
'''
|
|
Decorator to simplify introspection of to_fields callables, used by
|
|
cache_page_for_model below:
|
|
|
|
@to_fields("square", "inverse")
|
|
def compute_fields(value = None):
|
|
value = int(value)
|
|
return {"square": value*value, "inverse": -value}
|
|
|
|
>>> print compute_fields.local_keywords
|
|
("square", "inverse")
|
|
'''
|
|
def to_fields_decorator(func):
|
|
@functools.wraps(func)
|
|
def wrapper(*args, **kwargs):
|
|
return func(*args, **kwargs)
|
|
wrapper.local_keywords = local_keywords
|
|
return wrapper
|
|
return to_fields_decorator
|
|
|
|
#-------------------------------------------------------------------------------
|
|
def cache_versionned_page(group = None, cache_timeout = None,
|
|
vary_on_view = True):
|
|
'''
|
|
This is the most generic decorator in groupcache. Use it to
|
|
implement whatever versionning policy you wish to have.
|
|
'''
|
|
def cache_versionned_page_decorator(view_func):
|
|
return decorator_from_middleware_with_args(
|
|
CacheWithLocalVersionMiddleware)(
|
|
cache_timeout = cache_timeout,
|
|
group = group,
|
|
view_func = view_func,
|
|
vary_on_view = True)(view_func)
|
|
return cache_versionned_page_decorator
|
|
|
|
def cache_tagged_page(tag, cache_timeout = None):
|
|
'''
|
|
Decorator for extremely basic caching based on string labeling.
|
|
|
|
from groupcache.decorators import cache_tagged_page
|
|
|
|
@cache_tagged_page('some tag')
|
|
def my_view(request, ...):
|
|
...
|
|
|
|
Then, you can do from anywhere:
|
|
|
|
from groupcache.utils import uncache_tag
|
|
uncache_tag('some tag')
|
|
|
|
An every cached page marked with the tag will get invalidated.
|
|
'''
|
|
assert(isinstance(tag, basestring))
|
|
def cache_tagged_page_decorator(view_func):
|
|
return decorator_from_middleware_with_args(
|
|
CacheWithLocalVersionMiddleware)(
|
|
cache_timeout = cache_timeout,
|
|
group = tag,
|
|
view_func = view_func,
|
|
vary_on_view = False)(view_func)
|
|
return cache_tagged_page_decorator
|
|
|
|
#-------------------------------------------------------------------------------
|
|
def cache_page_against_model(model, cache_timeout = None, to_fields = None):
|
|
'''
|
|
If you got some one-to-one relationship between a view and a model,
|
|
you can apply this decorator to handle automatic caching invalidation
|
|
when the entity used in a given call to the view changes.
|
|
|
|
In the most common scenario, it\'s used like this:
|
|
|
|
from django.http.shortcuts import get_object_or_404
|
|
from groupcache.decorators import cache_page_for_model
|
|
|
|
@cache_page_for_model(MyModel):
|
|
def my_view(request, pk):
|
|
obj = get_object_or_404(MyModel, pk=pk)
|
|
...
|
|
|
|
This functionnality is the reason django-groupcache was written in
|
|
the first place.
|
|
'''
|
|
def cache_page_against_model_decorator(func):
|
|
# Retrieve django-style view name from view func
|
|
view_name = get_view_name(func)
|
|
|
|
# Map the view keywords to model fields using the to_fields group
|
|
if callable(to_fields):
|
|
raise ValueError(
|
|
'to_fields is a specialized group that cannot be a callable')
|
|
|
|
if to_fields is None:
|
|
# If to_fields is not given, we have no choice but getting the
|
|
# keywords by inspecting the view. Convenient, but less than
|
|
# ideal, as it means that *args, **kwargs style views signature
|
|
# will not get properly detected -- this is what might happens
|
|
# with views having cascading decorators.
|
|
modelfields = inspect.getargspec(func)[0][1:]
|
|
else:
|
|
# Other sequences and mappings only needs to be saved, as it's
|
|
# assumed there is a perfect relationship to model fields.
|
|
modelfields = list(to_fields)
|
|
|
|
if len(modelfields) == 0:
|
|
raise ValueError(
|
|
'there is no field left to match model entities against: '
|
|
'if all you are interested in is invalidating all pages '
|
|
'associated with a given view whenever an entity '
|
|
'from a given model is changed, use '
|
|
'the cache_page_for_models() decorator instead')
|
|
|
|
def invalidate(sender, instance, signal, *args, **kwargs):
|
|
# Construct the mapping from model instance
|
|
mapping = dict((field, getattr(instance, field))
|
|
for field in modelfields)
|
|
|
|
# Get the corresponding local key, and bump it.
|
|
local_key = get_local_key_from_mapping(mapping, view_name)
|
|
print 'local key', local_key
|
|
bump_local_prefix(local_key)
|
|
print 'Invalidating:', instance, signal, args, kwargs
|
|
|
|
post_save.connect(invalidate, sender = model, weak = False,
|
|
dispatch_uid = view_name)
|
|
|
|
return decorator_from_middleware_with_args(
|
|
CacheWithLocalVersionMiddleware)(
|
|
cache_timeout = cache_timeout,
|
|
group = to_fields,
|
|
view_func = func,
|
|
vary_on_view = True)(func)
|
|
|
|
return cache_page_against_model_decorator
|
|
|
|
#-------------------------------------------------------------------------------
|
|
def cache_page_against_models(*models, **kwargs):
|
|
def cache_page_against_models_decorator(func):
|
|
view_name = get_view_name(func)
|
|
|
|
def invalidate(sender, instance, signal, *args, **kwargs):
|
|
bump_local_prefix(get_local_key_from_mapping({}, view_name))
|
|
|
|
for model in models:
|
|
for sig in (post_save, post_delete):
|
|
sig.connect(invalidate, sender = model, weak = False,
|
|
dispatch_uid = view_name)
|
|
|
|
return decorator_from_middleware_with_args(
|
|
CacheWithLocalVersionMiddleware)(
|
|
cache_timeout = kwargs.get('cache_timeout'),
|
|
group = (),
|
|
view_func = func,
|
|
vary_on_view = True)(func)
|
|
|
|
return cache_page_against_models_decorator
|
|
|
|
#-------------------------------------------------------------------------------
|