Question

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.

Was it helpful?

Solution

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

OTHER TIPS

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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top