forked from alioth310/itt2zh
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ch2.html
410 lines (379 loc) · 31.1 KB
/
ch2.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
<!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" class="current-chapter">第二章:表单和模板</a></li>
<ul class="index-section">
<li><a href="#ch2-1">2.1 简单示例:Poem Maker Pro</a></li>
<ul class="index-section">
<li><a href="#ch2-1-1">2.1.1 渲染模板</a></li>
<li><a href="#ch2-1-2">2.1.2 填充</a></li>
</ul>
<li><a href="#ch2-2">2.2 模板语法</a></li>
<ul class="index-section">
<li><a href="#ch2-2-1">2.2.1 填充表达式</a></li>
<li><a href="#ch2-2-2">2.2.2 控制流语句</a></li>
<li><a href="#ch2-2-3">2.2.3 在模板中使用函数</a></li>
</ul>
<li><a href="#ch2-3">2.3 复杂示例:The Alpha Munger</a></li>
<ul class="index-section">
<li><a href="#ch2-3-1">2.3.1 它如何工作</a></li>
<li><a href="#ch2-3-2">2.3.2 提供静态文件</a></li>
<ul class="index-section">
<li><a href="#ch2-3-2-1">2.3.2.1 设置静态路径</a></li>
<li><a href="#ch2-3-2-2">2.3.2.2 使用static_url生成静态URL</a></li>
</ul>
<li><a href="#ch2-3-3">2.3.3 模板的下一步</a></li>
</ul>
</ul>
<li><a href="./ch3.html">第三章:模板扩展</a></li>
<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="ch2" href="#ch2">¶</a></h1>
<p>在<a href="./ch1.html">第一章</a>中,我们学习了使用Tornado创建一个Web应用的基础知识。包括处理函数、HTTP方法以及Tornado框架的总体结构。在这章中,我们将学习一些你在创建Web应用时经常会用到的更强大的功能。</p>
<p>和大多数Web框架一样,Tornado的一个重要目标就是帮助你更快地编写程序,尽可能整洁地复用更多的代码。尽管Tornado足够灵活,可以使用几乎所有Python支持的模板语言,Tornado自身也提供了一个轻量级、快速并且灵活的模板语言在<var>tornado.template</var>模块中。</p>
<h2>2.1 简单示例:Poem Maker Pro<a class="headerlink" id="ch2-1" href="#ch2-1">¶</a></h2>
<p>让我们以一个叫作<span class="examplename">Poem Maker Pro</span>的简单例子开始。Poem Maker Pro这个Web应用有一个让用户填写的HTML表单,然后处理表单的结果。代码清单2-1是它的Python代码。</p>
<div class="codelist">
<div class="codelist-title">代码清单2-1 简单表单和模板:poemmaker.py</div>
<pre class="codelist-code">import os.path
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
class PoemPageHandler(tornado.web.RequestHandler):
def post(self):
noun1 = self.get_argument('noun1')
noun2 = self.get_argument('noun2')
verb = self.get_argument('verb')
noun3 = self.get_argument('noun3')
self.render('poem.html', roads=noun1, wood=noun2, made=verb,
difference=noun3)
if __name__ == '__main__':
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[(r'/', IndexHandler), (r'/poem', PoemPageHandler)],
template_path=os.path.join(os.path.dirname(__file__), "templates")
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()</pre>
</div>
<p>除了<span class="filename">poemmaker.py</span>,你还需要将代码清单2-2和代码清单2-3中的两个文件加入到<span class="filename">templates</span>子文件夹中。</p>
<div class="codelist">
<div class="codelist-title">代码清单2-2 Poem Maker表单:index.html</div>
<pre class="codelist-code"><!DOCTYPE html>
<html>
<head><title>Poem Maker Pro</title></head>
<body>
<h1>Enter terms below.</h1>
<form method="post" action="/poem">
<p>Plural noun<br><input type="text" name="noun1"></p>
<p>Singular noun<br><input type="text" name="noun2"></p>
<p>Verb (past tense)<br><input type="text" name="verb"></p>
<p>Noun<br><input type="text" name="noun3"></p>
<input type="submit">
</form>
</body>
</html></pre>
</div>
<p></p>
<div class="codelist">
<div class="codelist-title">代码清单2-3 Poem Maker模板:poem.html</div>
<pre class="codelist-code"><!DOCTYPE html>
<html>
<head><title>Poem Maker Pro</title></head>
<body>
<h1>Your poem</h1>
<p>Two {{roads}} diverged in a {{wood}}, and I—<br>
I took the one less travelled by,<br>
And that has {{made}} all the {{difference}}.</p>
</body>
</html></pre>
</div>
<p>在命令行执行下述命令:</p>
<pre class="shell">$ <kbd>python poemmaker.py --port=8000</kbd></pre>
<p>现在,在浏览器中打开<a href="http://localhost:8000">http://localhost:8000</a>。当浏览器请求根目录(/)时,Tornado程序将渲染<span class="filename">index.html</span>,展示如图2-1所示的简单HTML表单。</p>
<div class="figure">
<img src="./static/images/Figure2-1.jpg" alt="图2-1" />
<p>图2-1 Poem Maker Pro:输入表单</p>
</div>
<p>这个表单包括多个文本域(命名为<var>noun1</var>、<var>noun2</var>等),其中的内容将在用户点击"Submit"按钮时以<dfn>POST</dfn>请求的方式送到<code>/poem</code>。现在往里面填写东西然后点击提交吧。</p>
<p>为了响应这个<dfn>POST</dfn>请求,Tornado应用跳转到<span class="filename">poem.html</span>,插入你在表单中填写的值。结果是Robert Frost的诗《The Road Not Taken》的轻微修改版本。图2-2展示了这个结果。</p>
<div class="figure">
<img src="./static/images/Figure2-2.jpg" alt="图2-2" />
<p>图2-2 Poem Maker Pro:输出</p>
</div>
<h3>2.1.1 渲染模板<a class="headerlink" id="ch2-1-1" href="#ch2-1-1">¶</a></h3>
<p>从结构上讲,<var>poemmaker.py</var>和<a href="./ch1.html">第一章</a>中的例子很相似。我们定义了几个<var>RequestHandler</var>子类并把它们传给<var>tornado.web.Application</var>对象。那么有什么不一样的地方呢?首先,我们向<var>Application</var>对象的<var>__init__</var>方法传递了一个<var>template_path</var>参数。</p>
<pre class="codelist-code">template_path=os.path.join(os.path.dirname(__file__), "templates")</pre>
<p><var>template_path</var>参数告诉Tornado在哪里寻找<span class="filename">模板文件</span>。我们将在本章和<a href="./ch3.html">第三章</a>中讲解其确切性质和语法,而它的基本要点是:模板是一个允许你嵌入Python代码片段的HTML文件。上面的代码告诉Python在你Tornado应用文件同目录下的<span class="filename">templates</span>文件夹中寻找模板文件。</p>
<p>一旦我们告诉Tornado在哪里找到模板,我们可以使用<var>RequestHandler</var>类的<var>render</var>方法来告诉Tornado读入模板文件,插入其中的模版代码,并返回结果给浏览器。比如,在<var>IndexHandler</var>中,我们发现了下面的语句:</p>
<pre class="codelist-code">self.render('index.html')</pre>
<p>这段代码告诉Tornado在<span class="filename">templates</span>文件夹下找到一个名为<span class="filename">index.html</span>的文件,读取其中的内容,并且发送给浏览器。</p>
<h3>2.1.2 填充<a class="headerlink" id="ch2-1-2" href="#ch2-1-2">¶</a></h3>
<p>实际上<span class="filename">index.html</span>完全不能称之为"模板",它所包含的完全是已编写好的HTML标记。这可以是模板的一个不错的使用方式,但在更通常的情况下我们希望HTML输出可以结合我们的程序传入给模板的值。模板<span class="filename">poem.html</span>使用<var>PoemPageHandler</var>渲染,是这种方式的一个很好的例子。让我们看看它是如何工作的吧。</p>
<p>在<span class="filename">poem.html</span>中,你可以看到模板中有一些被双大括号({{和}})括起来的字符串,就像这样:</p>
<pre class="codelist-code"><p>Two {{roads}} diverged in a {{wood}}, and I—<br/>
I took the one less travelled by,<br>
And that has {{made}} all the {{difference}}.</p></pre>
<p>在双大括号中的单词是占位符,当我们渲染模板时希望以实际值代替。我们可以使用向<var>render</var>函数中传递关键字参数的方法指定什么值将被填充到HTML文件中的对应位置,其中关键字对应模板文件中占位符的名字。下面是在<var>PoemPageHandler</var>中相应的代码部分:</p>
<pre class="codelist-code">noun1 = self.get_argument('noun1')
noun2 = self.get_argument('noun2')
verb = self.get_argument('verb')
noun3 = self.get_argument('noun3')
self.render('poem.html', roads=noun1, wood=noun2, made=verb, difference=noun3)</pre>
<p>在这里,我们告诉模板使用变量<var>noun1</var>(该变量是从<var>get_argument</var>方法取得的)作为模板中<var>roads</var>的值,<var>noun2</var>作为模板中<var>wood</var>的值,依此类推。假设用户在表单中按顺序键入了<kbd>pineapples</kbd>、<kbd>grandfather clock</kbd>、<kbd>irradiated</kbd>和<kbd>supernovae</kbd>,那么结果HTML将会如下所示:</p>
<pre class="shell"><p>Two pineapples diverged in a grandfather clock, and I—<br>
I took the one less travelled by,<br>
And that has irradiated all the supernovae.</p></pre>
<h2>2.2 模板语法<a class="headerlink" id="ch2-2" href="#ch2-2">¶</a></h2>
<p>既然我们已经看到了一个模板在实际应用中的简单例子,那么让我们深入地了解它们是如何工作的吧。Tornado模板是被Python表达式和控制语句标记的简单文本文件。Tornado的语法非常简单直接。熟悉Django、Liquid或其他相似框架的用户会发现它们非常相似,很容易学会。</p>
<p>在2.1节中,我们展示了如何在一个Web应用中使用<var>render</var>方法传送HTML给浏览器。你可以在Tornado应用之外使用Python解释器导入模板模块尝试模板系统,此时结果会被直接输出出来。</p>
<pre class="shell">>>> <kbd>from tornado.template import Template</kbd>
>>> <kbd>content = Template("<html><body><h1>{{ header }}</h1></body></html>")</kbd>
>>> <kbd>print content.generate(header="Welcome!")</kbd>
<html><body><h1>Welcome!</h1></body></html></pre>
<h3>2.2.1 填充表达式<a class="headerlink" id="ch2-2-1" href="#ch2-2-1">¶</a></h3>
<p>在代码清单2-1中,我们演示了填充Python变量的值到模板的双大括号中的使用。实际上,你可以将任何Python表达式放在双大括号中。Tornado将插入一个包含任何表达式计算结果值的字符串到输出中。下面是几个可能的例子:</p>
<pre class="shell">>>> <kbd>from tornado.template import Template</kbd>
>>> <kbd>print Template("{{ 1+1 }}").generate()</kbd>
2
>>> <kbd>print Template("{{ 'scrambled eggs'[-4:] }}").generate()</kbd>
eggs
>>> <kbd>print Template("{{ ', '.join([str(x*x) for x in range(10)]) }}").generate()</kbd>
0, 1, 4, 9, 16, 25, 36, 49, 64, 81</pre>
<h3>2.2.2 控制流语句<a class="headerlink" id="ch2-2-2" href="#ch2-2-2">¶</a></h3>
<p>你同样可以在Tornado模板中使用Python条件和循环语句。控制语句以{%和%}包围,并以类似下面的形式被使用:</p>
<pre class="codelist-code">{% if page is None %}</pre>
<p>或</p>
<pre class="codelist-code">{% if len(entries) == 3 %}</pre>
<p>控制语句的大部分就像对应的Python语句一样工作,支持<var>if</var>、<var>for</var>、<var>while</var>和<var>try</var>。在这些情况下,语句块以{%开始,并以%}结束。</p>
<p>所以这个模板:</p>
<pre class="codelist-code"><html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ header }}</h1>
<ul>
{% for book in books %}
<li>{{ book }}</li>
{% end %}
</ul>
</body>
</html></pre>
<p>当被下面这个处理函数调用时:</p>
<pre class="codelist-code">class BookHandler(tornado.web.RequestHandler):
def get(self):
self.render(
"book.html",
title="Home Page",
header="Books that are great",
books=[
"Learning Python",
"Programming Collective Intelligence",
"Restful Web Services"
]
)</pre>
<p>将会渲染得到下面的输出:</p>
<pre class="codelist-code"><html>
<head>
<title>Home Page</title>
</head>
<body>
<h1>Books that are great</h1>
<ul>
<li>Learning Python</li>
<li>Programming Collective Intelligence</li>
<li>Restful Web Services</li>
</ul>
</body>
</html></pre>
<p>不像许多其他的Python模板系统,Tornado模板语言的一个最好的东西是在<var>if</var>和<var>for</var>语句块中可以使用的表达式没有限制。因此,你可以在你的模板中执行所有的Python代码。</p>
<p>同样,你也可以在你的控制语句块中间使用<code>{% set foo = 'bar' %}</code>来设置变量。你还有很多可以在控制语句块中做的事情,但是在大多数情况下,你最好使用UI模块来做更复杂的划分。我们稍后会更详细的看到这一点。</p>
<h3>2.2.3 在模板中使用函数<a class="headerlink" id="ch2-2-3" href="#ch2-2-3">¶</a></h3>
<p>Tornado在所有模板中默认提供了一些便利的函数。它们包括:</p>
<h5><var>escape(s)</var></h5>
<p class="indentation">替换字符串s中的&、<、>为他们对应的HTML字符。</p>
<h5><var>url_escape(s)</var></h5>
<p class="indentation">使用<var>urllib.quote_plus</var>替换字符串s中的字符为URL编码形式。</p>
<h5><var>json_encode(val)</var></h5>
<p class="indentation">将val编码成JSON格式。(在系统底层,这是一个对<var>json</var>库的<var>dumps</var>函数的调用。查阅相关的文档以获得更多关于该函数接收和返回参数的信息。)</p>
<h5><var>squeeze(s)</var></h5>
<p class="indentation">过滤字符串s,把连续的多个空白字符替换成一个空格。</p>
<div class="warning">
<p>在Tornado 1.x中,模版不是被自动转义的。在Tornado 2.0中,模板被默认为自动转义(并且可以在<var>Application</var>构造函数中使用<var>autoscaping=None</var>关闭)。在不同版本的迁移时要注意向后兼容。</p>
</div>
<p>在模板中使用一个你自己编写的函数也是很简单的:只需要将函数名作为模板的参数传递即可,就像其他变量一样。</p>
<pre class="shell">>>> <kbd>from tornado.template import Template</kbd>
>>> <kbd>def disemvowel(s):</kbd>
... <kbd>return ''.join([x for x in s if x not in 'aeiou'])</kbd>
...
>>> <kbd>disemvowel("george")</kbd>
'grg'
>>> <kbd>print Template("my name is {{d('mortimer')}}").generate(d=disemvowel)</kbd>
my name is mrtmr</pre>
<h2>2.3 复杂示例:The Alpha Munger<a class="headerlink" id="ch2-3" href="#ch2-3">¶</a></h2>
<p>在代码清单2-4中,我们把在这一章中谈论过的所有东西都放了进来。这个应用被称为<span class="examplename">The Alpha Munger</span>。用户输入两个文本:一个"源"文本和一个"替代"文本。应用会返回替代文本的一个副本,并将其中每个单词替换成源文本中首字母相同的某个单词。图2-3展示了要填的表单,图2-4展示了结果文本。</p>
<p>这个应用包括四个文件:<span class="filename">main.py</span>(Tornado程序)、<span class="filename">style.css</span>(CSS样式表文件)、<span class="filename">index.html</span>和<span class="filename">munged.html</span>(Tornado模板)。让我们看看代码吧:</p>
<div class="codelist">
<div class="codelist-title">代码清单2-4 复杂表单和模板:main.py</div>
<pre class="codelist-code">import os.path
import random
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class IndexHandler(tornado.web.RequestHandler):
def get(self):
self.render('index.html')
class MungedPageHandler(tornado.web.RequestHandler):
def map_by_first_letter(self, text):
mapped = dict()
for line in text.split('\r\n'):
for word in [x for x in line.split(' ') if len(x) > 0]:
if word[0] not in mapped: mapped[word[0]] = []
mapped[word[0]].append(word)
return mapped
def post(self):
source_text = self.get_argument('source')
text_to_change = self.get_argument('change')
source_map = self.map_by_first_letter(source_text)
change_lines = text_to_change.split('\r\n')
self.render('munged.html', source_map=source_map, change_lines=change_lines,
choice=random.choice)
if __name__ == '__main__':
tornado.options.parse_command_line()
app = tornado.web.Application(
handlers=[(r'/', IndexHandler), (r'/poem', MungedPageHandler)],
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
debug=True
)
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()</pre>
</div>
<div class="figure">
<img src="./static/images/Figure2-3.jpg" alt="图2-3" />
<p>图2-3 Alpha Munger:输入表单</p>
</div>
<div class="figure">
<img src="./static/images/Figure2-4.jpg" alt="图2-4" />
<p>图2-4 Alpha Munger:输出</p>
</div>
<p>记住<var>Application</var>构造函数中的<var>static_path</var>参数。我们将在下面进行详细的介绍,但是现在你所需要知道的就是<var>static_path</var>参数指定了你应用程序放置静态资源(如图像、CSS文件、JavaScript文件等)的目录。另外,你还需要在<span class="filename">templates</span>文件夹下添加<span class="filename">index.html</span>和<span class="filename">munged.html</span>这两个文件。</p>
<div class="codelist">
<div class="codelist-title">代码清单2-5 Alpha Munger表单:index.html</div>
<pre class="codelist-code"><!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{{ static_url("style.css") }}">
<title>The Alpha Munger</title>
</head>
<body>
<h1>The Alpha Munger</h1>
<p>Enter two texts below. The replacement text will have its words
replaced by words beginning with the same letter in the source text.</p>
<form method="post" action="/poem">
<p>Source text<br>
<textarea rows=4 cols=55 name="source"></textarea></p>
<p>Text for replacement<br>
<textarea rows=4 cols=55 name="change"></textarea></p>
<input type="submit">
</form>
</body>
</html></pre>
</div>
<p></p>
<div class="codelist">
<div class="codelist-title">代码清单2-6 Alpha Munger模板:munged.html</div>
<pre class="codelist-code"><!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{{ static_url("style.css") }}">
<title>The Alpha Munger</title>
</head>
<body>
<h1>Your text</h1>
<p>
{% for line in change_lines %}
{% for word in line.split(' ') %}
{% if len(word) > 0 and word[0] in source_map %}
<span class="replaced"
title="{{word}}">{{ choice(source_map[word[0]]) }}</span>
{% else %}
<span class="unchanged" title="unchanged">{{word}}</span>
{% end %}
{% end %}
<br>
{% end %}
</p>
</body>
</html></pre>
</div>
<p>最后,将代码清单2-7中的内容写到<span class="filename">static</span>子目录下的<span class="filename">style.css</span>文件中。</p>
<h3>2.3.1 它如何工作<a class="headerlink" id="ch2-3-1" href="#ch2-3-1">¶</a></h3>
<p>这个Tornado应用定义了两个请求处理类:<var>IndexHandler</var>和<var>MungedPageHandler</var>。<var>IndexHandler</var>类简单地渲染了<span class="filename">index.html</span>中的模板,其中包括一个允许用户<dfn>POST</dfn>一个源文本(在<var>source</var>域中)和一个替换文本(在<var>change</var>域中)到<code>/poem</code>的表单。</p>
<p><var>MungedPageHandler</var>类用于处理到<code>/poem</code>的<dfn>POST</dfn>请求。当一个请求到达时,它对传入的数据进行一些基本的处理,然后为浏览器渲染模板。<var>map_by_first_letter</var>方法将传入的文本(从<var>source</var>域)分割成单词,然后创建一个字典,其中每个字母表中的字母对应文本中所有以其开头的单词(我们将其放入一个叫作<var>source_map</var>的变量)。再把这个字典和用户在替代文本(表单的<var>change</var>域)中指定的内容一起传给模板文件<span class="filename">munged.html</span>。此外,我们还将Python标准库的<var>random.choice</var>函数传入模板,这个函数以一个列表作为输入,返回列表中的任一元素。</p>
<p>在<span class="filename">munged.html</span>中,我们迭代替代文本中的每行,再迭代每行中的每个单词。如果当前单词的第一个字母是<var>source_map</var>字典的一个键,我们使用<var>random.choice</var>函数从字典的值中随机选择一个单词并展示它。如果字典的键中没有这个字母,我们展示源文本中的原始单词。每个单词包括一个<var>span</var>标签,其中的<var>class</var>属性指定这个单词是替换后的(<var>class="replaced"</var>)还是原始的(<var>class="unchanged"</var>)。(我们还将原始单词放到了<var>span</var>标签的<var>title</var>属性中,以便于用户在鼠标经过单词时可以查看是什么单词被替代了。你可以在图2-5中看到这个动作。)</p>
<div class="figure">
<img src="./static/images/Figure2-5.jpg" alt="图2-5" />
<p>图2-5 含有被替换单词提示的Alpha Munger</p>
</div>
<div class="tip">
<p>在这个例子中,你可能注意到了<var>debug=True</var>的使用。它调用了一个便利的测试模式:<var>tornado.autoreload</var>模块,此时,一旦主要的Python文件被修改,Tornado将会尝试重启服务器,并且在模板改变时会进行刷新。对于快速改变和实时更新这非常棒,但不要再生产上使用它,因为它将防止Tornado缓存模板!</p>
</div>
<h3>2.3.2 提供静态文件<a class="headerlink" id="ch2-3-2" href="#ch2-3-2">¶</a></h3>
<p>当编写Web应用时,你总希望提供像样式表、JavaScript文件和图像这样不需要为每个文件编写独立处理函数的"静态内容"。Tornado提供了几个有用的捷径来使其变得容易。</p>
<h4>2.3.2.1 设置静态路径<a class="headerlink" id="ch2-3-2-1" href="#ch2-3-2-1">¶</a></h4>
<p>你可以通过向<var>Application</var>类的构造函数传递一个名为<var>static_path</var>的参数来告诉Tornado从文件系统的一个特定位置提供静态文件。Alpha Munger中的相关代码片段如下:</p>
<pre class="codelist-code">app = tornado.web.Application(
handlers=[(r'/', IndexHandler), (r'/poem', MungedPageHandler)],
template_path=os.path.join(os.path.dirname(__file__), "templates"),
static_path=os.path.join(os.path.dirname(__file__), "static"),
debug=True
)</pre>
<p>在这里,我们设置了一个当前应用目录下名为<span class="filename">static</span>的子目录作为<var>static_path</var>的参数。现在应用将以读取<span class="filename">static</span>目录下的<span class="filename">filename.ext</span>来响应诸如<span class="filename">/static/filename.ext</span>的请求,并在响应的主体中返回。</p>
<h4>2.3.2.2 使用static_url生成静态URL<a class="headerlink" id="ch2-3-2-2" href="#ch2-3-2-2">¶</a></h4>
<p>Tornado模板模块提供了一个叫作<var>static_url</var>的函数来生成<span class="filename">static</span>目录下文件的URL。让我们来看看在<span class="filename">index.html</span>中<var>static_url</var>的调用的示例代码:</p>
<pre class="codelist-code"><link rel="stylesheet" href="{{ static_url("style.css") }}"></pre>
<p>这个对<var>static_url</var>的调用生成了URL的值,并渲染输出类似下面的代码:</p>
<pre class="codelist-code"><link rel="stylesheet" href="/static/style.css?v=ab12"></pre>
<p>那么为什么使用<var>static_url</var>而不是在你的模板中硬编码呢?有如下几个原因。其一,<var>static_url</var>函数创建了一个基于文件内容的hash值,并将其添加到URL末尾(查询字符串的参数<var>v</var>)。这个hash值确保浏览器总是加载一个文件的最新版而不是之前的缓存版本。无论是在你应用的开发阶段,还是在部署到生产环境使用时,都非常有用,因为你的用户不必再为了看到你的静态内容而清除浏览器缓存了。</p>
<p>另一个好处是你可以改变你应用URL的结构,而不需要改变模板中的代码。例如,你可以配置Tornado响应来自像路径<span class="filename">/s/filename.ext</span>的请求时提供静态内容,而不是默认的<span class="filename">/static</span>路径。如果你使用<var>static_url</var>而不是硬编码的话,你的代码不需要改变。比如说,你想把静态资源从我们刚才使用的<span class="filename">/static</span>目录移到新的<span class="filename">/s</span>目录。你可以简单地改变静态路径由<var>static</var>变为<var>s</var>,然后每个使用<var>static_url</var>包裹的引用都会被自动更新。如果你在每个引用静态资源的文件中硬编码静态路径部分,你将不得不手动修改每个模板。</p>
<h3>2.3.3 模板的下一步<a class="headerlink" id="ch2-3-3" href="#ch2-3-3">¶</a></h3>
<p>到目前为止,你已经能够处理Tornado模板系统的简单功能了。对于像Alpha Munger这样简单的Web应用而言,基础的功能对你而言足够用了。但是我们在模板部分的学习并没有结束。Tornado在块和模块的形式上仍然有一些技巧,这两个功能使得编写和维护复杂的Web应用更加简单。我们将在<a href="./ch3.html">第三章</a>中看到这些功能。</p>
</div>
<div class="footer">
<small>© 本文由<a href="http://www.pythoner.com">你像从前一样</a>翻译,转载请注明出处。本书版权由原作者拥有。</small>
</div>
</div>
</body>
</html>