Reputation: 16890
Is it possible to write a Jinja2 Extension that, while rendering, has access to the template context? I want to write an extension that accesses a context variable and outputs some data based on that variable. I couldn't find enough information on how to write such an extension.
Right now, I have this:
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)
But, in foo.jinja
<!DOCTYPE html>
<html>
<body>
<h1>This is a Test</h1>
{% csrf %}
</body>
</html>
I get
SyntaxError at /
invalid syntax (foo.jinja, line 7)
I thought I'd get a NameError since somehow_get_variable()
is not defined. I need to know a) how to get the variable from the current context, and b) how to write the Extension correctly.
Also, why line 7? The {% csrf %}
tag is on line 5. Even when I trim foo.jinja
to have only one line with the {% csrf %}
tag in it, it says line 7.
Upvotes: 6
Views: 2406
Reputation: 28133
I just ran into this problem and I found the solution to be decorating the call method with the @contextfunction
decorator. That tells the runtime to pass the active context as a first argument to the call method.
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
Upvotes: 1
Reputation: 16890
Found it. Seems like Jinja generates Python code from the Jinja AST (?) and that conversion failed, therefore caused the SyntaxError. jinja2.nodes.ContextReference()
can be used to get the render Context.
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
Upvotes: 13