可以访问上下文的Jinja扩展
有没有办法写一个 Jinja2 扩展,让它在渲染模板的时候可以访问模板的上下文?我想写一个扩展,能够获取一个上下文变量,并根据这个变量输出一些数据。不过我找不到足够的信息来写这样的扩展。
现在,我有这个:
class CsrfExtension(jinja2.ext.Extension):
r""" Adds a {% csrf %} tag to Jinja. """
tags = set(['csrf'])
template = '<input type="hidden" name="csrfmiddlewaretoken" value="%s">'
def parse(self, parser):
token = next(parser.stream)
lineno = token.lineno
return self.call_method('_render_csrf', lineno=lineno)
def _render_csrf(self, value, name, *args, **kwargs):
csrf_token = somehow_get_variable('csrf_token')
return jinja2.Markup(self.template % csrf_token)
但是,在 foo.jinja
文件里
<!DOCTYPE html>
<html>
<body>
<h1>This is a Test</h1>
{% csrf %}
</body>
</html>
我得到了
SyntaxError at /
invalid syntax (foo.jinja, line 7)
我本以为会出现一个名称错误,因为 somehow_get_variable()
这个函数没有定义。我需要知道 a) 如何从当前上下文中获取变量,以及 b) 如何正确地写这个扩展。
还有,为什么是第 7 行?{% csrf %}
标签在第 5 行。即使我把 foo.jinja
剪裁成只有一行,里面只有 {% csrf %}
标签,它还是说是第 7 行。
2 个回答
1
我刚遇到这个问题,发现解决办法是给调用方法加上一个叫做@contextfunction
的装饰器。这个装饰器的作用是告诉运行时,把当前的上下文作为第一个参数传给调用方法。
from jinja2 import nodes
from jinja2.ext import Extension
from jinja2.utils import contextfunction
class MyExtension(Extension):
"""See http://jinja.pocoo.org/docs/2.10/extensions/#module-jinja2.ext
for more information on how to create custom Jinja extensions.
"""
tags = set(['myext'])
def __init__(self, environment):
super(MyExtension, self).__init__(environment)
def parse(self, parser):
lineno = next(parser.stream).lineno
# Parse args if you need them ...
# args = [parser.parse_expression()]
node = nodes.CallBlock(self.call_method('_myext_method', args),
[], [], []).set_lineno(lineno)
return parser.parse_import_context(node, True)
@contextfunction
def _myext_method(self, context, args, caller):
# Return what you need
return "Hello I am a custom extension, rendered with: %r" % context
13
找到了。看起来Jinja会从Jinja的抽象语法树(AST)生成Python代码,但这个转换失败了,所以导致了语法错误。你可以使用 jinja2.nodes.ContextReference()
来获取渲染的上下文。
class CsrfExtension(jinja2.ext.Extension):
r""" Adds a {% csrf %} tag to Jinja. """
tags = set(['csrf', 'csrf_token'])
template = u'<input type="hidden" name="csrfmiddlewaretoken" value="%s">'
def parse(self, parser):
lineno = next(parser.stream).lineno
ctx_ref = jinja2.nodes.ContextReference()
node = self.call_method('_render_csrf', [ctx_ref], lineno=lineno)
return jinja2.nodes.CallBlock(node, [], [], [], lineno=lineno)
def _render_csrf(self, context, caller):
csrf_token = context['csrf_token']
return jinja2.Markup(self.template % unicode(csrf_token))
csrf = CsrfExtension