DRF (Django) 提供了工具直接对 视图方法 进行缓存。
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
class ModuleViewset(ModelViewSet):
@method_decorator(cache_page(60 * 60 * 2)) # 单位:秒
def list(self, request: Request, *args, **kwargs):
return super().list(request, *args, **kwargs)
需要启用缓存插件:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
}
}
method_decorator
method_decorator
将 函数装饰器 转换成 方法装饰器。
用来装饰 视图函数的请求处理方法 时,既可以放在请求处理方法上,也可以放在视图类上。
放在请求处理方法上:
class HomeView(View):
@method_decorator(check_login)
def get(self, request):
return render(request, "home.html")
放在视图类上(此时需要传递额外的 name
关键字参数):
@method_decorator(check_login, name="get")
class HomeView(View):
def get(self, request):
return render(request, "home.html")
cache_page
cache_page
用于缓存视图函数(或者视图类的请求处理方法)。
该函数以视图 URL 以及请求头中的部分数据作为缓存键。
多站点应用中还可以指定缓存键前缀用以区分不同站点的缓存。
from django.middleware.cache import CacheMiddleware
from django.utils.decorators import decorator_from_middleware_with_args
def cache_page(timeout, *, cache=None, key_prefix=None):
"""
Decorator for views that tries getting the page from the cache and
populates the cache if the page isn't in the cache yet.
The cache is keyed by the URL and some data from the headers.
Additionally there is the key prefix that is used to distinguish different
cache areas in a multi-site setup. You could use the
get_current_site().domain, for example, as that is unique across a Django
project.
Additionally, all headers from the response's Vary header will be taken
into account on caching -- just like the middleware does.
"""
return decorator_from_middleware_with_args(CacheMiddleware)(
page_timeout=timeout,
cache_alias=cache,
key_prefix=key_prefix,
)
这里用到了两个导入:
- CacheMiddleware: 缓存中间件
- decorator_from_middleware_with_args: 传递参数,构造中间件的装饰器
缓存中间件 CacheMiddleware
CacheMiddleware 的定义如下:
class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware):
def __init__(self, get_response, cache_timeout=None, page_timeout=None, key_prefix="", cache_alias="default"): None
/
中间件如何通过装饰器修改视图函数
decorator_from_middleware_with_args 的定义如下:
def make_middleware_decorator(middleware_class): # middleware_class <= CacheMiddleware
def _make_decorator(*m_args, **m_kwargs): # m_kwargs <= {page_timeout, cache_alias, key_prefix}
...
return _make_decorator
故而 decorator_from_middleware_with_args(CacheMiddleware)
返回的是一个构造装饰器的函数 _make_decorator
。
def make_middleware_decorator(middleware_class):
def _make_decorator(*m_args, **m_kwargs): # m_kwargs <= {page_timeout, cache_alias, key_prefix}
def _decorator(view_func):
...
return _decorator
return _make_decorator
调用 _make_decorator(page_timeout=timeout,cache_alias=cache,key_prefix=key_prefix)
将返回一个装饰器函数 _decorator
。
def make_middleware_decorator(middleware_class):
def _make_decorator(*m_args, **m_kwargs):
def _decorator(view_func):
middleware = middleware_class(view_func, *m_args, **m_kwargs)
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
...
return _wrapped_view
return _decorator
return _make_decorator
_decorator
内部干了啥呢?首先实例化缓存中间件:
# middleware = CacheMiddleware(view_func, *m_args, **m_kwargs)
middleware = middleware_class(view_func, page_timeout=timeout, cache_alias=cache, key_prefix=key_prefix)
中间件的洋葱模型处理方式
然后就是具体处理视图响应数据的逻辑(_wrapped_view
):
# _wrapped_view 负责对视图处理函数 view_func 进行再包装,同时通过 @wraps 将其伪装成原始视图函数
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs): # 这个形参与视图函数是一样的
# process_request: 缓存中间件对请求的拦截
if hasattr(middleware, "process_request"):
result = middleware.process_request(request)
if result is not None:
return result
# process_view: 缓存中间件对视图的拦截(请求需要进入视图函数才能返回响应)
if hasattr(middleware, "process_view"):
result = middleware.process_view(request, view_func, args, kwargs)
if result is not None:
return result
# view_func: 请求进入视图函数返回响应
try:
response = view_func(request, *args, **kwargs)
except Exception as e:
if hasattr(middleware, "process_exception"):
result = middleware.process_exception(request, e)
if result is not None:
return result
raise
# process_response: 缓存中间件对响应的拦截
if hasattr(response, "render") and callable(response.render):
if hasattr(middleware, "process_template_response"):
response = middleware.process_template_response(
request, response
)
# Defer running of process_response until after the template
# has been rendered:
if hasattr(middleware, "process_response"):
def callback(response):
return middleware.process_response(request, response)
response.add_post_render_callback(callback)
else:
if hasattr(middleware, "process_response"):
return middleware.process_response(request, response)
return response
cache_page 装饰视图函数
调用 cache_page
将返回一个 _decorator
函数,后者以原始视图函数为参数,返回一个施加了中间件作用的新的视图函数。
结合一个 cache_page
的使用例子:
# 下面这个带括号,会被直接调用,它返回一个以视图函数为参数,又返回一个新的视图函数的函数(_decorator)
# 原始函数就是这个 _decorator 的输入参数,处理请求的真实视图函数就是 _decorator 返回的函数
@cache_page(60) # 单位:秒
def my_view(request): ...
cache_page 装饰视图方法
DRF 中的视图函数多以视图类的方法的形式存在,cache_page
用于视图类的方法时需要先转换为 方法装饰器:
class ModuleViewset(ModelViewSet):
@method_decorator(cache_page(60 * 60 * 2))
def list(self, request: Request, *args, **kwargs):
return super().list(request, *args, **kwargs)
vary_on_cookie
当请求具有不同的 Cookie 值时,Django 会为每个不同的 Cookie 值缓存不同的响应。
from django.views.decorators.vary import vary_on_cookie
from django.http import HttpResponse
@vary_on_cookie
def my_view(request):
# 基于Cookie的一些处理逻辑
# ...
response = HttpResponse("Hello, World!")
return response
结合 DRF 使用:
class ModuleViewset(ModelViewSet):
@method_decorator(cache_page(60 * 60 * 2))
@method_decorator(vary_on_cookie)
def list(self, request: Request, *args, **kwargs):
return super().list(request, *args, **kwargs)
评论区