ImportError: cannot import name 'EmptyResultSet' from 'django.db.models.sql.datastructures'
最新版本的drf-extensions不兼容8.4发布的django3.1
原因django.core.exceptions.EmptyResultSet
的兼容性导入被删除:
from django.db.models.sql.datastructures import EmptyResultSet
以上位置不再能导入EmptyResultSet,而drf-extensions使用了以上位置的EmptyResultSet
需要做如下修改:
把它替换成django.core.exceptions.EmptyResultSet
即可
class KeyConstructor:
def __init__(self, memoize_for_request=None, params=None):
if memoize_for_request is None:
# 默认False
self.memoize_for_request = extensions_api_settings.DEFAULT_KEY_CONSTRUCTOR_MEMOIZE_FOR_REQUEST
else:
self.memoize_for_request = memoize_for_request
if params is None:
self.params = {}
else:
self.params = params
self.bits = self.get_bits()
# 获取设置的所有bits
def get_bits(self):
_bits = {}
for attr in dir(self.__class__):
attr_value = getattr(self.__class__, attr)
if isinstance(attr_value, bits.KeyBitBase):
_bits[attr] = attr_value
return _bits
def __call__(self, **kwargs):
return self.get_key(**kwargs)
# 获取key
def get_key(self, view_instance, view_method, request, args, kwargs):
# 可以设置把key保存在request里
if self.memoize_for_request:
memoization_key = self._get_memoization_key(
view_instance=view_instance,
view_method=view_method,
args=args,
kwargs=kwargs
)
if not hasattr(request, '_key_constructor_cache'):
request._key_constructor_cache = {}
if self.memoize_for_request and memoization_key in request._key_constructor_cache:
return request._key_constructor_cache.get(memoization_key)
else:
# md5加密后的key密文
value = self._get_key(
view_instance=view_instance,
view_method=view_method,
request=request,
args=args,
kwargs=kwargs
)
if self.memoize_for_request:
request._key_constructor_cache[memoization_key] = value
return value
def _get_memoization_key(self, view_instance, view_method, args, kwargs):
from rest_framework_extensions.utils import get_unique_method_id
return json.dumps({
'unique_method_id': get_unique_method_id(view_instance=view_instance, view_method=view_method),
'args': args,
'kwargs': kwargs,
'instance_id': id(self)
})
# 根据请求视图,生成加密key
def _get_key(self, view_instance, view_method, request, args, kwargs):
_kwargs = {
'view_instance': view_instance,
'view_method': view_method,
'request': request,
'args': args,
'kwargs': kwargs,
}
return self.prepare_key(
self.get_data_from_bits(**_kwargs)
)
# 对获取到的生成key用的数据dict 以key排序后转为md5
def prepare_key(self, key_dict):
res = hashlib.md5(json.dumps(key_dict, sort_keys=True).encode('utf-8')).hexdigest()
return res
# 从设置的bits(key规则)里,根据请求视图,获取key生成所需要的数据
# 例如{'unique_method_id': 'blog.views.PostViewSet.list'}
def get_data_from_bits(self, **kwargs):
result_dict = {}
for bit_name, bit_instance in self.bits.items():
if bit_name in self.params:
params = self.params[bit_name]
else:
try:
params = bit_instance.params
except AttributeError:
params = None
result_dict[bit_name] = bit_instance.get_data(
params=params, **kwargs)
return result_dict
以上总结:KeyConstructor定义了:
0.获取bits的方法
1.根据请求视图和设置的bits生成加密key的方法
2.可以设置memoize_for_request
属性来把key依据视图、请求方法保存在request的_key_constructor_cache里。
默认DefaultKeyConstructor
# unique_method_id 接口调用的视图方法的id,例如 'blog.views.PostViewSet.list'
# format 可以理解为视图格式类型 例如:'api'
# language 语言'zh-hans'
class DefaultKeyConstructor(KeyConstructor):
unique_method_id = bits.UniqueMethodIdKeyBit()
format = bits.FormatKeyBit()
language = bits.LanguageKeyBit()
一般来说可能默认的DefaultKeyConstructor就会足够了。它的key主要是根据视图方法来构建的
但是很多时候我们的list需要满足对应不同的访问者、不同的页码,甚至是不一样的sql查询语句来查询不一样的列表,因此我们也可以自定义KeyConstructor的bits.
get_data是不同bit获取不同数据的实现。那么除了UniqueMethodIdKeyBit
,FormatKeyBit
,LanguageKeyBit
有哪些Bits呢?
class ListSqlQueryKeyBit(bits.SqlQueryKeyBitBase):
def get_data(self, params, view_instance, view_method, request, args, kwargs):
queryset = view_instance.filter_queryset(view_instance.get_queryset())
res = self._get_queryset_query_string(queryset) # 数据库查询语句
return res
以上两者原理一致,只是后者是Retrieve方法专用。最终其返回了数据库查询语句,也就是说如果查询语句不一样,那么就能针对性的缓存不一样数据的结果,当然使用该bit很想明显需要注意的是,必须在视图集的get_queryset
方法就确定查询集Queryset。queryset.values_list()
的结果,总之就是根据查询结果的数据来作为bit对于一篇可编辑的文章,DefaultKeyConstructor满足不了我们,因为文章可以能修改,修改后缓存里可是之前的旧文章,所以我们也可以自定义KeyBit。
class UpdatedAtKeyBit(bits.KeyBitBase):
key = "updated_at"
# 缓存上一次修改文章的时间,注意必须只要做了文章创建或修改操作就更新这个值
def get_data(self, **kwargs):
value = cache.get(self.key, None)
if not value:
value = datetime.datetime.utcnow()
cache.set(self.key, value=value)
return str(value)
class PostUpdatedAtKeyBit(UpdatedAtKeyBit):
key = "post_updated_at"
# list
# DefaultKeyConstructor包含请求方法,其他2个没啥大用这里不提
# 自定义添加ListSqlQueryKeyBit:根据sql查询语句
# PaginationKeyBit根据分页参数
# PostUpdatedAtKeyBit根据更新时间
class PostListKeyConstructor(DefaultKeyConstructor):
list_sql = bits.ListSqlQueryKeyBit()
pagination = bits.PaginationKeyBit()
updated_at = PostUpdatedAtKeyBit()
# retrieve
class PostObjectKeyConstructor(DefaultKeyConstructor):
retrieve_sql = bits.RetrieveSqlQueryKeyBit()
updated_at = PostUpdatedAtKeyBit()
装饰器@cache_response(timeout=保存时间单位:秒,key_func=key构造类())
from rest_framework_extensions.cache.decorators import cache_response
class XXXViewSet(ModelViewSet):
……
……
@cache_response(timeout=20, key_func=PostListKeyConstructor())
def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs)
以上本人基于
追梦人生的drf博客教程:加缓存为接口提速
一文,后深入学习的笔记