Как я могу предотвратить циклические {% include %} звонки в шаблонах Jinja2
У меня есть приложение Django SaaS с пользователями, которые создают свои собственные шаблоны Jinja2 (в необычно изолированной среде для тех, кто только что выползли), которые сохраняются в базе данных. у меня есть template_type
поле, отмечая, является ли данный шаблон "включенным" или "полным шаблоном" (полный шаблон может, конечно, включать "включает"). Проблема в том, что пользователь мог поставить {% include "foo" %}
в шаблон под названием "bar"
, а также {% include "bar" %}
в "foo"
шаблон, в результате чего RuntimeError: maximum recursion depth exceeded
тип ситуации, которая не будет хорошей для производительности.
Есть хороший способ справиться с этой ситуацией, которая не включает регулярное выражение проверки (например, r'\{%\s+include'
) проверка на наличие включений во время создания пользовательского шаблона ("Убедитесь, что рекурсивный импорт никогда не попадет в базу данных, иначе ваш сервер взорвется" не совсем подходит мне).
Моя неудачная попытка
Я начал с использования пользовательского загрузчика, который содержит только пользовательские "include"::
def get_source(self, environment, template):
"""
Returns the source of the template or raises ``TemplateNotFound``.
This loader is for use with an environment which intends to load each
template from a string, since only includes are loaded from here.
"""
try:
template = self.snippets[template]
except KeyError:
raise jinja2.TemplateNotFound(
template, message=u'Snippet "{s}" was not found'.format(s=template)
)
else:
source = template['source']
return (source, None, lambda: True)
Проблема с этим заключается в том, что я в основном заблокировал себя от возможности воспользоваться преимуществами кеша байт-кода Jinja2, для которого явно требовалось бы, чтобы все шаблоны были доступны для load(...
вызов, который в свою очередь вызывает get_source(...
,
1 ответ
Для разбора шаблонов и проверки включений я использую этот код:
ast = env.parse(template_text)
for each in meta.find_referenced_templates(ast) : # find the (% includes %}