Skip to content

Instantly share code, notes, and snippets.

@surenkov
Last active May 2, 2023 12:43
Show Gist options
  • Save surenkov/d1eba07967f4f4905a305d23c0349361 to your computer and use it in GitHub Desktop.
Save surenkov/d1eba07967f4f4905a305d23c0349361 to your computer and use it in GitHub Desktop.
Django class-based view caching mixing with granular cache-control
import functools
import typing as t
from datetime import timedelta
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_cookie, vary_on_headers
from django.views.generic import View
__all__ = (
"CachedView",
"IsAuthenticatedCache",
"VaryOnCookie",
"VaryOnHeaders",
)
ResultT = t.TypeVar("ResultT")
FuncT = t.Callable[..., ResultT]
DecoratorT = t.Callable[[FuncT], FuncT]
class CachedView(View):
cache_alias: t.Optional[str] = None
cache_key_prefix: t.Optional[str] = None
cache_timeout: t.Optional[t.Union[int, timedelta]] = None
def __getattribute__(self, attr_name):
attr = super().__getattribute__(attr_name)
http_methods = super().__getattribute__("http_method_names")
if attr_name.lower() in http_methods:
get_cached_handler = super().__getattribute__("get_cached_handler")
return get_cached_handler(attr)
return attr
def get_cached_handler(self, handler: FuncT) -> FuncT:
def decorate(method, decorator):
return decorator(method)
def build_cached_handler(*args, **kwargs):
cache_decorator = self.get_cache_decorator(*args, **kwargs)
vary_decorators = self.get_vary_decorators(*args, **kwargs)
all_decorators = [cache_decorator, *vary_decorators]
return functools.reduce(decorate, reversed(all_decorators), handler)
def decorated_handler(*args, **kwargs):
if not self.should_cache_response(*args, **kwargs):
return handler(*args, **kwargs)
cached_handler = build_cached_handler(*args, **kwargs)
return cached_handler(*args, **kwargs)
return functools.update_wrapper(decorated_handler, handler)
def should_cache_response(self, request, *args, **kwargs):
return True
def get_cache_decorator(self, *args, **kwargs) -> DecoratorT:
timeout = self.get_cache_timeout(*args, **kwargs)
prefix = self.get_cache_key_prefix(*args, **kwargs)
cache_alias = self.get_cache_alias(*args, **kwargs)
return cache_page(timeout, cache=cache_alias, key_prefix=prefix)
def get_vary_decorators(self, *args, **kwargs) -> t.Iterable[DecoratorT]:
return ()
def get_cache_alias(self, request, *args, **kwargs) -> t.Optional[str]:
return self.cache_alias
def get_cache_key_prefix(self, request, *args, **kwargs) -> t.Optional[str]:
return self.cache_key_prefix
def get_cache_timeout(self, request, *args, **kwargs) -> t.Optional[int]:
timeout = self.cache_timeout
if isinstance(timeout, timedelta):
return int(timeout.total_seconds())
return timeout
class IsAuthenticatedCache(CachedView):
def should_cache_response(self, request, *args, **kwargs):
should_cache = super().should_cache_response(request, *args, **kwargs)
return should_cache and request.user.is_authenticated
class VaryOnCookie(CachedView):
def get_vary_decorators(self, *args, **kwargs) -> t.Iterable[DecoratorT]:
yield vary_on_cookie
yield from super().get_vary_decorators(*args, **kwargs)
class VaryOnHeaders(CachedView):
vary_on_headers: t.ClassVar[t.Collection[str]] = ()
def get_vary_decorators(self, *args, **kwargs) -> t.Iterable[DecoratorT]:
yield vary_on_headers(*self.vary_on_headers)
yield from super().get_vary_decorators(*args, **kwargs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment