扩展默认缓存标签
如果您在前面几节中解释的五个设置不足以满足您的要求,或者您想要一个与默认提供的不同的行为的模板标签,您一定会很高兴知道 django-adv-cache-tag 是为了容易扩展而编写的。
它提供了一个名为 CacheTag(位于 adv_cache_tag.tag)的类,该类有许多简短简单的方法,还有一个名为 Meta 的类(灵感来源于 django 模型 :D)。因此,覆盖简单部分非常容易。
Meta 类中定义的所有选项都可以通过 self.options.some_field 在类中访问
下面我们将展示许多扩展此类的方法。
基本覆盖
想象一下,您不想更改默认设置(所有设置为 False,并使用默认后端),但想要一个具有版本控制激活的模板标签
创建一个名为 myapp/templatetags/my_cache_tags.py 的新模板标签文件,内容如下
from adv_cache_tag.tag import CacheTag
class VersionedCacheTag(CacheTag):
class Meta(CacheTag.Meta):
versioning = True
from django import template
register = template.Library()
VersionedCacheTag.register(register, 'ver_cache')
有了这几行简单的代码,现在您就有了一个新的模板标签,在您想要版本控制时可以使用
{% load my_cache_tags %}
{% ver_cache 0 myobj_main_template obj.pk obj.date_last_updated %}
obj
{% endver_cache %}
如您所见,只需将 {% load adv_cache %}(或 django 默认的 {% load cache %})替换为 {% load my_cache_tags %}(您的模板标签模块),并将 {% cache %} 模板标签替换为您自己定义的新标签 {% ver_cache ... %}。别忘了替换结束标签: {% endver_cache %}。但是 {% nocache %} 将保持不变,除非您想要一个新的。为此,只需向 register 方法添加一个参数即可
MyCacheTag.register(register, 'ver_cache', 'ver_nocache')
{% ver_cache ... %}
cached
{% ver_nocache %}not cached{% endver_nocache %}
{% endver_cache %}
请注意,如果您知道您的模板中不会加载另一个提供 cache 标签的模板标签模块,您可以保留标签的名称为 cache。为此,最简单的方法是
MyCacheTag.register(register) # 'cache' and 'nocache' are the default values
django-adv-cache-tag 的所有设置在 Meta 类中都有一个对应的变量,因此您可以在自己的类中覆盖一个或多个。请参阅“设置”部分了解它们。
内部版本
当您的模板文件更新时,使所有缓存的模板版本失效的唯一方法是更新片段名称或传递给模板标签的参数。
使用 django-adv-cache-tag,您可以通过版本控制来做到这一点,通过将版本作为模板标签的最后一个参数来管理自己的版本。但如果你想要使用 django-adv-cache-tag 的版本控制系统的力量,可能会太啰嗦了。
{% load adv_cache %}
{% with template_version=obj.date_last_updated|stringformat:"s"|add:"v1" %}
{% cache 0 myobj_main_template obj.pk template_version %}
...
{% endcache %}
{% endwith %}
django-adv-cache-tag 提供了一种使用 ADV_CACHE_VERSION 设置轻松实现此功能的方法。但是,通过更新它,所有缓存的版本都将被失效,而不仅仅是您更新的那些。
要这样做,只需创建一个具有特定内部版本的自己的标签即可。
class MyCacheTag(CacheTag):
class Meta(CacheTag.Meta):
internal_version = "v1"
MyCacheTag.register('my_cache')
然后,在您的模板中,您可以直接这样做:
{% load my_cache_tags %}
{% my_cache 0 myobj_main_template obj.pk obj.date_last_updated %}
...
{% endmy_cache %}
每次您更新模板内容并希望使其失效时,只需更改 MyCacheTag 类中的 internal_version(或者您也可以为此使用设置)。
更改缓存后端
如果您想更改单个模板标签的缓存后端,这很简单
class MyCacheTag(CacheTag):
class Meta:
cache_backend = 'templates'
但您也可以通过覆盖一个方法来实现这一点
from django.core.cache import get_cache
class MyCacheTag(CacheTag):
def get_cache_object(self):
return get_cache('templates')
如果您想要为旧对象使用一个缓存后端,而为新对象使用另一个更快的前端
from django.core.cache import get_cache
class MyCacheTag(CacheTag):
class Meta:
cache_backend = 'fast_templates'
def get_cache_object(self):
cache_backend = self.options.cache_backend
if self.get_pk() < 1000:
cache_backend = 'slow_templates'
return get_cache(cache_backend)
由 get_cache_object 返回的值应该是一个缓存后端对象,但由于我们只在这个对象上使用 set 和 get 方法,如果它提供这两个方法,它可以满足您的需求。此外,如果您不想使用缓存后端对象的默认 set 和 get 方法,您还可以覆盖 CacheTag 类的 cache_set 和 cache_get 方法。
请注意,我们还支持在模板标签中使用 django 的方式更改缓存后端,使用 using 参数,将其设置为最后一个参数(using 和缓存后端名称之间没有空格)。
{% cache 0 myobj_main_template obj.pk obj.date_last_updated using=foo %}
更改缓存键
CacheTag 类提供了三个类来创建缓存键
参数如下:
nodename 参数是 templatetag 的名称:在 {% my_cache ... %} 中是 “my_cache”
name 是您的 templatetag 的“片段名称”,即过期时间之后的值
pk 仅在 self.options.include_pk 为 True 时使用,并由 this.get_pk() 返回
hash 是片段名称之后所有参数的哈希值,排除最后一个版本号(只有当 self.options.versioning 为 True 时才排除此值)
如果您想从缓存键的开头移除“template.”部分(如果有专门用于模板缓存的缓存后端,则此部分无意义),可以这样做:
class MyCacheTag(CacheTag):
def get_base_cache_key(self):
cache_key = super(MyCacheTag, self).get_base_cache_key()
return cache_key[len('template:'):] # or [9:]
向模板标签添加一个参数
默认情况下,CacheTag 提供的模板标签与默认 django 缓存模板标签具有相同的参数。
如果您想添加一个参数,很容易,因为该类提供了一个 get_template_node_arguments 方法,它将像正常 django templatetags 一样工作,接受一个标记列表,并返回将传递给真实 templatetag 的标记,以及与 CacheTag 相关的 Node 类。
假设您想在过期时间和片段名称之间添加一个 foo 参数
from django import template
from adv_cache_tag.tag import CacheTag, Node
class MyNode(Node):
def __init__(self, nodename, nodelist, expire_time, foo, fragment_name, vary_on):
""" Save the foo variable in the node (not resolved yet) """
super(MyNode, self).__init__(self, nodename, nodelist, expire_time, fragment_name, vary_on)
self.foo = foo
class MyCacheTag(CacheTag):
Node = MyNode
def prepare_params(self):
""" Resolve the foo variable to it's real content """
super(MyCacheTag, self).prepare_params()
self.foo = template.Variable(self.node.foo).resolve(self.context)
@classmethod
def get_template_node_arguments(cls, tokens):
""" Check validity of tokens and return them as ready to be passed to the Node class """
if len(tokens) < 4:
raise template.TemplateSyntaxError(u"'%r' tag requires at least 3 arguments." % tokens[0])
return (tokens[1], tokens[2], tokens[3], tokens[4:])
准备模板缓存
这一部分不是关于重写类,但它可能很有用。当一个对象被更新时,在此时重新生成缓存的模板可能比在我们需要显示它时更好。
这很简单。你可以通过捕获你模型的post_save信号,或者简单地通过重写其save方法来实现。在这个例子中,我们将使用后一种解决方案。
唯一特别的地方是知道你的templatetag所在的模板路径。在我的情况下,我有一个仅为此目的的模板(包含在其他一些通用模板中),因此更容易找到它并像以下示例那样重新生成它。
因为我们不在请求中,所以我们这里没有Request对象,因此上下文处理器不起作用,我们必须创建一个将用于渲染模板的上下文对象,其中包含所有必要的变量。
from django.template import loader, Context
class MyModel(models.Model):
# your fields
def save(self, *args, **kwargs):
super(MyModel, self.save(*args, **kwargs)
template = 'path/to/my_template_file_with_my_cache_block.html'
context = Context({
'obj': self,
# as you have no request, we have to add stuff from context processors manually if we need them
'STATIC_URL': settings.STATIC_URL,
# the line below indicates that we force regenerating the cache, even if it exists
'__regenerate__': True,
# the line below indicates if we only want html, without parsing the nocache parts
'__partial__': True,
})
loader.get_template(template).render(context)
在渲染之前从数据库加载数据
这是一个特殊情况。比如说你想显示一个对象列表,但你只有从redis(使用ZSET,以id为值,以更新日期(用作版本)为分数)检索到的ids和版本。
如果你知道你总是有一个有效的模板版本在缓存中,因为它们每次保存时都会被重新生成,如上所述,那很好,只需将对象的主体键作为templatetag参数中的pk即可,缓存的模板将被加载。
但如果不是这种情况,你将遇到问题:当Django渲染模板时,上下文中存在的对象的唯一部分是主体键,所以如果你需要名称或任何其他字段来渲染缓存的模板,它将不起作用。
使用django-adv-cache-tag很容易解决这个问题,因为我们可以从数据库中加载对象并将其添加到上下文中。
视图
def my_view(request):
objects = [
dict(
pk=val[0],
date_last_updated=val[1]
)
for val in
redis.zrevrange('my_objects', 0, 19, withscores=True)
]
return render(request, "my_results.html", dict(objects=objects))
模板“my_results.html”
{% for obj in objects %}
{% include "my_result.html" %}
{% endfor %}
模板“my_result.html”
{% load my_cache_tags %}
{% my_cache 0 myobj_main_template obj.pk obj.date_last_updated %}
{{ obj }}
{% endmy_cache %}
templatetag
在myapp/templatetags/my_cache_tags
from my_app.models import MyModel
class MyCacheTag(CacheTag):
class Meta(CacheTag.Meta):
""" Force options """
include_pk = True
versioning = True
def create_content(self):
""" If the object in context is not a real model, load it from db """
if not isinstance(context['obj'], MyObject):
context['obj'] = MyModel.objects.get(id=self.get_pk())
super(MyCacheTag, self).create_content()
MyCacheTag.register('my_cache')
注意这一点,它生成的数据库请求与要加载的对象一样多。
还有更多...
如果你想要做更多,请自由查看CacheTag类的源代码(在tag.py中),所有方法都有文档说明。
运行测试
如果adv_cache_tag在您的项目的INSTALLED_APPS中,只需运行
django-admin test adv_cache_tag
(根据您的安装情况,您可能想使用django-admin或./manage.py)
如果您在一个新的虚拟环境中工作adv_cache_tag,安装您想要的django版本
pip install django
然后使adv_cache_tag模块可在您的python路径中使用。例如,使用virtualenv-wrapper,假设您在django-adv-cache-tag存储库的根目录下,只需这样做
add2virtualenv .
或者简单地
pip install -e .
然后要运行测试,此库提供了一个测试项目,因此您可以这样启动它们
DJANGO_SETTINGS_MODULE=adv_cache_tag.tests.testproject.settings django-admin.py test adv_cache_tag
或者简单地启动runtests.sh脚本(它将运行此确切命令)
./runtests.sh