Pregunta

En Jinja2, me gustaría que el siguiente para el trabajo, ya que parece que debería, ejecutando:

from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('x.html')
print template.render()

En esencia, el objetivo es fusionar todo el Javascript en las etiquetas <head> utilizando una macro de una {% call js() %} /* some js */ {% endcall %}.


x.html

<html>
<head>
  <script type="text/javascript>
  {% block head_js %}{% endblock %}
  </script>
  </head>
<body>
  {% include "y.html" %}
</body>
</html>

y.html

{% macro js() -%}
    // extend head_js
    {%- block head_js -%}
    {{ super() }}
    try { {{ caller() }} } catch (e) {
       my.log.error(e.name + ": " + e.message);
    }
    {%- endblock -%}
{%- endmacro %}

Some ... <div id="abc">text</div> ...

{% call js() %}
    // jquery parlance:
    $(function () {
        $("#abc").css("color", "red");
    });
{% endcall %}

Resultado esperado

Cuando corro x.html través Jinja2, yo esperaría que el resultado sea:

<html>
<head>
  <script type="text/javascript>
  try { {{ $("#abc").css("color", "red"); }} } catch (e) {
       usf.log.error(e.name + ": " + e.message);
    }
  </script>
  </head>
<body>
      Some ... <div id="abc">text</div> ...
</body>
</html>

Resultado real

Los resultados reales no son alentadoras. Consigo un par de tipos de errores potencialmente iluminando, por ejemplo:.

  

TypeError: 'js' macro toma ningún argumento palabra clave 'llama'

o, cuando trate de añadir otra base macro como

{% macro js2() -%}
{%- block head_js -%}
//     ... something
{%- endblock -%}
{%- endmacro %}

Me sale el siguiente excepción

  

jinja2.exceptions.TemplateAssertionError: bloque '' head_js definidos dos veces

Me siento como si estoy corriendo en un problema de diseño con respecto a la precedencia de las etiquetas block más de las etiquetas macro (es decir, las macros no parecen etiquetas de bloque encapsulado en el camino espero).


supongo que mis preguntas son bastante simples:

  1. Can Jinja2 hacer lo que estoy tratando? Si es así, ¿cómo?

  2. Si no es así, ¿hay otro motor de plantillas basado en Python que es compatible con esta clase de patrón (por ejemplo, mako, Genshi, etc.), que funcionaría sin la edición en Google App Engine

Gracias por leer - Aprecio su entrada

.

Brian


Edit:

Estoy intentando escribir una extensión para resolver este problema. Estoy a mitad de camino - usando el siguiente código:

from jinja2 import nodes, Environment, FileSystemLoader
from jinja2.ext import Extension

class JavascriptBuilderExtension(Extension):
    tags = set(['js', 'js_content'])

    def __init__(self, environment):
        super(JavascriptBuilderExtension, self).__init__(environment)
        environment.extend(
            javascript_builder_content = [],
        )

    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.next()
        return getattr(self, "_%s" % str(tag))(parser, tag)

    def _js_content(self, parser, tag):
        """ Return the output """
        content_list = self.environment.javascript_builder_content
        node = nodes.Output(lineno=tag.lineno)
        node.nodes = []

        for o in content_list:
            print "\nAppending node: %s" % str(o)
            node.nodes.extend(o[0].nodes)
        print "Returning node: %s \n" % node
        return node

    def _js(self, parser, tag):
        body = parser.parse_statements(['name:endjs'], drop_needle=True)
        print "Adding: %s" % str(body)
        self.environment.javascript_builder_content.append(body)
        return nodes.Const('<!-- Slurped Javascript -->')

env = Environment(
    loader      = FileSystemLoader('.'),
    extensions  = [JavascriptBuilderExtension],
    )

Esto hace que sea sencillo para añadir JavaScript al final de una plantilla ... por ejemplo.

<html>
<head></head>
<body>
    {% js %}
    some javascript {{ 3 + 5 }}
    {% endjs %}
    {% js %}
    more {{ 2 }}
    {% endjs %}

<script type="text/javascript">
{% js_content %}
</script>
</body>
</html>

Running env.get_template('x.html').render() dará lugar a algunos comentarios que iluminan y el resultado esperado de:

<html>
<head>
  <script type="text/javascript>
  </script>
  </head>
<body>
    <!-- Slurped Javascript -->
    <!-- Slurped Javascript -->
<script type="text/javascript">
    some javascript 8
    more 2
</script>
</body>
</html>

Por supuesto, esto no es lo mismo que tener el guión en la cabeza, como se esperaba, pero al menos está convenientemente unió en un solo lugar.

Sin embargo, la solución no es completa porque cuando se tiene un {% include "y.html" %} allí, donde "y.html" incluye una declaración {% js %}, la {% js_content %} se llama antes de la declaración de {% js %} incluyen (es decir x.html está totalmente procesadas antes que comience y.html).

También es necesario, pero aún no se han insertado los nodos constantes que tendría el try/catch Javascript estática, que he indicado que quería tener allí. Este no es un problema.

Me complace estar avanzando, y estoy agradecido por entrada.

He abierto la pregunta relacionada: Jinja2 compilación incluye extensión después


Editar

Solución

class JavascriptBuilderExtension(Extension):
    tags = set(['js'])

    def __init__(self, environment):
        super(JavascriptBuilderExtension, self).__init__(environment)
        environment.extend(jbc = "",)

    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.next()
        body = parser.parse_statements(['name:endjs'], drop_needle=True)
        return nodes.CallBlock(
            self.call_method('_jbc', [], [], None, None),
            [], [], body
        ).set_lineno(tag.lineno)

    def _jbc(self, caller=None):
        self.environment.jbc += "\ntry { %s } catch (e) { ; };" % caller()
        return "<!-- Slurped -->"

Después de completarse, el medio ambiente contendrá un jbc variable que tiene todo el Javascript. Puedo insertar este a través de, por ejemplo, string.Template.


¿Fue útil?

Solución

Desde mi comentario:

  

Si desea utilizar en lugar de extender   incluyes que podría hacerlo. Pero porque   de la separación completa entre el   analizar y dar paso no será   capaz de cambiar el contexto de la   ámbito padre hasta después de que sea demasiado tarde.   Además, el contexto se supone que Jinja   inmutable.

Ejemplo:

base.html

<html>
   <head>
      {% block head %}

      <title>{% block title %}This is the main template{% endblock %}</title>

      <script type="text/javascript">
      {% block head_js %}
      $(function () {
        $("#abc").css("color", "red");
      });
      {% endblock %}
      </script>

      {% endblock head_js %}
   </head>
   <body>
      {% block body %}
      <h1>{% block body_title %}This is the main template{% endblock body_title %}</h1>

      {% endblock body %}
   </body>
 </html>

some_page.html

{% block title %}This is some page{% endblock title %}

{% block head_js %}
{{ super() }}
try { {{ caller() }} } catch (e) {
   my.log.error(e.name + ": " + e.message);
}        // jquery parlance:
{% endblock head_js %}

Otros consejos

Se puede generalizar esto en una extensión de captura genérico que trabaja dentro de las macros. Aquí hay una que escribí:

from jinja2 import nodes
from jinja2.ext import Extension

class CaptureExtension(Extension):
    """
    Generic HTML capture, inspired by Rails' capture helper

    In any template, you can capture an area of content and store it in a global
    variable:

    {% contentfor 'name_of_variable' %}
        blah blah blah 
    {% endcontentfor %}

    To display the result
    {{ name_of_variable }}

    Multiple contentfor blocks will append additional content to any previously 
    captured content.  

    The context is global, and works within macros as well, so it's useful for letting macros define
    javascript or <head> tag content that needs to go at a particular position
    on the base template.

    Inspired by http://stackoverflow.com/questions/4292630/insert-javascript-at-top-of-including-file-in-jinja-2
    and http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
    """
    tags = set(['contentfor'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)

    def parse(self, parser):
        """Parse tokens """
        tag = parser.stream.next()
        args = [parser.parse_expression()]
        body = parser.parse_statements(['name:endcontentfor'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args),[], [], body).set_lineno(tag.lineno)

    def _capture(self, name, caller):
        if name not in self.environment.globals:
            self.environment.globals[name] = ''
        self.environment.globals[name] += caller()
        return ""

La solución por Lee Semel no funcionó para mí. Creo globales están protegidos de este tipo de modificación en tiempo de ejecución ahora.

from jinja2 import nodes
import jinja2
from jinja2.ext import Extension

class CaptureExtension(Extension):
    """
    Generic HTML capture, inspired by Rails' capture helper

    In any template, you can capture an area of content and store it in a global
    variable:

    {% capture 'name_of_variable' %}
        blah blah blah 
    {% endcapture %}
    {% capture 'a'  %}panorama{% endcapture %}

    To display the result
    {{ captured['name_of_variable'] }}
    {{ captured['a'] }}

    The context is global, and works within macros as well, so it's useful for letting macros define
    javascript or <head> tag content that needs to go at a particular position
    on the base template.

    Inspired by http://stackoverflow.com/questions/4292630/insert-javascript-at-top-of-including-file-in-jinja-2
    and http://api.rubyonrails.org/classes/ActionView/Helpers/CaptureHelper.html
    """
    tags = set(['capture'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)
        assert isinstance(environment, jinja2.Environment)
        self._myScope = {}
        environment.globals['captured'] = self._myScope

    def parse(self, parser):
        """Parse tokens """
        assert isinstance(parser, jinja2.parser.Parser)
        tag = parser.stream.next()
        args = [parser.parse_expression()]
        body = parser.parse_statements(['name:endcapture'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args),[], [], body).set_lineno(tag.lineno)

    def _capture(self, name, caller):
        self._myScope[name] = caller()
        return ""

Las respuestas anteriores casi contestado a mi pregunta (yo quería poner trozos dispares de JavaScript en un solo lugar - la parte inferior), aceptar el uso de la variedad '+ =' que añade capturas entre sí causado problemas en Actualizar. La captura terminaría con múltiples copias de todo y ha causado todo tipo de problemas, dependiendo de cuántas veces fue golpeado actualización.

He trabajado en torno a esta utilizando el número de línea de la etiqueta en un diccionario para asegurar capturas se realizan sólo una vez. La desventaja de menor importancia de este enfoque es las necesidades globales para ser reconstruido cada vez que se encuentra un taje de captura.

funciona bien para mí, sin embargo.

from jinja2 import Markup, nodes
from jinja2.ext import Extension

class CaptureExtension(Extension):
    tags = set(['capture'])

    def __init__(self, environment):
        super(CaptureExtension, self).__init__(environment)
        environment.globals['captured'] = {}
        self._captured = {}

    def parse(self, parser):
        lineno = next(parser.stream).lineno
        args = [parser.parse_expression(), nodes.Const(lineno)]
        body = parser.parse_statements(['name:endcapture'], drop_needle=True)
        return nodes.CallBlock(self.call_method('_capture', args), [], [], body).set_lineno(lineno)

    def _capture(self, name, lineno, caller):
        if name not in self._captured:
            self._captured[name] = {}
        self._captured[name][lineno] = caller()
        markup = Markup(''.join(s for s in self._captured[name].values()))
        self.environment.globals['captured'][name] = markup
        return ''
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top