-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathch03-pylons.xml
760 lines (602 loc) · 27.6 KB
/
ch03-pylons.xml
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
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
<?xml version="1.0" encoding="UTF-8"?>
<!--
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
-->
<!-- =================================================================== -->
<sect1 id="psm.pylons">
<title>华丽外衣——Pylons造</title>
<para>在接触 Pylons 和其他 MVC 框架之前,对 Python 的 Web 编程一直感到比较恐惧,
因为看过 <application>MoinMoin</application> 的代码,
要为每一种协议(CGI, FastCGI, mod_python, WSGI)写相应的处理代码,
实在是麻烦透顶。还好有了Pylons等Web编程框架,为我们屏蔽了协议一层的复杂度。</para>
<para>Pylons 实现了 MVC 架构,在使用习惯上和 ROR 非常类似,因此从学习成本上考虑,
我选择了 Pylons。</para>
<!-- ================================================================= -->
<sect2 id="psm.pylons.basic">
<title>建立 Web 应用框架</title>
<para>我们的应用定名为 pySvnManager。建立同名的 Pylons 框架:</para>
<screen>
$ <emphasis>paster create -t pylons pySvnManager</emphasis>
Selected and implied templates:
Pylons#pylons Pylons application template
Variables:
egg: pySvnManager
package: pysvnmanager
project: pySvnManager
Enter template_engine (mako/genshi/jinja/etc: Template language) ['mako']:
Enter sqlalchemy (True/False: Include SQLAlchemy 0.4 configuration) [False]:
Creating template pylons
Creating directory ./pySvnManager
…
$ <emphasis>cd pySvnManager</emphasis>
$ <emphasis>ls -F</emphasis>
development.ini ez_setup.py pysvnmanager/ README.txt setup.py
docs/ MANIFEST.in pySvnManager.egg-info/ setup.cfg test.ini
</screen>
<para>启动Web应用:</para>
<screen>
$ <emphasis>paster serve --reload development.ini</emphasis>
Starting subprocess with file monitor
Starting server in PID 817.
serving on http://127.0.0.1:5000
</screen>
<para>用浏览器访问 http://127.0.0.1:5000 会看到一个网页。这个网页实际上调用的是
<filename>public/index.html</filename> 文件。如果删除该文件,则浏览器显示
404错误(网页未找到)。</para>
<!-- =============================================================== -->
<sect3 id="psm.pylons.basic.controller">
<title>理解控制器</title>
<para>下面用命令创建控制器 check,会产生两个文件,一个是控制器文件本身:
<filename>controllers/check.py</filename>,另外一个是单元测试文件:
<filename>tests/functional/test_check.py</filename>。</para>
<screen>
$ <emphasis>paster controller check</emphasis>
Creating /home/jiangxin/pyenv/pySvnManager/pysvnmanager/controllers/check.py
Creating /home/jiangxin/pyenv/pySvnManager/pysvnmanager/tests/functional/test_check.py
</screen>
<para>用浏览器访问URL:http://127.0.0.1:5000/check/ 会看到Hello World。
我们追根溯源,会看到 <filename>controllers/check.py</filename> 中的代码:</para>
<programlisting><![CDATA[
class CheckController(BaseController):
def index(self):
return 'Hello World'
]]></programlisting>
<para>哦,原来如此。Pylons 已经将 URL到代码的映射搞定!就是将浏览器对 URL
的访问映射到控制器代码,再由控制器处理后将结果显示给浏览器。
控制器调用实现逻辑(即Model),然后把从Model获取的结果填充到模板(View)中,
于是 MVC 便实现了逻辑和展现分离。Pylons 框架实现的将URL映射到控制器代码,
和 Windows 下 VC/Delphi 等GUI编程中将事件(鼠标、按钮等)映射到对应的代码是多么的近似。</para>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.basic.routing">
<title>修改控制器映射</title>
<para>还记得我们已经删除了 <filename>public/index.html</filename> 文件么?
我们现在通过修改控制器映射,将 Web 应用的缺省首页指向我们新建立的 controller。
要修改的文件就是: <filename>config/routing.py</filename></para>
<programlisting>
18 map.connect('/error/{action}', controller='error')
19 map.connect('/error/{action}/{id}', controller='error')
20
21 # CUSTOM ROUTES HERE
22 <emphasis>map.connect('/', controller='check', action='index')</emphasis>
23
24 <emphasis>map.connect('/{controller}')</emphasis>
25 map.connect('/{controller}/{action}')
26 map.connect('/{controller}/{action}/{id}')
</programlisting>
<para>第22行是我们新增的,告诉Pylons,将缺省的主页定位到名为 check 的控制器的
<methodname>index</methodname> 方法(动作)。</para>
<para>我们打开浏览器访问 <uri>http://127.0.0.1:5000/</uri> 会自动定位到
<uri>http://127.0.0.1:5000/check/index</uri> 。</para>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.basic.model">
<title>加入模组和单元测试</title>
<para>把我们已经开发完毕的 <package>svnauthz</package> 模组及其单元测试放到
<package>pySvnManager</package> 的代码树中,因为 <classname>svnauthz</classname>
和 <package>pySvnManager</package> 的耦合很紧,没有必要单独维护
<package>svnauthz</package> 模组。</para>
<para><filename>pySvnManager/model</filename> 目录是放置模组的地方,
将 <package>svnauthz</package> 的模组放在该目录下。</para>
<para>至于单元测试用例,则应该拷贝到 <filename>pysvnmanager/tests</filename>
目录下。该目录下有文件 <filename>test_models.py</filename>,就是用于测试模组的。
我们可以用 <filename>test_svnauthz.py</filename> 覆盖
空文件 <filename>test_models.py</filename> ,并在该文件中设置 Python 包含路径,
以便能成功包含要测试的模组:</para>
<programlisting>
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
…
20 import os
21 import sys
22 sys.path.insert(0,os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
23
24 from pysvnmanager.tests import *
25 from pysvnmanager import model
26 from pysvnmanager.model.svnauthz import *
</programlisting>
<para>实验一下 <command>nosetests</command> 是否依然可靠运行。</para>
<screen>
$ <emphasis>nosetests</emphasis>
.............
----------------------------------------------------------------------
Ran 13 tests in 0.546s
OK
</screen>
</sect3>
</sect2>
<!-- ================================================================= -->
<sect2 id="psm.pylons.controller.check">
<title>控制器check的实现</title>
<sidebar>
<figure id="psm.pylons.controller.check.fig1">
<title>控制器 check 的 MVC 框架示意图</title>
<graphic fileref="images/check_controller.png"/>
</figure>
</sidebar>
<orderedlist>
<listitem>
<para>路由:用户访问URL或提交表单,由 Pylons 负责将请求路由至控制器中的同名方法;</para>
</listitem>
<listitem>
<para>调用模组:控制器访问模组 <package>svnauthz</package> 的相关调用,调用结果返回给控制器;</para>
</listitem>
<listitem>
<para>调用视图:调用视图模板,并向其传递参数用于填充模板;</para>
</listitem>
<listitem>
<para>模板展现:最终填充后的模板发向浏览器,最终展现给用户;</para>
</listitem>
</orderedlist>
<!-- =============================================================== -->
<sect3 id="psm.pylons.framework.workflow">
<title>MVC中的数据流</title>
<!-- ============================================================= -->
<sect4 id="psm.pylons.user.to.controller">
<title>控制器获取用户请求</title>
<para>无论用户使用POST或者GET方式传递请求,都可以用
<varname>request.params</varname> 获取。</para>
<programlisting><![CDATA[
d = request.params # request.params 是包含用户传参的dict
if d.get('userinput') == 'manual':
username = d.get('username') # 从文本框获取用户手工输入的用户名
else:
username = d.get('userselector') # 从下拉框选择的用户名
]]></programlisting>
</sect4>
<!-- ============================================================= -->
<sect4 id="psm.pylons.controller.to.template">
<title>控制器向视图模板传参</title>
<programlisting><![CDATA[
c.access_map_msg ="<pre>"
c.access_map_msg+="\n\n".join(self.authz.get_access_map_msgs(username, repos))
c.access_map_msg+="</pre>"
return render('/check/index.mako')
]]></programlisting>
</sect4>
<!-- ============================================================= -->
<sect4 id="psm.pylons.template.var">
<title>视图模板用参数填充</title>
<programlisting><![CDATA[
<input type="submit" name="submit" value="提交">
${h.end_form()}
<hr>
${c.access_right_msg}
<pre>
${c.access_map_msg}
</pre>
]]></programlisting>
</sect4>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.page.design">
<title>页面模板布局</title>
<para>Check页面的布局参见:<xref linkend="psm.pylons.page.design.fig1"/></para>
<sidebar>
<figure id="psm.pylons.page.design.fig1">
<title>控制器check的MVC框架示意图</title>
<graphic fileref="images/html_design.png"/>
</figure>
</sidebar>
<para>各个部分的含义为:</para>
<orderedlist>
<listitem>
<para>
用户选择/输入框:选择或输入用户对象名称,可以为组、别名或用户名;
</para>
</listitem>
<listitem>
<para>
版本库选择/输入框:当选定一个版本库后,会更新③部分的授权路径列表;
</para>
</listitem>
<listitem>
<para>
授权路径选择/输入框:列表内容和版本库(②)相关;
</para>
</listitem>
<listitem>
<para>
权限检查按钮
</para>
</listitem>
<listitem>
<para>
结果输出
</para>
</listitem>
</orderedlist>
<note>
<para>其中:③和⑤是动态内容,②和④会触发表单提交。</para>
</note>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.template.basic">
<title>模板语法示例</title>
<para>Pylons缺省使用mako格式的模板。mako文件相当于ASP,PHP,JSP,
不过是Python语言的。模板文件的主体依旧是HTML,可以在模板中用“<% %>”
语法嵌入Python代码。例如:</para>
<programlisting><![CDATA[
<%
userlist = [[u'请选择...', '...'],
[u'所有用户(含匿名)', '*'],
[u'注册用户', '$authenticated'],
[u'匿名用户', '$anonymous'],]
for i in c.userlist:
if i == '*' or i =='$authenticated' or i == '$anonymous':
continue
if i[0] == '@':
userlist.append([u'团队:'+i[1:], i])
elif i[0] == '&':
userlist.append([u'别名:'+i[1:], i])
else:
userlist.append([i, i])
reposlist = [[u'请选择...', '...'], [u'所有版本库', '*'], [u'缺省', '/'],]
for i in c.reposlist:
if i == '/':
continue
reposlist.append([i, i])
pathlist = [[u'所有路径...', '*'],]
for i in c.pathlist:
pathlist.append([i, i])
%>
]]></programlisting>
<para>可以用“${expression}”将页面Python代码的或者Controller
传递的变量/表达式的值直接嵌入到模板中输出。例如:</para>
<programlisting><![CDATA[
<input type="radio" name="reposinput" value="select"
${c.checked_reposinput_select}> 选择代码库
<select name="reposselector" size="0" onFocus="select_repos(this.form)">
${h.options_for_select(reposlist, c.selected_repos)}
</select>
]]></programlisting>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.controller.method.index">
<title>控制器的index方法</title>
<programlisting><![CDATA[
class CheckController(BaseController):
def __init__(self):
self.authz = SvnAuthz(cfg.authz_file)
c.reposlist = map(lambda x:x.name, self.authz.reposlist)
c.userlist = map(lambda x:x.uname, self.authz.grouplist)
c.userlist.extend(map(lambda x:x.uname, self.authz.aliaslist))
c.userlist.extend(map(lambda x:x.uname, self.authz.userlist))
c.pathlist = []
def index(self):
return render('/check/index.mako')
]]></programlisting>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.controller.method.submit">
<title>控制器的submit方法</title>
<screen>
class CheckController(BaseController):
...
def submit(self):
d=request.params
# 从 request.params 中获取用户名、版本库名、路径等
if d['reposinput'] == 'manual':
repos = d['reposname']
else:
repos = d['reposselector']
# 略去参数解析
...
# 通过上下文对象传递Model返回值
c.access_map_msg ="<pre>"
c.access_map_msg+="\n\n".join(self.authz.get_access_map_msgs(username, repos))
c.access_map_msg+="</pre>"
# 调用并返回填充后的视图模板
return render('/check/index.mako')
</screen>
</sect3>
</sect2>
<!-- ================================================================= -->
<sect2 id="psm.pylons.ajax">
<title>用AJAX取代传统的form提交</title>
<orderedlist>
<listitem>
<para>
为什么用AJAX?
</para>
<para>
使用AJAX,用户对Web的体验会更“敏捷”:数据提交页面不会闪屏;页面局部更新速度快;网络带宽占用低。
</para>
</listitem>
<listitem>
<para>
AJAX开发相较传统模式的简单之处:
</para>
<para>
传统模式下,表单提交则整个页面重绘,为了维持页面用户对表单的状态改变,要多些不少代码。
要在控制器和模板之间传递更多参数以保持页面状态。而AJAX不然,因为页面只是局部更新,
不关心也不会影响页面其他部分的内容。
</para>
</listitem>
<listitem>
<para>
AJAX开发相较传统模式的难度:
</para>
<para>
需要了解、精通JavaScript,而JavaScript存在调试麻烦、浏览器兼容性等很多障碍。
</para>
</listitem>
</orderedlist>
<!-- =============================================================== -->
<sect3 id="psm.pylons.ajax.framework">
<title>启用Prototype的JavaScript框架</title>
<para>Prototype是一个JavaScript框架,可以更加容易的使用AJAX实现动态Web。
Pylons内置了prototype脚本。如果想要启用Pylons自带prototype
的JavaScript框架,只要在模板中嵌入如下WebHelpers语句:</para>
<screen><![CDATA[
<html>
<head>
${h.javascript_include_tag(builtins=True)}
]]></screen>
<para>实际上会在页面中产生下面两个JavaScrip包含语句:</para>
<screen><![CDATA[
<script src="/javascripts/prototype.js" type="text/javascript"></script>
<script src="/javascripts/scriptaculous.js" type="text/javascript"></script>
]]></screen>
<sidebar>
<para>最新版本的 Pylons 使用 0.6 版本的 <package>WebHelpers</package>,
不建议使用 <package>webhelpers.rails</package>,甚至在未来版本去掉
<package>webhelpers.rails</package>。也不再自动 import
<package>webhelpers.rails</package>。对于要用到的 webhelpers 方法,
需要显示的声明。参见文件 <filename>lib/helpers.py</filename>。</para>
<programlisting>
from routes import url_for, redirect_to
from webhelpers.html import escape, HTML, literal, url_escape
from webhelpers.html.tags import *
from webhelpers.rails.prototype import link_to_remote, form_remote_tag
from webhelpers.rails.scriptaculous import visual_effect
from webhelpers.rails.asset_tag import javascript_include_tag, stylesheet_link_tag
</programlisting>
<para>相关的 prototype 和 scriptaculous JavaScript 脚本,在新版的 Pylons
中也不提供,需要从相关站点下载,复制到 <filename>public/javascripts/</filename>
目录下。</para>
<para>建议逐步将 webhelpers.rails 调用替换。建议使用 Fork JavaScript 框架
取代 Prototype 的 JavaScript 框架。</para>
</sidebar>
</sect3>
<!-- ============================================================= -->
<sect3 id="psm.pylons.ajax.cgi">
<title>改造CGI(controller)</title>
<para>改造之后的CGI(controller的action)不再返回整个页面,
而是返回局部的需要动态更新的内容,或者是返回一段数据供页面中的
JavaScript解析使用。</para>
<para>需要把原来返回一个整个页面的CGI(一个controller的一个方法)改造成多个CGI
(多个方法)以针对不同情况返回不同的动态内容。</para>
<para>例如:pySvnManager的check控制器的submit方法实际上要处理两种情况:
一个是当选定一个版本库时要更新页面中的路径列表项(因为不同的版本库定义了不同的授权路径),
另外一个是按下“检查权限”按钮要进行的表单提交,显示用户授权信息。
将check控制器的submit方法改造为AJAX实现,就需要一分为二。</para>
</sect3>
<!-- ============================================================= -->
<sect3 id="psm.pylons.ajax.webpage">
<title>页面模板充分利用DOM 和JavaScript</title>
<para>页面要动态更新的内容封装在一个DOM容器中;</para>
<para>页面提交修改为执行一个JavaScript函数,该函数调用Ajax.Updater或者Ajax.Request函数;</para>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.ajax.sample1">
<title>改造示例一:用Ajax.Updater直接进行区域更新</title>
<para>当点击权限检查(④)按钮,原来的实现是直接进行表单的提交,
修改之后为执行一段JavaScript代码。</para>
<para>文件 check/index.mako 中用WebHelpers.rails的form_remote_tag
快速创建了一个Ajax Form。</para>
<screen><![CDATA[
## AJAX Form
<%
context.write(
h.form_remote_tag(
html={'id':'main_form'},
url=h.url(action='access_map'),
update=dict(success="acl_msg", failure="acl_error"),
method='post', before='showNoticesPopup()',
complete='hideNoticesPopup();'+h.visual_effect("Highlight", "acl_msg", duration=1),
)
)
%>
]]></screen>
<para>出现在页面中,则是如下的代码:</para>
<screen><![CDATA[
<form action="/check/access_map" id="main_form" method="POST" onsubmit="showNoticesPopup();
new Ajax.Updater({success:'acl_msg', failure:'acl_error'}, '/check/access_map',
{asynchronous:true, evalScripts:true, method:'post', onComplete:function(request)
{hideNoticesPopup(); new Effect.Highlight("acl_msg",{duration:1}); },
parameters:Form.serialize(this)});
return false;">
]]></screen>
<para>说明</para>
<itemizedlist>
<listitem>
<para>
当Form提交会执行onSubmit部分的代码,而不去执行Form action,因为onSubmit返回false;
</para>
</listitem>
<listitem>
<para>
Ajax.Updater的参数success,是成功执行后用返回信息填充的DOM容器;failure则相反;
</para>
</listitem>
<listitem>
<para>
'/check/access_map'是Ajax要执行的服务器CGI,其返回结果将用于填充相应的DOM容器;
</para>
</listitem>
<listitem>
<para>
onComplete是成功执行Ajax.Updater代码后要执行的JavaScript代码;
</para>
</listitem>
<listitem>
<para>
showNoticesPopup():弹出窗口,提示用户Ajax正在执行过程中,避免用户重复点击;
</para>
</listitem>
<listitem>
<para>
hideNoticesPopup():在Ajax执行完毕,关闭Ajax正在运行的提示窗口;
</para>
</listitem>
<listitem>
<para>
Effect.Highlight()是 scriptaculous.js提供的特效,闪烁更新的区域以引起注意;
</para>
</listitem>
<listitem>
<para>
parameters是用于传递参数,这里把整个表单的数据提交;
</para>
</listitem>
</itemizedlist>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.ajax.sample2">
<title>改造示例二:用Ajax.Request获取并处理数据</title>
<para>当从版本库下拉框(②)选择时,将触发更新授权路径的列表(③)。
原来的实现是提交整个表单并刷新整个页面,用AJAX改造后,
只更新授权路径的列表(③)部分。</para>
<para>虽然也可以用Ajax.Updater来更新整个授权路径列表,但为了演示另外一种Ajax处理方式,
以及获得更少的带宽占用和更快的响应,使用Ajax.Request来实现。</para>
<para>版本库下拉框(②)更新时,执行JavaScript函数:update_path(),而非提交表单:</para>
<screen><![CDATA[
<input type="radio" name="reposinput" value="select" Checked onClick="update_path(this.form)">
]]></screen>
<para>函数update_path(),执行Ajax.Request,从"get_auth_path"这个action获取信息,
并用返回值(request.reponseText)为参数调用JavaScript函数ajax_update_path。</para>
<screen><![CDATA[
function update_path(form)
{
var repos = "";
if (form.reposinput[0].checked) {
repos = form.reposselector.options[form.reposselector.selectedIndex].value;
} else {
repos = form.reposname.value;
}
var params = {repos:repos};
showNoticesPopup();
new Ajax.Request(
'${h.url_for(controller="check", action="get_auth_path")}',
{asynchronous:true, evalScripts:true, method:'post',
onComplete:
function(request)
{ hideNoticesPopup();
ajax_update_path(request.responseText);},
parameters:params
});
}
]]></screen>
<para>函数ajax_update_path(),解析参数code,更新授权路径的下拉列表框。
本例非常简单,直接将参数(code)当作JavaScript代码并执行(eval函数),
这是因为Ajax.Request获取到的内容是字符串格式的JavaScript代码。
最终这些JavaScript代码在函数ajax_update_path中被执行,
并用相应的数据更新了授权路径的列表(③)。</para>
<screen><![CDATA[
function ajax_update_path(code)
{
var id = new Array();
var name = new Array();
var total = 0;
pathselector = document.forms[0].pathselector;
lastselect = pathselector.value;
pathselector.options.length = 0;
try {
eval(code);
for (var i=0; i < total; i++)
{
pathselector.options[i] = new Option(name[i], id[i]);
if (id[i]==lastselect)
pathselector.options[i].selected = true;
}
}
catch(exception) {
alert(exception);
}
}
]]></screen>
</sect3>
</sect2>
<!-- ================================================================= -->
<sect2 id="psm.pylons.controller.unittest">
<title>控制器的单元测试</title>
<para>每一个控制器,在tests/functional 目录下都一个对应的单元测试文件。
Pylons的单元测试是使用 paste.fixture 来模拟浏览器对Web服务器的访问,
通过对返回结果的检查实现测试。</para>
<para>测试用例的运行,还是使用nosetests,nosetests能够主动到tests目录下发现测试用例,
并运行。</para>
<!-- =============================================================== -->
<sect3 id="psm.pylons.nosetest">
<title>配置nosetests</title>
<para>在setup.cfg文件中,对nosetests进行设置。可以设置采用不同的pylons配置文件。</para>
<screen>
[nosetests]
verbose=True
verbosity=2
with-pylons=test.ini # 使用test.ini作为pylons的配置文件
detailed-errors=1
#with-doctest=1 # 不进行 doctest测试,因为依赖的confobj包的doctest不通过
</screen>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.controller.unittest.sample1">
<title>测试示例一</title>
<screen><![CDATA[
res = self.app.get(url_for(controller='check'))
assert res.status == 200
assert '''<input type="submit" name="submit" value='Check Permissions'>''' in res.body
assert res.c.reposlist == ['/', u'repos1', u'repos2', u'repos3', u'document']
]]></screen>
</sect3>
<!-- =============================================================== -->
<sect3 id="psm.pylons.controller.unittest.sample2">
<title>测试示例二</title>
<screen><![CDATA[
params = {
'userinput':'select',
'userselector':'user1',
'reposinput':'select',
'reposselector':'repos1',
'pathinput':'manual',
'pathname':'/trunk/src/test',
'abbr':'True',
}
res = self.app.get(url_for(controller='check', action='access_map'), params)
assert res.status == 200
assert '''<div id='acl_path_msg'>[repos1:/trunk/src/test] user1 =</div>''' in res.body, res.body
]]></screen>
</sect3>
</sect2>
<!-- ================================================================= -->
<sect2 id="psm.pylons.controller.others">
<title>实现其他的控制器</title>
<para>Check控制器完成之后,进而对role和authz控制器进行开发,
分别实现角色控制和授权管理的功能。在开发新的控制器过程中,我们还依然采取:
模板(视图)设计,控制器设计,单元测试的流程。</para>
<para>当完成所有的三个控制器之后,会发现似乎少了些什么?
难道要任何人都可以查看 SVN 版本库的授权甚至修改版本库授权么?
我们需要为 pySvnManager 增加认证和授权管理。</para>
</sect2>
</sect1>