Привет всем!
Наверное одним из самых популярных фреймворкеров в среде программистов WEB-разработчиков на Python является Django. И скорее всего ни для кого из них не секрет о функциональной слабости встроенного в Django шаблонизатора. По этой причине программисты на Django довольно часто, в конечном счёте, переходят на какой-либо другой шаблонизатор, и в Django это проще всего сделать на Jinja2, переход на который, в принципе, уже предусмотрен прямо из коробки.
Jinja2, по моему мнению, довольно таки не плохой и весьма удобный шаблонизатор, хотя, по причине специфики его работы, после Django-шаблонизатора к нему и надо немного привыкнуть. Но, у него оказался, по крайней мере, один недостаток - у него отсутствует встроенный тэг для создания блока кэширования фрагмента шаблона. В Django же, несмотря на все его недостатки, встроенный блок кэширования есть, причём реализован он очень хорошо.
И тут, конечно, надо вспомнить про возможность расширения Jinja2 шаблонизатора, с помощью которых можно сделать практически любой блок или тэг. Но, есть опять же одно но. Если какую-нибудь свою глобальную функцию, или фильтр или даже тест в Jinja2 добавить довольно просто, то вот создать свой тэг для добавления в шаблон того же блока кэширования, несколько проблематично. Для этого для начала надо разобраться в принципе работы шаблонизатора Jinja2 и в конструкции базового класса расширений, со всеми его парсингами, множеством видов токенов, нодов и тому подобное. А информации, как оказалось, об этом не очень-то и много.
Кстати, на официальном сайте Jinja2, как раз представлен пример расширения для добавления тэга кэширования. Но, мало того, что я вообще сомневаюсь, подойдет ли он для работы с Django, так ещё он и не реализовывает нужные мне возможности. А нужно мне было, что бы этот тэг, кроме параметров времени жизни кэша и наименования фрагмента, обязательно ещё принимал дополнительные параметры, причем столько, сколько нужно, которые бы давали возможность создания более гибкого кэширования. Например, в шаблоне, который должен отображать какие-нибудь статьи, чтобы какой-либо фрагмент мог кэшироваться для каждой статьи отдельно, в зависимости, скажем, от идентификатора статьи, отображаемой этим шаблоном. И вот, представляю Вам программный код такого расширения:
from jinja2 import nodes
from jinja2 import lexer
from jinja2.ext import Extension
from django.core.cache import caches
from django.core.cache.utils import make_template_fragment_key
class TemplateCacheExtension(Extension):
""" Расширение кеширование фрагмента шаблона для Jinja2 """
tags = {"cache"}
def parse(self, parser):
lineno = next(parser.stream).lineno
args, kwargs = self._get_token_args(parser)
body = parser.parse_statements(end_tokens=["name:endcache"], drop_needle=True)
return nodes.CallBlock(
self.call_method("_cache_support", args, kwargs), [], [], body
).set_lineno(lineno)
@classmethod
def _get_token_args(cls, parser):
""" Возвращает параметры блока кеширования """
args = []
kwargs = {}
# Пока не будет достигнут конец знака блока
while parser.stream.current.type != lexer.TOKEN_BLOCK_END:
# будем анализировать входные параметры.
# Во первых надо пропустить все запятые.
if parser.stream.skip_if(lexer.TOKEN_COMMA):
continue
# Двлее получим очередной параметр,
token = parser.parse_expression()
# и проверим, если это параметр 'using', значит это указатель на вид cache-а,
if isinstance(token, nodes.Name) and token.name == 'using':
# и тогда перепрыгиваем знак равно,
next(parser.stream)
# получаем значение типа кэша, и добавляем его в виле ключевого параметра,
kwargs = [nodes.Keyword('using', parser.parse_expression())]
# и выходим из анализа параметров. так этот параметр должен быть последним.
break
# Все остальные параметры добавляем в позиционные параметры.
args.append(token)
return args, kwargs
@classmethod
def _cache_support(cls, *args, **kwargs):
""" Кеширует указанный фрагмент шаблона """
# Первым параметром должно быть время жизни кэша.
timeout = args[0]
# Вторым параметром должно быть название фрагмента.
fragment_name = args[1]
ext_name = []
# Далее надо пройтись по всем оставшимся параметрам.
if len(args) > 2:
for i in range(2, len(args)):
ext_name.append(args[i])
# Если в блоке указан ключевой параметр 'using', значит через него передан алиас кэширования.
cache = caches['default'] if 'using' not in kwargs else caches[kwargs['using']]
# В эту переменную сохраняется функция возврата содержимого текущего фрагмента шаблона.
caller = kwargs['caller']
# Для начала надо сформировать ключ фрагмента, причем с учетом дополнительных параметров фрагмента.
key = make_template_fragment_key(fragment_name, ext_name)
# По ключу попробуем взять фрагмент из кэша.
fragment = cache.get(key)
# Если в кэше этого фрагмента ещё нет, или срок его истёк,
if fragment is None:
# то получим фрагмент из шаблона,
fragment = caller()
# и сразу отправим его в кэш.
cache.set(key, fragment, timeout)
# По окоанчанию работы с кэшем его лучше закрыть.
cache.close()
# Возвращаем полученный фрагмент.
return fragment
Примерно вот так вот оно подключается:
TEMPLATES = [
{
'NAME': 'jinja2',
'BACKEND': 'django.template.backends.jinja2.Jinja2',
'APP_DIRS': True,
'OPTIONS': {
'environment': 'DjangoProject.jinja2.environment',
'autoescape': False,
'trim_blocks': True,
"extensions": [
"jinja2.ext.do",
"jinja2.ext.i18n",
"path_to_extensions.TemplateCacheExtension"
],
}
}
]
или вот так:
env = Environment(extensions=["path_to_extensions.TemplateCacheExtension"], **options)
Так как, как я уже ранее упоминал, мне очень нравилось, как реализовано кэширование в шаблонизаторе Django, то я постарался сделать тэг кэширования для Jinja2 максимально на него похожим. И вот как можно использовать этот тэг:
...
<div class="container">
<div class="row">
<!-- LEFT-SIDEBAR -->
<div id="left-sidebar">
<div class="sidebar-data">
{% cache 3600, "left-sidebar", request.user.username, using="cacheAliasName" %}
{% include "for_inclusion/left_menu.html" %}
{% endcache %}
</div>
</div><!-- /.left-sidebar -->
<!-- MAIN-CONTENT -->
<div id="content">
...
Тэг кэширования "cache" первым параметром принимает время жизни кэша в секундах, вторым параметром принимает уникальное наименование фрагмента шаблона. Далее можно указать сколько угодно дополнительных параметров, которые ещё больше определят уникальность данного фрагмента кэша, в данном примере указана переменная логина авторизованного пользователя. Если в приложении используется несколько видов кэширования, например кэширование в базу данных, и кэширование в файловую систему, то в конце параметров можно указать ключевой параметр "using", через который передать наименование алиаса кэша, с помощью которого данный фрагмент должен быть кэширован. Эти алиасы указаны в настройках Django, как раз в разделе настроек кэша.
Что удобно, так это то что далее, например, в представлении, к такому кэшу можно легко обратиться, и, допустим, очистить его, так же как это возможно сделать в случае со встроенными шаблонами Django:
from django.core.cache import caches
from django.core.cache.utils import make_template_fragment_key
def clear_left_sidebar_cache(self, username):
key = make_template_fragment_key("left-sidebar", [username])
self.caches["cacheAliasName"].delete(key)