Вставьте JavaScript вверху, включая файл в Jinja 2 - PullRequest
10 голосов
/ 27 ноября 2010

В Jinja2 я бы хотел, чтобы при работе с помощью команды

работало следующее:
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('x.html')
print template.render()

По сути, цель заключается в объединении всего javascript-кода в теги <head> с помощью макроса {% 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 %}

Ожидаемый результат

Когда я запускаю X.html через jinja2, я ожидаю, что результат будет:

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

Фактический результат

Фактические результаты не внушают оптимизма. Я получаю несколько типов потенциально освещающих ошибок, например ::1010

TypeError: макрос 'js' не принимает аргумента ключевого слова 'caller'

или, когда я пытаюсь добавить другой базовый макрос, такой как

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

я получаю следующее исключение

jinja2.exceptions.TemplateAssertionError: блок 'head_js', определенный дважды

Мне кажется, что я сталкиваюсь с проблемой проектирования, касающейся приоритета тегов block над тегами macro (т.е. макросы, похоже, не инкапсулируют блочные теги так, как я ожидаю).


Полагаю, мои вопросы довольно просты:

  1. Может ли Jinja2 делать то, что я пытаюсь? Если да, то как?

  2. Если нет, есть ли другой шаблонизатор на основе Python, который поддерживает этот тип паттернов (например, мако, гэнши и т. Д.), Который без проблем работал бы в Google App Engine

Спасибо за чтение - я ценю ваш вклад.

Brian


Редактировать:

Я пытаюсь написать расширение для решения этой проблемы. Я на полпути - использую следующий код:

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

Это упрощает добавление Javascript в конец шаблона ... например,

<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() приведет к некоторым осветительным комментариям и ожидаемому результату:

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

Конечно, это не то же самое, что иметь сценарий в голове, как мы надеялись, но, по крайней мере, его удобно объединить в одном месте.

Однако решение не является полным, потому что когда у вас есть {% include "y.html" %} там, где «y.html» включает в себя оператор {% js %}, {% js_content %} вызывается перед оператором {% js %} include (т.е. 1080 * полностью анализируется перед запуском y.html).

Мне также нужно, но еще не вставлять постоянные узлы, которые будут иметь статический javascript try/catch, который я указал, что я хотел бы иметь там. Это не проблема.

Я рад, что делаю успехи, и я благодарен за ввод.

Я открыл связанный вопрос: Расширение компиляции Jinja2 после включения


Редактировать

Решение

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

После завершения среда будет содержать переменную jbc, которая содержит весь Javascript. Я могу вставить это через, например, string.Template.


Ответы [ 4 ]

5 голосов
/ 30 ноября 2010

Из моего комментария:

Если вы используете расширение вместо включить вы могли бы сделать это. Но потому что полного разделения между разбор и рендеринг шага у тебя не будет возможность изменить контекст родительский охват, пока не стало слишком поздно. Кроме того, контекст Jinja должен быть неизменным.

Пример:

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 %}
2 голосов
/ 23 мая 2011

Вы можете обобщить это в универсальное расширение захвата, которое работает в макросах. Вот что я написал:

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 /3954690/vstavte-javascript-vverhu-vklychaya-fail-v-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 ""
1 голос
/ 26 февраля 2016

Ответы выше почти ответили на мой запрос (я хотел поместить разрозненные кусочки JavaScript в одном месте - снизу), примите вариант «+ =», который добавляет перехваты друг к другу вызванные проблемы на обновление. Захват мог закончиться несколькими копиями всего и вызывал всевозможные проблемы в зависимости от того, сколько раз было выполнено обновление.

Я обошел эту проблему, используя номер строки тега в словаре, чтобы гарантировать, что захват выполняется только один раз. Один незначительный недостаток этого подхода заключается в том, что глобальные потребности необходимо перестраивать каждый раз, когда происходит обнаружение захвата.

Хотя у меня хорошо работает.

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 ''
1 голос
/ 25 февраля 2013

Решение Ли Семеля у меня не сработало. Я думаю, что глобалы теперь защищены от такого рода изменений во время выполнения.

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 /3954690/vstavte-javascript-vverhu-vklychaya-fail-v-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 ""
...