Last active
May 2, 2023 12:43
-
-
Save surenkov/d1eba07967f4f4905a305d23c0349361 to your computer and use it in GitHub Desktop.
Django class-based view caching mixing with granular cache-control
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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