扩展 — Jinja 文档
扩展
Jinja2 支持可以添加额外过滤器、测试、全局变量甚至扩展解析器的扩展。 扩展的主要动机是将经常使用的代码移动到可重用的类中,例如添加对国际化的支持。
添加扩展
在创建时将扩展添加到 Jinja2 环境中。 创建环境后,无法添加其他扩展。 要添加扩展,请将扩展类列表或导入路径传递给 Environment
构造函数的 extensions 参数。 以下示例创建了一个加载了 i18n 扩展的 Jinja2 环境:
jinja_env = Environment(extensions=['jinja2.ext.i18n'])
i18n 扩展
进口名称: jinja2.ext.i18n
i18n 扩展可以与 gettext 或 babel 结合使用。 如果启用了 i18n 扩展,Jinja2 会提供一个 trans 语句,将包装的字符串标记为可翻译并调用 gettext。
启用后,将调用转发到 gettext 的虚拟 _ 函数被添加到环境全局变量中。 然后,国际化的应用程序必须在命名空间中提供 gettext 函数和可选的 ngettext 函数,无论是全局的还是为每个渲染提供的。
环境方法
启用扩展后,环境提供以下附加方法:
- jinja2.Environment.install_gettext_translations(translations, newstyle=False)
为该环境全局安装翻译。 提供的翻译对象必须至少实现 ugettext 和 ungettext。 支持 gettext.NullTranslations 和 gettext.GNUTranslations 类以及 Babels Translations 类。
2.5 版变更: 新增 gettext 样式
- jinja2.Environment.install_null_translations(newstyle=False)
安装虚拟 gettext 函数。 如果您想为国际化准备应用程序,但还不想实现完整的国际化系统,这将非常有用。
2.5 版变更: 新增 gettext 样式
- jinja2.Environment.install_gettext_callables(gettext, ngettext, newstyle=False)
将给定的 gettext 和 ngettext 可调用对象作为全局变量安装到环境中。 它们的行为应该与标准库的
gettext.ugettext()
和gettext.ungettext()
函数完全一样。如果 newstyle 被激活,callables 被包装成像 newstyle callables 一样工作。 有关更多信息,请参阅 Newstyle Gettext。
2.5 版中的新功能。
- jinja2.Environment.uninstall_gettext_translations()
- 再次卸载翻译。
- jinja2.Environment.extract_translations(source)
从给定的模板节点或源中提取可本地化的字符串。
对于找到的每个字符串,此函数生成一个
(lineno, function, message)
元组,其中:lineno 是找到字符串的行号,
function 是使用的 gettext 函数的名称(如果字符串是从嵌入的 Python 代码中提取的),以及
message 是字符串本身(unicode 对象,或具有多个字符串参数的函数的 unicode 对象的元组)。
对于提供多种语言但为所有用户提供相同语言的 Web 应用程序(例如为法语社区安装的多语言论坛软件)可以加载一次翻译并在环境生成时将翻译方法添加到环境中:
translations = get_gettext_translations()
env = Environment(extensions=['jinja2.ext.i18n'])
env.install_gettext_translations(translations)
get_gettext_translations 函数将返回当前配置的翻译器。 (例如使用 gettext.find)
模板设计者使用 i18n 扩展作为模板文档 的一部分 进行了介绍。
新式获取文本
2.5 版中的新功能。
从 2.5 版开始,您可以使用 newstyle gettext 调用。 这些受到 trac 内部 gettext 函数的启发,并得到 babel 提取工具的完全支持。 如果您不使用 Babel 的,它们可能无法按其他提取工具的预期工作。
标准和新式 gettext 调用之间的最大区别是什么? 一般来说,它们不太容易打字,也不太容易出错。 此外,如果它们在自动转义环境中使用,它们会更好地支持自动转义。 以下是旧电话和新电话之间的一些常见区别:
标准获取文本:
{{ gettext('Hello World!') }}
{{ gettext('Hello %(name)s!')|format(name='World') }}
{{ ngettext('%(num)d apple', '%(num)d apples', apples|count)|format(
num=apples|count
)}}
newstyle gettext 看起来像这样:
{{ gettext('Hello World!') }}
{{ gettext('Hello %(name)s!', name='World') }}
{{ ngettext('%(num)d apple', '%(num)d apples', apples|count) }}
newstyle gettext 的优点是您需要输入的内容更少,并且命名占位符成为强制性的。 后者听起来像是一个缺点,但解决了翻译人员在无法切换两个占位符的位置时经常面临的很多麻烦。 使用 newstyle gettext,所有格式字符串看起来都一样。
此外,对于 newstyle gettext,如果不使用占位符,也会使用字符串格式,这使得所有字符串的行为完全相同。 最后但并非最不重要的是,newstyle gettext 调用能够正确标记字符串以进行自动转义,这解决了许多模板在使用自动转义时随着时间的推移而遇到的许多转义相关问题。
表达式语句
进口名称: jinja2.ext.do
“do”又名表达式语句扩展向模板引擎添加了一个简单的 do 标签,该标签的工作方式类似于变量表达式,但会忽略返回值。
循环控制
进口名称: jinja2.ext.loopcontrols
此扩展添加了对循环中 break 和 continue 的支持。 启用后,Jinja2 提供了这两个关键字,它们的工作方式与 Python 完全相同。
有声明
进口名称: jinja2.ext.with_
在 2.9 版中更改。
这个扩展现在是内置的,不再做任何事情。
自动逃生扩展
进口名称: jinja2.ext.autoescape
在 2.9 版中更改。
此扩展已被删除,现在是内置的。 启用扩展不再做任何事情。
编写扩展
通过编写扩展,您可以向 Jinja2 添加自定义标签。 这是一项重要任务,通常不需要,因为默认标签和表达式涵盖了所有常见用例。 i18n 扩展是一个很好的例子,说明了扩展为何有用。 另一种是片段缓存。
在编写扩展时,您必须记住,您正在使用 Jinja2 模板编译器,它不会验证您传递给它的节点树。 如果 AST 格式不正确,您将收到各种令人讨厌的编译器或运行时错误。 始终确保您使用的是正确创建的节点。 下面的 API 文档显示了存在哪些节点以及如何使用它们。
示例扩展
以下示例使用 cachelib 库为 Jinja2 实现了 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(FragmentCacheExtension, self).__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 %}
扩展API
扩展总是要扩展 jinja2.ext.Extension 类:
- class jinja2.ext.Extension(environment: jinja2.environment.Environment)
扩展可用于在解析器级别向 Jinja 模板系统添加额外功能。 自定义扩展绑定到环境,但可能不会在 self 上存储环境特定数据。 这样做的原因是通过创建副本并重新分配 environment 属性,可以将扩展绑定到另一个环境(用于覆盖)。
由于扩展是由环境创建的,因此它们不能接受任何配置参数。 人们可能想通过使用工厂函数来解决这个问题,但这是不可能的,因为扩展是由它们的导入名称标识的。 配置扩展的正确方法是将配置值存储在环境中。 因为通过这种方式,环境最终充当中央配置存储,属性可能会发生冲突,这就是为什么扩展必须确保它们为配置选择的名称不太通用。 例如
prefix
是一个糟糕的名字,另一方面fragment_cache_prefix
是一个好名字,因为它包含了扩展名(片段缓存)。- identifier
扩展的标识符。 这始终是扩展类的真正导入名称,不得更改。
- tags
如果扩展实现了自定义标签,则这是扩展正在侦听的一组标签名称。
- attr(name: str, lineno: Optional[int] = None) jinja2.nodes.ExtensionAttribute
返回当前扩展的属性节点。 这对于将扩展上的常量传递给生成的模板代码很有用。
self.attr('_my_attribute', lineno=lineno)
- call_method(name: str, args: Optional[List[jinja2.nodes.Expr]] = None, kwargs: Optional[List[jinja2.nodes.Keyword]] = None, dyn_args: Optional[jinja2.nodes.Expr] = None, dyn_kwargs: Optional[jinja2.nodes.Expr] = None, lineno: Optional[int] = None) jinja2.nodes.Call
调用扩展的方法。 这是 attr() +
jinja2.nodes.Call
的快捷方式。
- filter_stream(stream: TokenStream) Union[TokenStream, Iterable[Token]]
它传递了一个 TokenStream,可用于过滤返回的令牌。 此方法必须返回 Token 的迭代,但它不必返回 TokenStream。
- parse(parser: Parser) Union[jinja2.nodes.Node, List[jinja2.nodes.Node]]
如果任何 tags 匹配,则使用解析器作为第一个参数调用此方法。 解析器流指向的标记是匹配的名称标记。 此方法必须返回一个或多个节点的列表。
- preprocess(source: str, name: Optional[str], filename: Optional[str] = None) str
此方法在实际词法分析之前调用,可用于预处理源。 文件名 是可选的。 返回值必须是预处理过的源。
解析器API
传递给 Extension.parse() 的解析器提供了解析不同类型表达式的方法。 扩展可以使用以下方法:
- class jinja2.parser.Parser(environment: Environment, source: str, name: Optional[str] = None, filename: Optional[str] = None, state: Optional[str] = None)
这是 Jinja 使用的中央解析类。 它传递给扩展,可用于解析表达式或语句。
- filename
解析器处理的模板的文件名。 这是 不是 模板的加载名称。 有关负载名称,请参阅 name。 对于未从文件系统加载的模板,这是 None。
- name
模板的加载名称。
- stream
当前的 TokenStream
- fail(msg: str, lineno: Optional[int] = None, exc: Type[jinja2.exceptions.TemplateSyntaxError] = <class 'jinja2.exceptions.TemplateSyntaxError'>) te.NoReturn
使用消息、传递的行号或最后一行号以及当前名称和文件名引发 exc 的便捷方法。
- free_identifier(lineno: Optional[int] = None) jinja2.nodes.InternalName
返回一个新的空闲标识符作为
InternalName
。
- parse_assign_target(with_tuple: bool = True, name_only: bool = False, extra_end_rules: Optional[Tuple[str, ...]] = None, with_namespace: bool = False) Union[jinja2.nodes.NSRef, jinja2.nodes.Name, jinja2.nodes.Tuple]
解析分配目标。 由于 Jinja 允许对元组进行赋值,因此该函数可以解析所有允许的赋值目标。 解析元组的默认分配,但是可以通过将 with_tuple 设置为 False 来禁用。 如果只需要给名称赋值 name_only 可以设置为 True。 extra_end_rules 参数被转发到元组解析函数。 如果启用 with_namespace,则可以解析命名空间分配。
- parse_expression(with_condexpr: bool = True) jinja2.nodes.Expr
解析表达式。 默认情况下,所有表达式都被解析,如果可选的 with_condexpr 参数设置为 False 条件表达式不被解析。
- parse_statements(end_tokens: Tuple[str, ...], drop_needle: bool = False) List[jinja2.nodes.Node]
将多个语句解析为一个列表,直到达到结束标记之一。 这用于解析语句体,因为它还会在适当的情况下解析模板数据。 解析器首先检查当前标记是否为冒号,如果有则跳过它。 然后它检查块结束并解析直到是否达到 end_tokens 之一。 默认情况下,调用结束时流中的活动标记是匹配的结束标记。 如果不需要 drop_needle 可以设置为 True 并删除结束标记。
- parse_tuple(simplified: bool = False, with_condexpr: bool = True, extra_end_rules: Optional[Tuple[str, ...]] = None, explicit_parentheses: bool = False) Union[jinja2.nodes.Tuple, jinja2.nodes.Expr]
像 parse_expression 一样工作,但如果多个表达式由逗号分隔,则会创建
Tuple
节点。 如果找不到逗号,此方法还可以返回正则表达式而不是元组。默认的解析模式是一个完整的元组。 如果 simplified 是 True,则仅解析名称和文字。 no_condexpr 参数被转发到 parse_expression()。
因为元组不需要分隔符并且可能以假逗号结尾,所以需要额外的提示来标记元组的结尾。 例如,for 循环支持 for 和 in 之间的元组。 在这种情况下,extra_end_rules 设置为
['name:in']
。explicit_parentheses 如果解析是由括号中的表达式触发的,则为真。 这用于确定空元组是否是有效表达式。
- class jinja2.lexer.TokenStream(generator: Iterable[jinja2.lexer.Token], name: Optional[str], filename: Optional[str])
令牌流是产生 令牌 的可迭代对象。 然而,解析器不会对其进行迭代,而是调用
next()
以前进一个标记。 当前活动令牌存储为 current。- current
当前的 Token。
- __next__() jinja2.lexer.Token
前进一个令牌并返回旧的。
使用内置的
next()
而不是直接调用它。
- property eos: bool
我们是在流的尽头吗?
- expect(expr: str) jinja2.lexer.Token
期望给定的令牌类型并返回它。 这接受与 jinja2.lexer.Token.test() 相同的参数。
- look() jinja2.lexer.Token
看下一个令牌。
- next_if(expr: str) Optional[jinja2.lexer.Token]
执行令牌测试并在匹配时返回令牌。 否则返回值为 None。
- push(token: jinja2.lexer.Token) None
将令牌推回流。
- skip(n: int = 1) None
前面有 n 个代币。
- skip_if(expr: str) bool
像 next_if() 但只返回 True 或 False。
- class jinja2.lexer.Token(lineno, type, value)
- lineno
令牌的行号
- type
令牌的类型。 这个字符串是实习的,因此您可以使用 is 运算符将其与任意字符串进行比较。
- value
代币的价值。
- test(expr: str) bool
针对令牌表达式测试令牌。 这可以是令牌类型或
'token_type:token_value'
。 这只能针对字符串值和类型进行测试。
- test_any(*iterable: str) bool
针对多个令牌表达式进行测试。
lexer 模块中还有一个实用函数,可以计算字符串中的换行符:
- jinja2.lexer.count_newlines(value: str) int
- 计算字符串中换行符的数量。 这对于过滤流的扩展很有用。
AST
AST(抽象语法树)用于表示解析后的模板。 它是节点的构建,然后编译器将其转换为可执行的 Python 代码对象。 提供自定义语句的扩展可以返回节点以执行自定义 Python 代码。
下面的列表描述了当前可用的所有节点。 AST 可能会在 Jinja2 版本之间发生变化,但会保持向后兼容。
有关更多信息,请查看 jinja2.Environment.parse() 的代表。
- exception jinja2.nodes.Impossible
- 如果节点无法执行请求的操作,则引发。