在Jinja2中,我希望通过以下方式运行以下内容:
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template('x.html')
print template.render()
基本上,目标是通过使用<head>
宏将所有javascript合并到{% call js() %} /* some js */ {% endcall %}
标记中。
<html>
<head>
<script type="text/javascript>
{% block head_js %}{% endblock %}
</script>
</head>
<body>
{% include "y.html" %}
</body>
</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 %}
当我通过jinja2运行X.html时,我希望结果是:
<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>
实际结果并不令人鼓舞。我得到了几种可能有启发性的错误,例如:
TypeError:macro'js'不带关键字参数'caller'
或者,当我尝试添加另一个基础宏,例如
{% macro js2() -%}
{%- block head_js -%}
// ... something
{%- endblock -%}
{%- endmacro %}
我收到以下异常
jinja2.exceptions.TemplateAssertionError:阻止'head_js'定义两次
我觉得我遇到了关于block
标签优先于macro
标签的设计问题(即宏似乎没有以我期望的方式封装块标签)。
我想我的问题很简单:
Jinja2可以做我正在尝试的事吗?如果是这样,怎么样?
如果没有,是否有另一个基于Python的模板引擎支持这种模式(例如mako,genshi等),它可以在Google App Engine中正常运行
感谢您阅读 - 感谢您的意见。
布赖恩
我正在尝试编写扩展程序来解决此问题。我在那里 - 使用以下代码:
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 %}
之前被调用{1}}语句(即x.html
在y.html
开始之前完全解析。
我还需要,但还没有,插入具有静态javascript try/catch
的常量节点,我表示我想要在那里。这不是问题。
我很高兴能取得进步,我很感激投入。
我已打开相关问题:Jinja2 compile extension after includes
解决方案
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 -->"
完成后,环境将包含一个包含所有Javascript的变量jbc
。我可以插入此内容,例如string.Template
。
答案 0 :(得分:5)
来自我的评论:
如果你使用extend而不是 包括你可以做到。但是因为 完全分离的 解析和渲染步骤你不会 能够改变的背景 父母的范围,直到它为时已晚。 此外,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 %}
答案 1 :(得分:2)
您可以将其概括为在宏中工作的通用捕获扩展。这是我写的一个:
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 ""
答案 2 :(得分:1)
Lee Semel的解决方案对我不起作用。我认为全局变量在运行时受到保护,不受此类修改的影响。
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 ""
答案 3 :(得分:1)
上面几乎的答案回答了我的问题(我想把不同的JavaScript全部放在一个地方 - 底部),接受使用&#39; + =&#39;互相追加捕获的多样性导致刷新问题。捕获最终将包含所有内容的多个副本,并根据刷新次数而导致各种问题。
我通过使用字典中标记的行号来解决这个问题,以确保捕获只进行一次。这种方法的一个小缺点是每次遇到捕获标记时都需要重建全局。
虽然适合我。
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 ''