Как получить переменные вместе с их именем фильтра из шаблона JINJA2

#python #django #flask #jinja2

#python #jinja2

Вопрос:

Я работаю над приложением на основе GAE (python) и JINJA. Я создал шаблон JINJA из текстовой строки, используя from_string метод. т.е.

 template = JINJA.from_string(text)
  

Результирующий шаблон выглядит следующим образом:

 Template(body=[Scope(body=[ScopedEvalContextModifier(options=[Keyword(key='autoescape', value=Name(name='on', ctx='load'))], body=[Output(nodes=[TemplateData(data=u'Dear '), Filter(node=Name(name='customer_name', ctx='load'), name='safe', args=[], kwargs=[], dyn_args=None, dyn_kwargs=None), TemplateData(data=u',nn'), Filter(node=Name(name='customer_name_new', ctx='load'), name='extra', args=[], kwargs=[], dyn_args=None, dyn_kwargs=None), TemplateData(data=u'n                    nThank you for choosing '), Name(name='company_name', ctx='load'), TemplateData(data=u'.nn')]), If(test=Name(name='start_datetime', ctx='load'), body=[Output(nodes=[TemplateData(data=u'Your '), Name(name='order_type', ctx='load'), TemplateData(data=u' is scheduled for:n'), Filter(node=Name(name='start_datetime_block', ctx='load'), name='safe', args=[], kwargs=[], dyn_args=None, dyn_kwargs=None), TemplateData(data=u'nYou can check out the estimated time of arrival for your '), Name(name='order_type', ctx='load'), TemplateData(data=u' using the button belown'), Filter(node=Name(name='live_link_button', ctx='load'), name='safe', args=[], kwargs=[], dyn_args=None, dyn_kwargs=None), TemplateData(data=u'n')])], else_=[Output(nodes=[TemplateData(data=u'Your '), Name(name='order_type', ctx='load'), TemplateData(data=u' is now placed.n')])]), If(test=And(left=Name(name='start_datetime', ctx='load'), right=Name(name='confirmation_required', ctx='load')), body=[Output(nodes=[TemplateData(data=u'Please confirm your availability for this appointment:n'), Filter(node=Name(name='confirmation_buttons', ctx='load'), name='safe', args=[], kwargs=[], dyn_args=None, dyn_kwargs=None), TemplateData(data=u'n')])], else_=[]), If(test=Name(name='custom_text', ctx='load'), body=[Output(nodes=[Filter(node=Name(name='custom_text', ctx='load'), name='safe', args=[], kwargs=[], dyn_args=None, dyn_kwargs=None), TemplateData(data=u'n')])], else_=[]), Output(nodes=[TemplateData(data=u'We look forward to seeing you. In case you have any questions please reach us at '), Name(name='company_email', ctx='load'), TemplateData(data=u'. '), Name(name='company_name', ctx='load'), TemplateData(data=u' '), Name(name='company_address', ctx='load'), TemplateData(data=u' '), Name(name='company_phone', ctx='load')])])])])
  

Теперь, что я хочу сделать, так это то, что я хочу получить все переменные из этого шаблона, и особенно меня интересуют такие переменные, которые имеют некоторые фильтры, как в приведенном выше шаблоне, ожидаемыми фильтрами являются safe и extra . Пожалуйста, обратите внимание, что extra это мой пользовательский фильтр.

Вызывающий meta.find_undeclared_variables метод выдает мне только список ключевых слов, но не их фильтры. т.Е.

 parsed_content = JINJA.parse(text)
keywords = meta.find_undeclared_variables(parsed_content)
  

Есть ли какой-либо способ, которым я могу получить ключевые слова вместе с именами фильтров?

Ответ №1:

Вот простое решение, которое может помочь. Он предоставляет имя переменной с фильтрами (только переменные, у которых есть фильтры):

 from jinja2 import Environment, PackageLoader, meta, nodes

def find_filters(ast):
    """Find all the nodes of a given type.  If the type is a tuple,
    the check is performed for any of the tuple items.
    """
    for child in ast.iter_child_nodes():
        if isinstance(child, nodes.Filter):
            yield child
        else:
          for result in find_filters(child):
              yield result


def filtered_variables(ast):
  """Return variables that have filters, along with their filters. might
  return duplicate variable names with different filters
  """
  results = []
  for i, node in enumerate(find_filters(ast)):
      filters = []
      f = node
      filters.append(f.name)
      while isinstance(f.node, nodes.Filter):
        f = f.node
        filters.append(f.name)
      filters.reverse()
      results.append((f.node.name, filters))
  return results


env = Environment(loader=PackageLoader('templates'))

template = '{% extends "layout.html" %}'
           '{% from "test.html" import a, b as c %}{{ some_variable | a | x}} {{ some_other }}'
           '{% import "meh.html" as meh %}{{ some_variable | b | c | d}}'
           '{% include "muh.html" %}'

ast = env.parse(template)
print(filtered_variables(ast))
  

Результат будет:

 [('some_variable', ['a', 'x']), ('some_variable', ['b', 'c', 'd'])]
  

Вы можете включать переменные, у которых нет такого фильтра:

 f_vars = filtered_variables(ast)
filtered = []
for var in f_vars:
  filtered.append(var[0])
f = [(x, []) for x in keywords if x not in filtered]
f_vars.extend(f)
print(f_vars)
  

Вывод:

 [('some_variable', ['a', 'x']), ('some_variable', ['b', 'c', 'd']), ('some_other', [])]
  

Обратите внимание, что результат может иметь повторяющиеся значения. Это может быть более полезным, поскольку для каждого вхождения переменной могут присутствовать разные фильтры.