我想缓存我的 API 响应并将其与 GET 请求中存在的参数绑定。 请求如下所示:
GET /products?producent=some_company
这是我的简化课程:
class ProductsListCreate(generics.ListCreateAPIView):
def list(self, request, *args, **kwargs):
producent = request.query_params.get("producent")
cached_response = cache.get(f"response-products-{producent}", None)
if not cached_response:
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
serializer = self.get_serializer(page)
response = self.get_paginated_response(serializer.data)
cache.set(f"response-products-{producent}", response , timeout=20)
return response
return cached_response
但是当我尝试缓存响应时,我收到错误:
django.template.response.ContentNotRenderedError: The response content must be rendered before it can be pickled.
你有什么建议给我吗?当我试图弄清楚这一点时,我在这里搜索https://docs.djangoproject.com/en/3.2/topics/cache/。起初我尝试了使用
@cache_page
的方法,但它不允许我使用请求中的参数,所以我想要走的路是低级缓存 API。
错误消息实际上非常具有描述性。在尝试将其 pickle 为可缓存格式之前,您必须对响应调用
render()
。问题是:这将导致您陷入解构响应并从缓存数据重建的困境。 IMO,你最好缓存查询集。尽管如此,这就是它的样子:
class ProductsListCreate(generics.ListCreateAPIView):
def list(self, request, *args, **kwargs):
producent = request.query_params.get("producent")
response_triple = cache.get(f"response-products-{producent}", None)
if not response_triple:
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
serializer = self.get_serializer(page)
response = self.get_paginated_response(serializer.data)
response.render()
if not response.status_code >= 400:
# django 3.0 has no .items() method, django 3.2 has no ._headers
if hasattr(response, '_headers'):
headers = response._headers.copy()
else:
headers = {k: (k, v) for k, v in response.items()}
response_triple = (
response.rendered_content,
response.status_code,
headers
)
cache.set(f"response-products-{producent}", response_triple, timeout=20)
else:
# build smaller Django HttpResponse
content, status, headers = response_triple
response = HttpResponse(content=content, status=status)
for k, v in headers.values():
response[k] = v
if not hasattr(response, '_closable_objects'):
response._closable_objects = []
return response
您可以在基于类的视图上使用此自定义函数:
def generate_cache_key(key_prefix, request):
""" Generate cache key """
query_params = request.GET.dict()
query_string = urlencode(sorted(query_params.items()))
base_key = f"{key_prefix}:{request.path}:{query_string}"
return hashlib.md5(base_key.encode('utf-8')).hexdigest()
def cache_page_with_query_params(timeout, key_prefix=''):
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
# Generate the custom cache key
cache_key = generate_cache_key(key_prefix, request)
# Try to get the cached response
cached_response = cache.get(cache_key)
if cached_response:
return JsonResponse(cached_response)
# Call the view function to get the response
response = view_func(request, *args, **kwargs)
data = response.data
# Cache the response
if response.status_code == 200:
cache.set(cache_key, data, timeout)
return JsonResponse(data)
return _wrapped_view
return decorator
现在您可以像这样使用它:
@method_decorator(cache_page_with_query_params(60 * 15, key_prefix="add_your_prefix"))
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)