Frage

In Jinja2, möchte ich folgendes zu arbeiten, wie es aussieht wie es sollte, indem Sie:

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

Im Wesentlichen das Ziel ist es, alle Javascript in die <head> Tags zu vereinigen, indem ein ein {% call js() %} /* some js */ {% endcall %} Makro.


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 %}

Erwartetes Ergebnis

Wenn ich X.html durch jinja2 laufen, würde ich das Ergebnis erwarten sein:

<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>

Die tatsächliche Ergebnis

Die tatsächlichen Ergebnisse sind nicht ermutigend. Ich bekomme ein paar Arten von potenziell Beleuchtungsfehler, z.

  

Typeerror: Makro 'js' nimmt kein Schlüsselwort-Argument 'Anrufer'

oder, wenn ich versuche, das Hinzufügen eines weiteren Basis-Makro wie

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

Ich erhalte die folgende Ausnahme

  

jinja2.exceptions.TemplateAssertionError: block 'head_js' definiert zweimal

Ich fühle mich, als ob ich in ein Design-Problem bin mit Bezug auf die Priorität der block Tags über die macro Tags (das heißt Makros scheinen nicht zu verkapseln Block-Tags in der Art, wie ich erwarten).


Ich nehme an, meine Fragen sind recht einfach:

  1. Can Jinja2 tun, was ich bin versucht? Wenn ja, wie?

  2. Wenn nicht, gibt es eine andere Python basierten Template-Engine, die diese Art von Muster jedoch zwingend nötig (z mako, genshi, etc.), die ohne Problem in Google App Engine

  3. funktionieren würde

Danke für das Lesen - ich schätze Ihre Eingabe

.

Brian


Edit:

Ich versuche, eine Erweiterung zu schreiben, um dieses Problem zu lösen. Ich bin auf halbem Weg dort - mit dem folgenden Code:

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],
    )

Dies macht es einfach Javascript Ende einer Vorlage hinzufügen ... z.

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

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

env.get_template('x.html').render() Lauf in einigen Beleuchtungs Kommentaren führen und die erwarteten Ausgabe von:

<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>

Natürlich, das ist nicht das gleiche wie das Skript im Kopf, wie erhofft, aber zumindest ist es bequem an einen Ort verschmolzen.

Allerdings ist die Lösung nicht vollständig, weil, wenn Sie einen {% include "y.html" %} dort haben, wo „y.html“ enthält eine {% js %} Aussage, die {% js_content %} aufgerufen wird, bevor die die {% js %} Erklärung enthalten (dh x.html vollständig vor y.html beginnt analysiert wird).

Ich brauche auch, aber noch nicht, konstant Knoten eingefügt, der die statische Javascript try/catch haben würde, die ich angedeutet ich dort haben wollte. Das ist kein Problem.

Ich bin froh, Fortschritte zu machen, und ich bin dankbar für die Eingabe.

Ich habe die damit verbundene Frage eröffnet: Jinja2 Kompilierung Erweiterung umfasst nach


Bearbeiten

Lösung

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 -->"

Nach der Fertigstellung wird die Umgebung eine Variable jbc enthalten, die alle Javascript. Ich kann diese über einfügen, beispielsweise string.Template.


War es hilfreich?

Lösung

Von meinem Kommentar:

  

Wenn Sie statt verwenden würden verlängern   schließen Sie es tun könnte. Aber weil   der vollständigen Trennung zwischen dem   analysieren und machen Schritt, den Sie nicht sein   Lage, den Kontext der Änderungen   geordneten Bereich bis nach dem es zu spät ist.   Auch wird der Jinja Kontext soll   sein unveränderlich.

Beispiel:

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 %}

Andere Tipps

Sie können diese in eine generische Erfassung Erweiterung verallgemeinern, die innerhalb von Makros funktioniert. Hier ist ein schrieb ich:

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 ""

Die Lösung von Lee Semel nicht für mich arbeiten. Ich denke, Globals von dieser Art der Änderung zur Laufzeit geschützt wird jetzt.

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 ""

Die Antworten oben fast beantwortet meine Frage (Ich wollte unterschiedliche Bits JavaScript, um alle an einem Ort setzen - unten), übernehmen mit der ‚+ =‘ Sorte, die verursacht Aufnahmen zueinander anfügt Probleme beim Aktualisieren. Die Einnahme würde mit mehreren Kopien von allem, was am Ende und verursacht alle möglichen Probleme, je nachdem wie oft Refresh Hit war.

ich gearbeitet, um dieses durch die Zeilennummer des Tags in einem Wörterbuch mit Aufnahmen zu gewährleisten, werden nur einmal getan. Der eine geringfügige Nachteil dieses Ansatzes ist, die globalen Bedürfnisse jedes Mal ein Capture-tage angetroffen wird wieder aufgebaut werden.

funktioniert gut für mich aber.

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 ''
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top