Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Jinja Extension that has access to Context

Tags:

python

jinja2

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.

like image 543
Niklas R Avatar asked Apr 19 '14 12:04

Niklas R


People also ask

What is context in Jinja2?

Context contains the dynamic content that you want to inject in your template when it is being rendered.

What is the difference between Jinja and Jinja2?

Jinja, also commonly referred to as "Jinja2" to specify the newest release version, is a Python template engine used to create HTML, XML or other markup formats that are returned to the user via an HTTP response.

What is Jinja file extension?

Synopsis. A Jinja template is simply a text file. Jinja can generate any text-based format (HTML, XML, CSV, LaTeX, etc.). A Jinja template doesn't need to have a specific extension: . html , .


2 Answers

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
like image 91
Niklas R Avatar answered Oct 20 '22 01:10

Niklas R


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
like image 22
Pedro M Duarte Avatar answered Oct 20 '22 00:10

Pedro M Duarte