Разрешен ли встроенный код в шаблонах Jinja? - PullRequest
14 голосов
/ 25 сентября 2011

Я использую Jinja на моем сайте, и мне это нравится.

Я столкнулся с простой необходимостью. Как отобразить сегодняшнюю дату? Есть ли способ встроить некоторый код Python в шаблон Jinja?

import datetime
now = datetime.datetime.utcnow()
print now.strftime("%Y-%m-%d %H:%M")

Эта статья говорит, что нет , но предлагает использовать макрос или фильтр?

В самом деле? Должны ли мы прибегать ко всему этому? Хорошо, как бы это выглядело в этом случае?

Ответы [ 3 ]

11 голосов
/ 25 сентября 2011

Нет, нет способа встроить Python в Jinja.Однако вы можете добавить к конструкциям, которые знает Jinja, расширив Environment механизма шаблонов или глобальное пространство имен , доступное для всех шаблонов.Кроме того, вы можете добавить фильтр, который позволяет форматировать объекты даты и времени.

Flask сохраняет среду Jinja2 в app.jinja_env.Вы можете добавить новый контекст в среду, либо добавив в этот словарь напрямую, либо используя декоратор @app.context_processor.

Какой бы путь вы ни выбрали, это следует сделать во время настройкидо подачи заявки, прежде чем вы обслуживаете какие-либо запросы.(См. Раздел фрагментов веб-сайта для некоторых хороших примеров того, как настроить фильтры - документы содержат хороший пример добавления к глобальным переменным).

3 голосов
/ 25 сентября 2011

Вы можете добавить к глобальные переменные , к которым можно получить доступ из шаблонов Jinja. Вы можете поместить туда свои собственные определения функций, которые делают все, что вам нужно.

1 голос
/ 06 апреля 2019

Текущие ответы верны практически для любой ситуации. Однако есть несколько очень редких случаев, когда вы хотите иметь код Python внутри шаблона. В моем случае я хочу использовать его для предварительной обработки некоторых латексных файлов, и я бы предпочел, чтобы код Python, генерирующий табличные значения, графики и т. Д., Находился внутри самого латексного файла.

Итак, я сделал расширение Jinja2, которое добавляет новый блок «py», позволяющий писать код python внутри шаблона. Пожалуйста, имейте в виду, что мне пришлось сделать некоторые сомнительные обходные пути, чтобы это сработало, поэтому я не уверен на 100%, в каких ситуациях он не работает или работает неожиданно.

Это пример шаблона.

Foo was given to the template
foo: {{ foo }}

Bar was not, so it is missing
bar is missing: {{ bar == missing }}

{% py %}
    # Normal python code in here
    # Excess indentation will be removed.
    # All template variables are accessible and can be modified.
    import numpy as np
    a = np.array([1, 2])
    m = np.array([[3, 4], [5, 6]])
    bar = m @ a * foo

    # It's also possible to template the python code.
    {% if change_foo %}
    foo = 'new foo value'
    {% endif %}

    print("Stdio is redirected to the output.")
{% endpy %}

Foo will have the new value if you set change_foo to True
foo: {{ foo }}

Bar will now have a value.
bar: {{ bar }}

{% py %}
    # The locals from previous blocks are accessible.
    m = m**2
{% endpy %}
m:
{{ m }}

Вывод, если мы установим параметры шаблона на foo=10, change_foo=True, будет:

Foo was given to the template
foo: 10

Bar was not, so it is missing
bar is missing: True

Stdio is redirected to the output.


Foo will have the new value if you set change_foo to True
foo: new foo value

Bar will now have a value.
bar: [110 170]


m:
[[ 9 16]
 [25 36]]

Расширение с основной функцией для запуска примера.

from jinja2 import Environment, PackageLoader, nodes
from jinja2.ext import Extension
from textwrap import dedent
from io import StringIO
import sys
import re
import ctypes


def main():
    env = Environment(
        loader=PackageLoader('python_spike', 'templates'),
        extensions=[PythonExtension]
    )

    template = env.get_template('emb_py2.txt')
    print(template.render(foo=10, change_foo=True))


var_name_regex = re.compile(r"l_(\d+)_(.+)")


class PythonExtension(Extension):
    # a set of names that trigger the extension.
    tags = {'py'}

    def __init__(self, environment: Environment):
        super().__init__(environment)

    def parse(self, parser):
        lineno = next(parser.stream).lineno
        body = parser.parse_statements(['name:endpy'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_exec_python',
                                                [nodes.ContextReference(), nodes.Const(lineno), nodes.Const(parser.filename)]),
                               [], [], body).set_lineno(lineno)

    def _exec_python(self, ctx, lineno, filename, caller):
        # Remove access indentation
        code = dedent(caller())

        # Compile the code.
        compiled_code = compile("\n"*(lineno-1) + code, filename, "exec")

        # Create string io to capture stdio and replace it.
        sout = StringIO()
        stdout = sys.stdout
        sys.stdout = sout

        try:
            # Execute the code with the context parents as global and context vars and locals.
            exec(compiled_code, ctx.parent, ctx.vars)
        except Exception:
            raise
        finally:
            # Restore stdout whether the code crashed or not.
            sys.stdout = stdout

        # Get a set of all names in the code.
        code_names = set(compiled_code.co_names)

        # The the frame in the jinja generated python code.
        caller_frame = sys._getframe(2)

        # Loop through all the locals.
        for local_var_name in caller_frame.f_locals:
            # Look for variables matching the template variable regex.
            match = re.match(var_name_regex, local_var_name)
            if match:
                # Get the variable name.
                var_name = match.group(2)

                # If the variable's name appears in the code and is in the locals.
                if (var_name in code_names) and (var_name in ctx.vars):
                    # Copy the value to the frame's locals.
                    caller_frame.f_locals[local_var_name] = ctx.vars[var_name]
                    # Do some ctypes vodo to make sure the frame locals are actually updated.
                    ctx.exported_vars.add(var_name)
                    ctypes.pythonapi.PyFrame_LocalsToFast(
                        ctypes.py_object(caller_frame),
                        ctypes.c_int(1))

        # Return the captured text.
        return sout.getvalue()

if __name__ == "__main__":
    main()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...