扩展 — Jinja 文档

来自菜鸟教程
Jinja/docs/3.0.x/extensions
跳转至:导航、​搜索

扩展

Jinja 支持可以添加额外过滤器、测试、全局变量甚至扩展解析器的扩展。 扩展的主要动机是将经常使用的代码移动到可重用的类中,例如添加对国际化的支持。

添加扩展

扩展在创建时添加到 Jinja 环境中。 要添加扩展,请将扩展类列表或导入路径传递给 Environment 构造函数的 extensions 参数。 以下示例创建了一个加载了 i18n 扩展的 Jinja 环境:

jinja_env = Environment(extensions=['jinja2.ext.i18n'])

要在创建时间后添加扩展,请使用 add_extension() 方法:

jinja_env.add_extension('jinja2.ext.debug')

i18n 扩展

进口名称: jinja2.ext.i18n

i18n 扩展可以与 gettextBabel 结合使用。 启用后,Jinja 提供 trans 语句,将块标记为可翻译并调用 gettext

启用后,应用程序必须为 gettextngettext 以及可选的 pgettextnpgettext 提供功能,无论是全局还是渲染时。 添加了 _() 函数作为 gettext 函数的别名。

环境方法

启用扩展后,环境提供以下附加方法:

jinja2.Environment.install_gettext_translations(translations, newstyle=False)

为环境全局安装翻译。 translations 对象必须实现 gettextngettext 和可选的 pgettextnpgettext。 支持 gettext.NullTranslationsgettext.GNUTranslationsBabels Translations

3.0 版本变更: 添加 pgettextnpgettext

2.5 版更改: 添加了新样式的 gettext 支持。

jinja2.Environment.install_null_translations(newstyle=False)

安装无操作 gettext 函数。 如果您想为国际化准备应用程序但还不想实现完整的系统,这将非常有用。

2.5 版更改: 添加了新样式的 gettext 支持。

jinja2.Environment.install_gettext_callables(gettext, ngettext, newstyle=False, pgettext=None, npgettext=None)

将给定的 gettextngettextpgettextnpgettext 可调用对象安装到环境中。 它们的行为应该与 gettext.gettext()gettext.ngettext()gettext.pgettext()gettext.npgettext() 完全一样。

如果 newstyle 被激活,callables 被包装成像 newstyle callables 一样工作。 有关更多信息,请参阅 新样式 Gettext

3.0 版本变更: 添加 pgettextnpgettext

2.5 新功能: 添加了新样式的 gettext 支持。

jinja2.Environment.uninstall_gettext_translations()
卸载环境的全局安装翻译。
jinja2.Environment.extract_translations(source)

从给定的模板节点或源中提取可本地化的字符串。

对于找到的每个字符串,此函数生成一个 (lineno, function, message) 元组,其中:

  • lineno 是找到字符串所在行的编号。

  • function 是使用的 gettext 函数的名称(如果字符串是从嵌入的 Python 代码中提取的)。

  • message 是字符串本身,或者是具有多个参数的函数的字符串元组。

如果安装了 Babel,请参阅 Babel 来提取字符串。

对于提供多种语言但为所有用户提供相同语言的 Web 应用程序(例如,为法语社区安装的多语言论坛软件),可能会在创建环境时安装翻译。

translations = get_gettext_translations()
env = Environment(extensions=["jinja2.ext.i18n"])
env.install_gettext_translations(translations)

get_gettext_translations 函数将返回当前配置的转换器,例如使用 gettext.find

i18n 扩展对模板设计者的使用在模板文档中有介绍。


空白修剪

2.10 版中的新功能。


{% trans %} 块中,修剪换行符和空格会很有用,这样文本块看起来就像翻译文件中带有单个空格的简单字符串。

通过启用 ext.i18n.trimmed 策略 ,可以自动修剪换行符和周围的空白。


新样式获取文本

2.5 版中的新功能。


新样式的 gettext 调用更少键入,更不容易出错,并且更好地支持自动转义。

您可以通过设置 env.newstyle_gettext = True 或将 newstyle=True 传递给 env.install_translations 来使用“新样式”gettext 调用。 Babel 提取工具完全支持它们,但可能无法与其他提取工具按预期工作。

对于标准的 gettext 调用,字符串格式化是使用 |format 过滤器完成的单独步骤。 这需要为 ngettext 调用重复工作。

{{ gettext("Hello, World!") }}
{{ gettext("Hello, %(name)s!")|format(name=name) }}
{{ ngettext(
       "%(num)d apple", "%(num)d apples", apples|count
   )|format(num=apples|count) }}
{{ pgettext("greeting", "Hello, World!") }}
{{ npgettext(
       "fruit", "%(num)d apple", "%(num)d apples", apples|count
   )|format(num=apples|count) }}

新样式 gettext 使格式化成为调用的一部分,并在幕后加强一致性。

{{ gettext("Hello, World!") }}
{{ gettext("Hello, %(name)s!", name=name) }}
{{ ngettext("%(num)d apple", "%(num)d apples", apples|count) }}
{{ pgettext("greeting", "Hello, World!") }}
{{ npgettext("fruit", "%(num)d apple", "%(num)d apples", apples|count) }}

newstyle gettext 的优点是:

  • 没有单独的格式化步骤,您不必记住使用 |format 过滤器。
  • 只允许命名占位符。 这解决了翻译人员面临的一个常见问题,因为位置占位符无法有意义地切换位置。 命名占位符总是携带关于什么值去哪里的语义信息。
  • 即使不使用占位符,也会使用字符串格式,这使得所有字符串都使用一致的格式。 请记住将任何原始百分号转义为 %%,例如 100%%
  • 翻译后的字符串被标记为安全,格式化会根据需要执行转义。 如果参数已被转义,则将其标记为 |safe


表达式语句

进口名称: jinja2.ext.do

“do”又名表达式语句扩展向模板引擎添加了一个简单的 do 标签,该标签的工作方式类似于变量表达式,但会忽略返回值。


循环控制

进口名称: jinja2.ext.loopcontrols

此扩展在循环中添加了对 breakcontinue 的支持。 启用后,Jinja 提供了这两个关键字,它们的工作方式与 Python 中的完全相同。


有声明

进口名称: jinja2.ext.with_

2.9 版更改: 此扩展现已内置,不再执行任何操作。


自动逃生扩展

进口名称: jinja2.ext.autoescape

在 2.9 版更改:此扩展已被删除,现在是内置的。 启用扩展不再做任何事情。


调试扩展

进口名称: jinja2.ext.debug

添加 {% debug %} 标签以转储当前上下文以及可用的过滤器和测试。 这对于在不设置调试器的情况下查看模板中可用的内容很有用。


编写扩展

通过编写扩展,您可以向 Jinja 添加自定义标签。 这是一项重要任务,通常不需要,因为默认标签和表达式涵盖了所有常见用例。 i18n 扩展是一个很好的例子,说明了扩展为何有用。 另一种是片段缓存。

在编写扩展时,您必须记住,您正在使用 Jinja 模板编译器,它不会验证您传递给它的节点树。 如果 AST 格式不正确,您将收到各种令人讨厌的编译器或运行时错误。 始终确保您使用的是正确创建的节点。 下面的 API 文档显示了存在哪些节点以及如何使用它们。


示例扩展

缓存

以下示例使用 cachelib 库为 Jinja 实现了 cache 标签:

from jinja2 import nodes
from jinja2.ext import Extension


class FragmentCacheExtension(Extension):
    # a set of names that trigger the extension.
    tags = {"cache"}

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

        # add the defaults to the environment
        environment.extend(fragment_cache_prefix="", fragment_cache=None)

    def parse(self, parser):
        # the first token is the token that started the tag.  In our case
        # we only listen to ``'cache'`` so this will be a name token with
        # `cache` as value.  We get the line number so that we can give
        # that line number to the nodes we create by hand.
        lineno = next(parser.stream).lineno

        # now we parse a single expression that is used as cache key.
        args = [parser.parse_expression()]

        # if there is a comma, the user provided a timeout.  If not use
        # None as second parameter.
        if parser.stream.skip_if("comma"):
            args.append(parser.parse_expression())
        else:
            args.append(nodes.Const(None))

        # now we parse the body of the cache block up to `endcache` and
        # drop the needle (which would always be `endcache` in that case)
        body = parser.parse_statements(["name:endcache"], drop_needle=True)

        # now return a `CallBlock` node that calls our _cache_support
        # helper method on this extension.
        return nodes.CallBlock(
            self.call_method("_cache_support", args), [], [], body
        ).set_lineno(lineno)

    def _cache_support(self, name, timeout, caller):
        """Helper callback."""
        key = self.environment.fragment_cache_prefix + name

        # try to load the block from the cache
        # if there is no fragment in the cache, render it and store
        # it in the cache.
        rv = self.environment.fragment_cache.get(key)
        if rv is not None:
            return rv
        rv = caller()
        self.environment.fragment_cache.add(key, rv, timeout)
        return rv

以下是您在环境中使用它的方式:

from jinja2 import Environment
from cachelib import SimpleCache

env = Environment(extensions=[FragmentCacheExtension])
env.fragment_cache = SimpleCache()

在模板内部,可以将块标记为可缓存。 以下示例将侧边栏缓存 300 秒:

{% cache 'sidebar', 300 %}
<div class="sidebar">
    ...
</div>
{% endcache %}

内联 gettext

以下示例演示使用 Extension.filter_stream() 解析对 _() gettext 函数的调用与静态数据内联,无需 Jinja 块。

<h1>_(Welcome)</h1>
<p>_(This is a paragraph)</p>

它需要加载和配置 i18n 扩展。

import re

from jinja2.exceptions import TemplateSyntaxError
from jinja2.ext import Extension
from jinja2.lexer import count_newlines
from jinja2.lexer import Token


_outside_re = re.compile(r"\\?(gettext|_)\(")
_inside_re = re.compile(r"\\?[()]")


class InlineGettext(Extension):
    """This extension implements support for inline gettext blocks::

        <h1>_(Welcome)</h1>
        <p>_(This is a paragraph)</p>

    Requires the i18n extension to be loaded and configured.
    """

    def filter_stream(self, stream):
        paren_stack = 0

        for token in stream:
            if token.type != "data":
                yield token
                continue

            pos = 0
            lineno = token.lineno

            while True:
                if not paren_stack:
                    match = _outside_re.search(token.value, pos)
                else:
                    match = _inside_re.search(token.value, pos)
                if match is None:
                    break
                new_pos = match.start()
                if new_pos > pos:
                    preval = token.value[pos:new_pos]
                    yield Token(lineno, "data", preval)
                    lineno += count_newlines(preval)
                gtok = match.group()
                if gtok[0] == "\\":
                    yield Token(lineno, "data", gtok[1:])
                elif not paren_stack:
                    yield Token(lineno, "block_begin", None)
                    yield Token(lineno, "name", "trans")
                    yield Token(lineno, "block_end", None)
                    paren_stack = 1
                else:
                    if gtok == "(" or paren_stack > 1:
                        yield Token(lineno, "data", gtok)
                    paren_stack += -1 if gtok == ")" else 1
                    if not paren_stack:
                        yield Token(lineno, "block_begin", None)
                        yield Token(lineno, "name", "endtrans")
                        yield Token(lineno, "block_end", None)
                pos = match.end()

            if pos < len(token.value):
                yield Token(lineno, "data", token.value[pos:])

        if paren_stack:
            raise TemplateSyntaxError(
                "unclosed gettext expression",
                token.lineno,
                stream.name,
                stream.filename,
            )

扩展API

延期

扩展总是要扩展 jinja2.ext.Extension 类:

class jinja2.ext.Extension(environment)

扩展可用于在解析器级别向 Jinja 模板系统添加额外功能。 自定义扩展绑定到环境,但可能不会在 self 上存储环境特定数据。 这样做的原因是通过创建副本并重新分配 environment 属性,可以将扩展绑定到另一个环境(用于覆盖)。

由于扩展是由环境创建的,因此它们不能接受任何配置参数。 人们可能想通过使用工厂函数来解决这个问题,但这是不可能的,因为扩展是由它们的导入名称标识的。 配置扩展的正确方法是将配置值存储在环境中。 因为通过这种方式,环境最终充当中央配置存储,属性可能会发生冲突,这就是为什么扩展必须确保它们为配置选择的名称不太通用。 例如 prefix 是一个糟糕的名字,另一方面 fragment_cache_prefix 是一个好名字,因为它包含了扩展名(片段缓存)。

参数

environment (jinja2.environment.Environment) –

返回类型

没有任何

identifier

扩展的标识符。 这始终是扩展类的真正导入名称,不得更改。

tags

如果扩展实现了自定义标签,则这是扩展正在侦听的一组标签名称。

attr(name, lineno=None)

返回当前扩展的属性节点。 这对于将扩展上的常量传递给生成的模板代码很有用。

self.attr('_my_attribute', lineno=lineno)
参数
  • 名称 (str) –

  • lineno (可选[int]) –

返回类型

jinja2.nodes.ExtensionAttribute

call_method(name, args=None, kwargs=None, dyn_args=None, dyn_kwargs=None, lineno=None)

调用扩展的方法。 这是 attr() + jinja2.nodes.Call 的快捷方式。

参数
  • 名称 (str) –

  • args (可选[列表[jinja2.nodes.Expr ]]) –

  • kwargs (可选[列表[jinja2.nodes.Keyword ]]) –

  • dyn_args (可选[jinja2.nodes.Expr]) –

  • dyn_kwargs (可选[jinja2.nodes.Expr]) –

  • lineno (可选[int]) –

返回类型

jinja2.nodes.Call

filter_stream(stream)

它传递了一个 TokenStream,可用于过滤返回的令牌。 此方法必须返回 Token 的迭代,但它不必返回 TokenStream

参数

stream (TokenStream) –

返回类型

Union[TokenStream, Iterable[Token]]

parse(parser)

如果任何 tags 匹配,则使用解析器作为第一个参数调用此方法。 解析器流指向的标记是匹配的名称标记。 此方法必须返回一个或多个节点的列表。

参数

解析器 (解析器) –

返回类型

联合[jinja2.nodes.Node, List[jinja2.nodes.Node]]

preprocess(source, name, filename=None)

此方法在实际词法分析之前调用,可用于预处理源。 文件名 是可选的。 返回值必须是预处理过的源。

参数
  • source (str) –

  • 名称 (可选[str]) –

  • 文件名 (可选[str]) –

返回类型

字符串


解析器

传递给 Extension.parse() 的解析器提供了解析不同类型表达式的方法。 扩展可以使用以下方法:

class jinja2.parser.Parser(environment, source, name=None, filename=None, state=None)

这是 Jinja 使用的中央解析类。 它传递给扩展,可用于解析表达式或语句。

参数
  • 环境 (环境) –

  • source (str) –

  • 名称 (可选[str]) –

  • 文件名 (可选[str]) –

  • state (可选[str]) –

返回类型

没有任何

filename

解析器处理的模板的文件名。 这是 不是 模板的加载名称。 有关负载名称,请参阅 name。 对于未从文件系统加载的模板,这是 None

name

模板的加载名称。

stream

当前的 TokenStream

fail(msg, lineno=None, exc=<class 'jinja2.exceptions.TemplateSyntaxError'>)

使用消息、传递的行号或最后一行号以及当前名称和文件名引发 exc 的便捷方法。

参数
返回类型

te.NoReturn

free_identifier(lineno=None)

返回一个新的空闲标识符作为 InternalName

参数

lineno (可选[int]) –

返回类型

jinja2.nodes.InternalName

parse_assign_target(with_tuple=True, name_only=False, extra_end_rules=None, with_namespace=False)

解析分配目标。 由于 Jinja 允许对元组进行赋值,因此该函数可以解析所有允许的赋值目标。 解析元组的默认分配,但是可以通过将 with_tuple 设置为 False 来禁用。 如果只需要给名称赋值 name_only 可以设置为 True。 extra_end_rules 参数被转发到元组解析函数。 如果启用 with_namespace,则可以解析命名空间分配。

参数
  • with_tuple (bool) –

  • name_only (bool) –

  • extra_end_rules (可选[元组[str, ...]]) –

  • with_namespace (bool) –

返回类型

联合[jinja2.nodes.NSRef, jinja2.nodes.Name, jinja2.nodes.Tuple]

parse_expression(with_condexpr=True)

解析表达式。 默认情况下,所有表达式都被解析,如果可选的 with_condexpr 参数设置为 False 条件表达式不被解析。

参数

with_condexpr (bool) –

返回类型

jinja2.nodes.Expr

parse_statements(end_tokens, drop_needle=False)

将多个语句解析为一个列表,直到达到结束标记之一。 这用于解析语句体,因为它还会在适当的情况下解析模板数据。 解析器首先检查当前标记是否为冒号,如果有则跳过它。 然后它检查块结束并解析直到是否达到 end_tokens 之一。 默认情况下,调用结束时流中的活动标记是匹配的结束标记。 如果不需要 drop_needle 可以设置为 True 并删除结束标记。

参数
  • end_tokens (元组[str,...][ X85X]) –

  • drop_needle (bool) –

返回类型

列表[jinja2.nodes.Node]

parse_tuple(simplified=False, with_condexpr=True, extra_end_rules=None, explicit_parentheses=False)

像 parse_expression 一样工作,但如果多个表达式由逗号分隔,则会创建 Tuple 节点。 如果找不到逗号,此方法还可以返回正则表达式而不是元组。

默认的解析模式是一个完整的元组。 如果 simplified 是 True,则仅解析名称和文字。 no_condexpr 参数被转发到 parse_expression()

因为元组不需要分隔符并且可能以假逗号结尾,所以需要额外的提示来标记元组的结尾。 例如,for 循环支持 for 和 in 之间的元组。 在这种情况下,extra_end_rules 设置为 ['name:in']

explicit_parentheses 如果解析是由括号中的表达式触发的,则为真。 这用于确定空元组是否是有效表达式。

参数
  • 简化 (bool) –

  • with_condexpr (bool) –

  • extra_end_rules (可选[元组[str, ...]]) –

  • explicit_parentheses (bool) –

返回类型

联合[jinja2.nodes.Tuple, jinja2.nodes.Expr]

class jinja2.lexer.TokenStream(generator, name, filename)

令牌流是产生 令牌 的可迭代对象。 然而,解析器不会对其进行迭代,而是调用 next() 以前进一个标记。 当前活动令牌存储为 current

参数
  • generator (Iterable[jinja2.lexer.Token]) –

  • 名称 (可选[str]) –

  • 文件名 (可选[str]) –

current

当前的 Token

__next__()

前进一个令牌并返回旧的。

使用内置的 next() 而不是直接调用它。

返回类型

jinja2.lexer.Token

property eos: bool

我们是在流的尽头吗?

expect(expr)

期望给定的令牌类型并返回它。 这接受与 jinja2.lexer.Token.test() 相同的参数。

参数

expr (str) –

返回类型

jinja2.lexer.Token

look()

看下一个令牌。

返回类型

jinja2.lexer.Token

next_if(expr)

执行令牌测试并在匹配时返回令牌。 否则返回值为 None。

参数

expr (str) –

返回类型

可选[jinja2.lexer.Token]

push(token)

将令牌推回流。

参数

token (jinja2.lexer.Token) –

返回类型

没有任何

skip(n=1)

前面有 n 个代币。

参数

n (int) –

返回类型

没有任何

skip_if(expr)

next_if() 但只返回 True 或 False。

参数

expr (str) –

返回类型

布尔值

class jinja2.lexer.Token(lineno, type, value)
参数
  • lineno (int) –

  • type (str) –

  • (str) –

lineno

令牌的行号

type

令牌的类型。 这个字符串是实习的,因此您可以使用 is 运算符将其与任意字符串进行比较。

value

代币的价值。

test(expr)

针对令牌表达式测试令牌。 这可以是令牌类型或 'token_type:token_value'。 这只能针对字符串值和类型进行测试。

参数

expr (str) –

返回类型

布尔值

test_any(*iterable)

针对多个令牌表达式进行测试。

参数

iterable (str) –

返回类型

布尔值

lexer 模块中还有一个实用函数,可以计算字符串中的换行符:

jinja2.lexer.count_newlines(value)
计算字符串中换行符的数量。 这对于过滤流的扩展很有用。
参数
(str) –
返回类型
整数


AST

AST(抽象语法树)用于表示解析后的模板。 它是节点的构建,然后编译器将其转换为可执行的 Python 代码对象。 提供自定义语句的扩展可以返回节点以执行自定义 Python 代码。

下面的列表描述了当前可用的所有节点。 AST 可能会在 Jinja 版本之间发生变化,但会保持向后兼容。

有关更多信息,请查看 jinja2.Environment.parse() 的代表。

exception jinja2.nodes.Impossible
如果节点无法执行请求的操作,则引发。