forked from alioth310/itt2zh
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ch3.html
458 lines (420 loc) · 34.5 KB
/
ch3.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="utf-8">
<title>第三章:模板扩展 - Introduction to Tornado 中文翻译</title>
<meta name="author" content="你像从前一样">
<link href="./static/css/styles.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="sidebar">
<div class="indextable">
<ul class="index-chapter">
<li><a href="./index.html">索引页</a></li>
<li><a href="./ch1.html">第一章:引言</a></li>
<li><a href="./ch2.html">第二章:表单和模板</a></li>
<li><a href="./ch3.html" class="current-chapter">第三章:模板扩展</a></li>
<ul class="index-section">
<li><a href="#ch3-1">3.1 块和替换</a></li>
<ul class="index-section">
<li><a href="#ch3-1-1">3.1.1 块基础</a></li>
<li><a href="#ch3-1-2">3.1.2 模板练习:Burt's Book</a></li>
<li><a href="#ch3-1-3">3.1.3 自动转义</a></li>
</ul>
<li><a href="#ch3-2">3.2 UI模块</a></li>
<ul class="index-section">
<li><a href="#ch3-2-1">3.2.1 基础模块使用</a></li>
<li><a href="#ch3-2-2">3.2.2 模块深入</a></li>
<li><a href="#ch3-2-3">3.2.3 嵌入JavaScript和CSS</a></li>
</ul>
<li><a href="#ch3-3">3.3 总结</a></li>
</ul>
<li><a href="./ch4.html">第四章:数据库</a></li>
<li><a href="./ch5.html">第五章:异步Web服务</a></li>
<li><a href="./ch6.html">第六章:编写安全应用</a></li>
<li><a href="./ch7.html">第七章:外部服务认证</a></li>
<li><a href="./ch8.html">第八章:部署Tornado</a></li>
</ul>
</div>
</div>
<div class="article">
<h1>第三章:模板扩展<a class="headerlink" id="ch3" href="#ch3">¶</a></h1>
<p>在<a href="./ch2.html">第二章</a>中,我们看到了Tornado模板系统如何简单地传递信息给网页,使你在插入动态数据时保持网页标记的整洁。然而,大多数站点希望复用像header、footer和布局网格这样的内容。在这一章中,我们将看到如何使用扩展Tornado模板或UI模块完成这一工作。</p>
<h2>3.1 块和替换<a class="headerlink" id="ch3-1" href="#ch3-1">¶</a></h2>
<p>当你花时间为你的Web应用建立和制定模板时,希望像你的后端Python代码一样重用你的前端代码似乎只是合逻辑的,不是吗?幸运的是,Tornado可以让你做到这一点。Tornado通过<var>extends</var>和<var>block</var>语句支持模板继承,这就让你拥有了编写能够在合适的地方复用的流体模板的控制权和灵活性。</p>
<p>为了扩展一个已经存在的模板,你只需要在新的模板文件的顶部放上一句<code>{% extends "filename.html" %}。</code>比如,为了在新模板中扩展一个父模板(在这里假设为<span class="filename">main.html</span>),你可以这样使用:</p>
<pre class="codelist-code">{% extends "main.html" %}</pre>
<p>这就使得新文件继承<span class="filename">main.html</span>的所有标签,并且覆写为期望的内容。</p>
<h3>3.1.1 块基础<a class="headerlink" id="ch3-1-1" href="#ch3-1-1">¶</a></h3>
<p>扩展一个模板使你复用之前写过的代码更加简单,但是这并不会为你提供所有的东西,除非你可以适应并改变那些之前的模板。所以,<var>block</var>语句出现了。</p>
<p>一个块语句压缩了一些当你扩展时可能想要改变的模板元素。比如,为了使用一个能够根据不同页覆写的动态header块,你可以在父模板<span class="filename">main.html</span>中添加如下代码:</p>
<pre class="codelist-code"><header>
{% block header %}{% end %}
</header></pre>
<p>然后,为了在子模板<span class="filename">index.html</span>中覆写<code>{% block header %}{% end %}</code>部分,你可以使用块的名字引用,并把任何你想要的内容放到其中。</p>
<pre class="codelist-code">{% block header %}{% end %}
{% block header %}
<h1>Hello world!</h1>
{% end %}</pre>
<p>任何继承这个模板的文件都可以包含它自己的<code>{% block header %}</code>和<code>{% end %}</code>,然后把一些不同的东西加进去。</p>
<p>为了在Web应用中调用这个子模板,你可以在你的Python脚本中很轻松地渲染它,就像之前你渲染其他模板那样:</p>
<pre class="codelist-code">class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render("index.html")</pre>
<p>所以此时,<span class="filename">main.html</span>中的<var>body</var>块在加载时会被以<span class="filename">index.html</span>中的信息"Hello world!"填充(参见图3-1)。</p>
<div class="figure">
<img src="./static/images/Figure3-1.jpg" alt="图3-1" />
<p>图3-1 Hello world!</p>
</div>
<p>我们已经可以看到这种方法在处理整体页面结构和节约多页面网站的开发时间上多么有用。更好的是,你可以为每个页面使用多个块,此时像header和footer这样的动态元素将会被包含在同一个流程中。</p>
<p>下面是一个在父模板<span class="filename">main.html</span>中使用多个块的例子:</p>
<pre class="codelist-code"><html>
<body>
<header>
{% block header %}{% end %}
</header>
<content>
{% block body %}{% end %}
</content>
<footer>
{% block footer %}{% end %}
</footer>
</body>
</html></pre>
<p>当我们扩展父模板<span class="filename">main.html</span>时,可以在子模板<span class="filename">index.html</span>中引用这些块。</p>
<pre class="codelist-code">{% extends "main.html" %}
{% block header %}
<h1>{{ header_text }}</h1>
{% end %}
{% block body %}
<p>Hello from the child template!</p>
{% end %}
{% block footer %}
<p>{{ footer_text }}</p>
{% end %}</pre>
<p>用来加载模板的Python脚本和上一个例子差不多,不过在这里我们传递了几个字符串变量给模板使用(如图3-2):</p>
<pre class="codelist-code">class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"index.html",
header_text = "Header goes here",
footer_text = "Footer goes here"
)</pre>
<div class="figure">
<img src="./static/images/Figure3-2.jpg" alt="图3-2" />
<p>图3-2 块基础</p>
</div>
<p>你也可以保留父模板块语句中的默认文本和标记,就像扩展模板没有指定它自己的块版本一样被渲染。这种情况下,你可以根据某页的情况只替换必须的东西,这在包含或替换脚本、CSS文件和标记块时非常有用。</p>
<div class="warning">
<p>正如模板文档所记录的,"错误报告目前...呃...是非常有意思的"。一个语法错误或者没有闭合的<var>{% block %}</var>语句可以使得浏览器直接显示500: Internal Server Error(如果你运行在<var>debug</var>模式下会引发完整的Python堆栈跟踪)。如图3-3所示。</p>
<p>总之,为了你自己好的话,你需要使自己的模板尽可能的鲁棒,并且在模板被渲染之前发现错误。</p>
</div>
<div class="figure">
<img src="./static/images/Figure3-3.jpg" width="760" alt="图3-3" />
<p>图3-3 块错误</p>
</div>
<h3>3.1.2 模板练习:Burt's Book<a class="headerlink" id="ch3-1-2" href="#ch3-1-2">¶</a></h3>
<p>所以,你会认为这听起来很有趣,但却不能描绘出在一个标准的Web应用中如何使用?那么让我们在这里看一个例子,我们的朋友Burt希望运行一个名叫Burt's Books的书店。</p>
<p>Burt通过他的书店卖很多书,他的网站会展示很多不同的内容,比如新品推荐、商店信息等等。Burt希望有一个固定的外观和感觉的网站,同时也能更简单的更新页面和段落。</p>
<p>为了做到这些,Burt's Book使用了以Tornado为基础的网站,其中包括一个拥有样式、布局和header/footer细节的主模版,以及一个处理页面的轻量级的子模板。在这个系统中,Burt可以把最新发布、员工推荐、即将发行等不同页面编写在一起,共同使用通用的基础属性。</p>
<p>Burt's Book的网站使用一个叫作<span class="filename">main.html</span>的主要基础模板,用来包含网站的通用架构,如下面的代码所示:</p>
<pre class="codelist-code"><html>
<head>
<title>{{ page_title }}</title>
<link rel="stylesheet" href="{{ static_url("css/style.css") }}" />
</head>
<body>
<div id="container">
<header>
{% block header %}<h1>Burt's Books</h1>{% end %}
</header>
<div id="main">
<div id="content">
{% block body %}{% end %}
</div>
</div>
<footer>
{% block footer %}
<p>
For more information about our selection, hours or events, please email us at
<a href="mailto:[email protected]">[email protected]</a>.
</p>
{% end %}
</footer>
</div>
<script src="{{ static_url("js/script.js") }}"></script>
</body>
</html></pre>
<p>这个页面定义了结构,应用了一个CSS样式表,并加载了主要的JavaScript文件。其他模板可以扩展它,在必要时替换header、body和footer块。</p>
<p>这个网站的index页(<span class="filename">index.html</span>)欢迎友好的网站访问者并提供一些商店的信息。通过扩展<span class="filename">main.html</span>,这个文件只需要包括用于替换默认文本的header和body块的信息。</p>
<pre class="codelist-code">{% extends "main.html" %}
{% block header %}
<h1>{{ header_text }}</h1>
{% end %}
{% block body %}
<div id="hello">
<p>Welcome to Burt's Books!</p>
<p>...</p>
</div>
{% end %}</pre>
<p>在footer块中,这个文件使用了Tornado模板的默认行为,继承了来自父模板的联系信息。</p>
<p>为了运作网站,传递信息给index模板,下面给出Burt's Book的Python脚本(<span class="filename">main.py</span>):</p>
<pre class="codelist-code">import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
import os.path
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class Application(tornado.web.Application):
def __init__(self):
handlers = [
(r"/", MainHandler),
]
settings = dict(
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
debug=True,
)
tornado.web.Application.__init__(self, handlers, **settings)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"index.html",
page_title = "Burt's Books | Home",
header_text = "Welcome to Burt's Books!",
)
if __name__ == "__main__":
tornado.options.parse_command_line()
http_server = tornado.httpserver.HTTPServer(Application())
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()</pre>
<div class="tip">
<p>这个例子的结构和我们之前见到的不太一样,但你一点都不需要害怕。我们不再像之前那样通过使用一个处理类列表和一些其他关键字参数调用<var>tornado.web.Application</var>的构造函数来创建实例,而是定义了我们自己的Application子类,在这里我们简单地称之为<var>Application</var>。在我们定义的<var>__init__</var>方法中,我们创建了处理类列表以及一个设置的字典,然后在初始化子类的调用中传递这些值,就像下面的代码一样:</p>
<p><code>tornado.web.Application.__init__(self, handlers, **settings)</code></p>
</div>
<p>所以在这个系统中,Burt's Book可以很容易地改变index页面并保持基础模板在其他页面被使用时完好。此外,他们可以充分利用Tornado的真实能量,由Python脚本和/或数据库提供动态内容。我们将在之后看到更多相关的内容。</p>
<h3>3.1.3 自动转义<a class="headerlink" id="ch3-1-3" href="#ch3-1-3">¶</a></h3>
<p>Tornado默认会自动转义模板中的内容,把标签转换为相应的HTML实体。这样可以防止后端为数据库的网站被恶意脚本攻击。比如,你的网站中有一个评论部分,用户可以在这里添加任何他们想说的文字进行讨论。虽然一些HTML标签在标记和样式冲突时不构成重大威胁(如评论中没有闭<var><h1></var>标签),但<var><script></var>标签会允许攻击者加载其他的JavaScript文件,打开通向跨站脚本攻击、XSS或漏洞之门。</p>
<p>让我们考虑Burt's Book网站上的一个用户反馈页面。Melvin,今天感觉特别邪恶,在评论里提交了下面的文字:</p>
<pre class="codelist-code">Totally hacked your site lulz <script>alert('RUNNING EVIL H4CKS AND SPL01TS NOW...')</script></pre>
<p>当我们在没有转义用户内容的情况下给一个不知情的用户构建页面时,脚本标签被作为一个HTML元素解释,并被浏览器执行,所以Alice看到了如图3-4所示的提示窗口。幸亏Tornado会自动转义在双大括号间被渲染的表达式。更早地转义Melvin输入的文本不会激活HTML标签,并且会渲染为下面的字符串:</p>
<pre class="codelist-code">Totally hacked your site lulz &lt;script&gt;alert('RUNNING EVIL H4CKS AND SPL01TS NOW...')&lt;/script&gt;</pre>
<div class="figure">
<img src="./static/images/Figure3-4.jpg" width="760" alt="图3-4" />
<p>图3-4 网站漏洞问题</p>
</div>
<p>现在当Alice访问网站时,没有恶意脚本被执行,所以她看到的页面如图3-5所示。</p>
<div class="figure">
<img src="./static/images/Figure3-5.jpg" width="760" alt="图3-5" />
<p>图3-5 网站漏洞问题--解决</p>
</div>
<div class="warning">
<p>在Tornado1.x版本中,模板没有被自动转义,所以我们之前谈论的防护措施需要显式地在未过滤的用户输入上调用<var>escape()</var>函数。</p>
</div>
<p>所以在这里,我们可以看到自动转义是如何防止你的访客进行恶意攻击的。然而,当通过模板和模块提供HTML动态内容时它仍会让你措手不及。</p>
<p>举个例子,如果Burt想在footer中使用模板变量设置email联系链接,他将不会得到期望的HTML链接。考虑下面的模板片段:</p>
<pre class="codelist-code">{% set mailLink = "<a href="mailto:[email protected]">Contact Us</a>" %}
{{ mailLink }}'</pre>
<p>它会在页面源代码中渲染成如下代码:</p>
<pre class="codelist-code">&lt;a href=&quot;mailto:[email protected]&quot;&gt;Contact Us&lt;/a&gt;</pre>
<p>此时自动转义被运行了,很明显,这无法让人们联系上Burt。</p>
<p>为了处理这种情况,你可以禁用自动转义,一种方法是在Application构造函数中传递<var>autoescape=None</var>,另一种方法是在每页的基础上修改自动转义行为,如下所示:</p>
<pre class="codelist-code">{% autoescape None %}
{{ mailLink }}</pre>
<p>这些<var>autoescape</var>块不需要结束标签,并且可以设置<var>xhtml_escape</var>来开启自动转义(默认行为),或<var>None</var>来关闭。</p>
<p>然而,在理想的情况下,你希望保持自动转义开启以便继续防护你的网站。因此,你可以使用<var>{% raw %}</var>指令来输出不转义的内容。</p>
<pre class="codelist-code">{% raw mailLink %}</pre>
<p>需要特别注意的是,当你使用诸如Tornado的<var>linkify()</var>和<var>xsrf_form_html()</var>函数时,自动转义的设置被改变了。所以如果你希望在前面代码的footer中使用<var>linkify()</var>来包含链接,你可以使用一个<var>{% raw %}</var>块:</p>
<pre class="codelist-code">{% block footer %}
<p>
For more information about our selection, hours or events, please email us at
<a href="mailto:[email protected]">[email protected]</a>.
</p>
<p class="small">
Follow us on Facebook at
{% raw linkify("https://fb.me/burtsbooks", extra_params='ref=website') %}.
</p>
{% end %}</pre>
<p>这样,你可以既利用<var>linkify()</var>简记的好处,又可以保持在其他地方自动转义的好处。</p>
<h2>3.2 UI模块<a class="headerlink" id="ch3-2" href="#ch3-2">¶</a></h2>
<p>正如前面我们所看到的,模板系统既轻量级又强大。在实践中,我们希望遵循软件工程的谚语,<em>Don't Repeat Yourself</em>。为了消除冗余的代码,我们可以使模板部分模块化。比如,展示物品列表的页面可以定位一个单独的模板用来渲染每个物品的标记。另外,一组共用通用导航结构的页面可以从一个共享的模块渲染内容。Tornado的UI模块在这种情况下特别有用</p>
<p>UI模块是封装模板中包含的标记、样式以及行为的可复用组件。它所定义的元素通常用于多个模板交叉复用或在同一个模板中重复使用。模块本身是一个继承自Tornado的<var>UIModule</var>类的简单Python类,并定义了一个<var>render</var>方法。当一个模板使用<var>{% module Foo(...) %}</var>标签引用一个模块时,Tornado的模板引擎调用模块的<var>render</var>方法,然后返回一个字符串来替换模板中的模块标签。UI模块也可以在渲染后的页面中嵌入自己的JavaScript和CSS文件,或指定额外包含的JavaScript或CSS文件。你可以定义可选的<var>embedded_javascript</var>、<var>embedded_css</var>、<var>javascript_files</var>和<var>css_files</var>方法来实现这一方法。</p>
<h3>3.2.1 基础模块使用<a class="headerlink" id="ch3-2-1" href="#ch3-2-1">¶</a></h3>
<p>为了在你的模板中引用模块,你必须在应用的设置中声明它。<var>ui_moudles</var>参数期望一个模块名为键、类为值的字典输入来渲染它们。考虑代码清单3-1。</p>
<div class="codelist">
<div class="codelist-title">代码清单3-1 模块基础:hello_module.py</div>
<pre class="codelist-code">import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.options
import os.path
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class HelloHandler(tornado.web.RequestHandler):
def get(self):
self.render('hello.html')
class HelloModule(tornado.web.UIModule):
def render(self):
return '<h1>Hello, world!</h1>'
if __name__ == '__main__':
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[(r'/', HelloHandler)],
template_path=os.path.join(os.path.dirname(__file__), 'templates'),
ui_modules={'Hello': HelloModule}
)
server = tornado.httpserver.HTTPServer(app)
server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()</pre>
</div>
<p>这个例子中<var>ui_module</var>字典里只有一项,它把到名为<var>Hello</var>的模块的引用和我们定义的<var>HelloModule</var>类结合了起来。</p>
<p>现在,当调用<var>HelloHandler</var>并渲染<span class="filename">hello.html</span>时,我们可以使用<var>{% module Hello() %}</var>模板标签来包含<var>HelloModule</var>类中<var>render</var>方法返回的字符串。</p>
<pre class="codelist-code"><html>
<head><title>UI Module Example</title></head>
<body>
{% module Hello() %}
</body>
</html></pre>
<p>这个<span class="filename">hello.html</span>模板通过在模块标签自身的位置调用<var>HelloModule</var>返回的字符串进行填充。下一节的例子将会展示如何扩展UI模块来渲染它们自己的模板并包含脚本和样式表。</p>
<h3>3.2.2 模块深入<a class="headerlink" id="ch3-2-2" href="#ch3-2-2">¶</a></h3>
<p>很多时候,一个非常有用的做法是让模块指向一个模板文件而不是在模块类中直接渲染字符串。这些模板的标记看起来就像我们已经看到过的作为整体的模板。</p>
<p>UI模块的一个常见应用是迭代数据库或API查询中获得的结果,为每个独立项目的数据渲染相同的标记。比如,Burt想在Burt's Book里创建一个推荐阅读部分,他已经创建了一个名为<span class="filename">recommended.html</span>的模板,其代码如下所示。就像前面看到的那样,我们将使用<var>{% module Book(book) %}</var>标签调用模块。</p>
<pre class="codelist-code">{% extends "main.html" %}
{% block body %}
<h2>Recommended Reading</h2>
{% for book in books %}
{% module Book(book) %}
{% end %}
{% end %}</pre>
<p>Burt还创建了一个叫作<span class="filename">book.html</span>的图书模块的模板,并把它放到了<span class="filename">templates/modules</span>目录下。一个简单的图书模板看起来像下面这样:</p>
<pre class="codelist-code"><div class="book">
<h3 class="book_title">{{ book["title"] }}</h3>
<img src="{{ book["image"] }}" class="book_image"/>
</div></pre>
<p>现在,当我们定义<var>BookModule</var>类的时候,我们将调用继承自<var>UIModule</var>的<var>render_string</var>方法。这个方法显式地渲染模板文件,当我们返回给调用者时将其关键字参数作为一个字符串。</p>
<pre class="codelist-code">class BookModule(tornado.web.UIModule):
def render(self, book):
return self.render_string('modules/book.html', book=book)</pre>
<p>在完整的例子中,我们将使用下面的模板来格式化每个推荐书籍的所有属性,代替先前的<span class="filename">book.html</span></p>
<pre class="codelist-code"><div class="book">
<h3 class="book_title">{{ book["title"] }}</h3>
{% if book["subtitle"] != "" %}
<h4 class="book_subtitle">{{ book["subtitle"] }}</h4>
{% end %}
<img src="{{ book["image"] }}" class="book_image"/>
<div class="book_details">
<div class="book_date_released">Released: {{ book["date_released"]}}</div>
<div class="book_date_added">
Added: {{ locale.format_date(book["date_added"], relative=False) }}
</div>
<h5>Description:</h5>
<div class="book_body">{% raw book["description"] %}</div>
</div>
</div></pre>
<p>使用这个布局,传递给<span class="filename">recommended.html</span>模板的<var>books</var>参数的每项都将会调用这个模块。每次使用一个新的<var>book</var>参数调用<var>Book</var>模块时,模块(以及<span class="filename">book.html</span>模板)可以引用<var>book</var>参数的字典中的项,并以适合的方式格式化数据(如图3-6)。</p>
<div class="figure">
<img src="./static/images/Figure3-6.jpg" alt="图3-6" />
<p>图3-6 包含样式数据的图书模块</p>
</div>
<p>现在,我们可以定义一个<var>RecommendedHandler</var>类来渲染模板,就像你通常的操作那样。这个模板可以在渲染推荐书籍列表时引用<var>Book</var>模块。</p>
<pre class="codelist-code">class RecommendedHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"recommended.html",
page_title="Burt's Books | Recommended Reading",
header_text="Recommended Reading",
books=[
{
"title":"Programming Collective Intelligence",
"subtitle": "Building Smart Web 2.0 Applications",
"image":"/static/images/collective_intelligence.gif",
"author": "Toby Segaran",
"date_added":1310248056,
"date_released": "August 2007",
"isbn":"978-0-596-52932-1",
"description":"<p>This fascinating book demonstrates how you "
"can build web applications to mine the enormous amount of data created by people "
"on the Internet. With the sophisticated algorithms in this book, you can write "
"smart programs to access interesting datasets from other web sites, collect data "
"from users of your own applications, and analyze and understand the data once "
"you've found it.</p>"
},
...
]
)</pre>
<p>如果要用更多的模块,只需要简单地在<var>ui_modules</var>参数中添加映射值。因为模板可以指向任何定义在<var>ui_modules</var>字典中的模块,所以在自己的模块中指定功能非常容易。</p>
<div class="tip">
<p>在这个例子中,你可能已经注意到了<var>locale.format_date()</var>的使用。它调用了<var>tornado.locale</var>模块提供的日期处理方法,这个模块本身是一组i18n方法的集合。<var>format_date()</var>选项默认格式化GMT Unix时间戳为<code>XX time ago</code>,并且可以向下面这样使用:</p>
<p><code>{{ locale.format_date(book["date"]) }}</code></p>
<p><var>relative=False</var>将使其返回一个绝对时间(包含小时和分钟),而<var>full_format=True</var>选项将会展示一个包含月、日、年和时间的完整日期(比如,<code>July 9, 2011 at 9:47 pm</code>),当搭配<var>shorter=True</var>使用时可以隐藏时间,只显示月、日和年。</p>
<p>这个模块在你处理时间和日期时非常有用,并且还提供了处理本地化字符串的支持。</p>
</div>
<h3>3.2.3 嵌入JavaScript和CSS<a class="headerlink" id="ch3-2-3" href="#ch3-2-3">¶</a></h3>
<p>为了给这些模块提供更高的灵活性,Tornado允许你使用<var>embedded_css</var>和<var>embedded_javascript</var>方法嵌入其他的CSS和JavaScript文件。举个例子,如果你想在调用模块时给DOM添加一行文字,你可以通过从模块中嵌入JavaScript来做到:</var>
<pre class="codelist-code">class BookModule(tornado.web.UIModule):
def render(self, book):
return self.render_string(
"modules/book.html",
book=book,
)
def embedded_javascript(self):
return "document.write(\"hi!\")"</pre>
<p>当调用模块时,<var>document.write(\"hi!\")</var>将被<var><script></var>包围,并被插入到<var><body></var>的闭标签中:</p>
<pre class="codelist-code"><script type="text/javascript">
//<![CDATA[
document.write("hi!")
//]]>
</script></pre>
<p>显然,只是在文档主体中写这些内容并不是世界上最有用的事情,而我们还有另一个给予你极大灵活性的选项,当创建这些模块时,可以在每个模块中包含JavaScript文件。</p>
<p>类似的,你也可以把只在这些模块被调用时加载的额外的CSS规则放进来:</p>
<pre class="codelist-code">def embedded_css(self):
return ".book {background-color:#F5F5F5}"</pre>
<p>在这种情况下,<code>.book {background-color:#555}</code>这条CSS规则被包裹在<var><style></var>中,并被直接添加到<var><head></var>的闭标签之前。</p>
<pre class="codelist-code"><style type="text/css">
.book {background-color:#F5F5F5}
</style></pre>
<p>更加灵活的是,你甚至可以简单地使用<var>html_body()</var>来在闭合的<var></body></var>标签前添加完整的HTML标记:</p>
<pre class="codelist-code">def html_body(self):
return "<script>document.write("Hello!")</script>"</pre>
<p>显然,虽然直接内嵌添加脚本和样式表很有用,但是为了更严谨的包含(以及更整洁的代码!),添加样式表和脚本文件会显得更好。他们的工作方式基本相同,所以你可以使用<var>javascript_files()</var>和<var>css_files()</var>来包含完整的文件,不论是本地的还是外部的。</p>
<p>比如,你可以添加一个额外的本地CSS文件如下:</p>
<pre class="codelist-code">def css_files(self):
return "/static/css/newreleases.css"</pre>
<p>或者你可以取得一个外部的JavaScript文件:</p>
<pre class="codelist-code">def javascript_files(self):
return "https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.14/jquery-ui.min.js"</pre>
<p>当一个模块需要额外的库而应用的其他地方不是必需的时候,这种方式非常有用。比如,你有一个使用JQuery UI库的模块(而在应用的其他地方都不会被使用),你可以只在这个样本模块中加载<span class="filename">jquery-ui.min.js</span>文件,减少那些不需要它的页面的加载时间。</p>
<div class="warning">
<p>因为模块的内嵌JavaScript和内嵌HTML函数的目标都是紧邻<var></body></var>标签,<var>html_body()</var>、<var>javascript_files()</var>和<var>embedded_javascript()</var>都会将内容渲染后插到页面底部,那么它们出现的顺序正好是你指定它们的顺序的倒序。</p>
<p>如果你有一个模块如下面的代码所示:</p>
<p><pre class="codelist-code">class SampleModule(tornado.web.UIModule):
def render(self, sample):
return self.render_string(
"modules/sample.html",
sample=sample
)
def html_body(self):
return "<div class=\"addition\"><p>html_body()</p></div>"
def embedded_javascript(self):
return "document.write(\"<p>embedded_javascript()</p>\")"
def embedded_css(self):
return ".addition {color: #A1CAF1}"
def css_files(self):
return "/static/css/sample.css"
def javascript_files(self):
return "/static/js/sample.js"</pre>
<p><var>html_body()</var>最先被编写,它紧挨着出现在<var></body></var>标签的上面。<var>embedded_javascript()</var>接着被渲染,最后是<var>javascript_files()</var>。你可以在图3-7中看到它是如何工作的。</p>
<p>需要小心的是,你不能包括一个需要其他地方东西的方法(比如依赖其他文件的JavaScript函数),因为此时他们可能会按照和你期望不同的顺序进行渲染。</p>
</div>
<p>总之,模块允许你在模板中渲染格式化数据时非常灵活,同时也让你能够只在调用模块时包含指定的一些额外的样式和函数规则。</p>
<h3>3.3 总结<a class="headerlink" id="ch3-3" href="#ch3-3">¶</a></h3>
<p>正如我们之前看到的,Tornado使扩展模板更容易,以便你的网站代码可以在整个应用中轻松复用。而使用模块后,你可以在什么文件、样式和脚本动作需要被包括进来这个问题上拥有更细粒度的决策。然而,我们的例子依赖于使用Python原生数据结构时是否简单,在你的实际应用中硬编码大数据结构的感觉可不好。下一步,我们将看到如何配合持久化存储来处理存储、提供和编辑动态内容。</p>
</div>
<div class="footer">
<small>© 本文由<a href="http://www.pythoner.com">你像从前一样</a>翻译,转载请注明出处。本书版权由原作者拥有。</small>
</div>
</div>
</body>
</html>