18:变形的形式和验证¶
模式驱动的、带有验证的自动生成的表单。
背景¶
现代Web应用程序广泛处理表单。然而,开发人员对于框架应该如何帮助他们使用表单有着广泛的哲学。因此,Pyramid并不直接捆绑一个特定的表单库。相反,Pyramid中有各种各样的表单库,很容易使用。
Deform 就是这样一个类库。在这一步中,我们为我们的形式引入deform。这也给了我们 Colander 用于模式和验证。
目标¶
使用colander(变形的伴生)制作一个模式。
使用deform创建一个表单,并更改我们的视图以处理验证。
步骤¶
首先我们复制
view_classes
步骤:cd ..; cp -r view_classes forms; cd forms
让我们编辑
forms/setup.py
要声明对deform的依赖,而deform又将colander作为依赖项拉入:1from setuptools import setup 2 3# List of dependencies installed via `pip install -e .` 4# by virtue of the Setuptools `install_requires` value below. 5requires = [ 6 'deform', 7 'pyramid', 8 'pyramid_chameleon', 9 'waitress', 10] 11 12# List of dependencies installed via `pip install -e ".[dev]"` 13# by virtue of the Setuptools `extras_require` value in the Python 14# dictionary below. 15dev_requires = [ 16 'pyramid_debugtoolbar', 17 'pytest', 18 'webtest', 19] 20 21setup( 22 name='tutorial', 23 install_requires=requires, 24 extras_require={ 25 'dev': dev_requires, 26 }, 27 entry_points={ 28 'paste.app_factory': [ 29 'main = tutorial:main' 30 ], 31 }, 32)
我们现在可以在开发模式下安装我们的项目:
$VENV/bin/pip install -e .
在中注册静态视图
forms/tutorial/__init__.py
对于deform的css、javascript等,以及我们的演示wiki页面的视图:1from pyramid.config import Configurator 2 3 4def main(global_config, **settings): 5 config = Configurator(settings=settings) 6 config.include('pyramid_chameleon') 7 config.add_route('wiki_view', '/') 8 config.add_route('wikipage_add', '/add') 9 config.add_route('wikipage_view', '/{uid}') 10 config.add_route('wikipage_edit', '/{uid}/edit') 11 config.add_static_view('deform_static', 'deform:static/') 12 config.scan('.views') 13 return config.make_wsgi_app()
在中实现新视图、表单架构和一些虚拟数据。
forms/tutorial/views.py
:1import colander 2import deform.widget 3 4from pyramid.httpexceptions import HTTPFound 5from pyramid.view import view_config 6 7pages = { 8 '100': dict(uid='100', title='Page 100', body='<em>100</em>'), 9 '101': dict(uid='101', title='Page 101', body='<em>101</em>'), 10 '102': dict(uid='102', title='Page 102', body='<em>102</em>') 11} 12 13class WikiPage(colander.MappingSchema): 14 title = colander.SchemaNode(colander.String()) 15 body = colander.SchemaNode( 16 colander.String(), 17 widget=deform.widget.RichTextWidget() 18 ) 19 20 21class WikiViews: 22 def __init__(self, request): 23 self.request = request 24 25 @property 26 def wiki_form(self): 27 schema = WikiPage() 28 return deform.Form(schema, buttons=('submit',)) 29 30 @property 31 def reqts(self): 32 return self.wiki_form.get_widget_resources() 33 34 @view_config(route_name='wiki_view', renderer='wiki_view.pt') 35 def wiki_view(self): 36 return dict(pages=pages.values()) 37 38 @view_config(route_name='wikipage_add', 39 renderer='wikipage_addedit.pt') 40 def wikipage_add(self): 41 form = self.wiki_form.render() 42 43 if 'submit' in self.request.params: 44 controls = self.request.POST.items() 45 try: 46 appstruct = self.wiki_form.validate(controls) 47 except deform.ValidationFailure as e: 48 # Form is NOT valid 49 return dict(form=e.render()) 50 51 # Form is valid, make a new identifier and add to list 52 last_uid = int(sorted(pages.keys())[-1]) 53 new_uid = str(last_uid + 1) 54 pages[new_uid] = dict( 55 uid=new_uid, title=appstruct['title'], 56 body=appstruct['body'] 57 ) 58 59 # Now visit new page 60 url = self.request.route_url('wikipage_view', uid=new_uid) 61 return HTTPFound(url) 62 63 return dict(form=form) 64 65 @view_config(route_name='wikipage_view', renderer='wikipage_view.pt') 66 def wikipage_view(self): 67 uid = self.request.matchdict['uid'] 68 page = pages[uid] 69 return dict(page=page) 70 71 @view_config(route_name='wikipage_edit', 72 renderer='wikipage_addedit.pt') 73 def wikipage_edit(self): 74 uid = self.request.matchdict['uid'] 75 page = pages[uid] 76 77 wiki_form = self.wiki_form 78 79 if 'submit' in self.request.params: 80 controls = self.request.POST.items() 81 try: 82 appstruct = wiki_form.validate(controls) 83 except deform.ValidationFailure as e: 84 return dict(page=page, form=e.render()) 85 86 # Change the content and redirect to the view 87 page['title'] = appstruct['title'] 88 page['body'] = appstruct['body'] 89 90 url = self.request.route_url('wikipage_view', 91 uid=page['uid']) 92 return HTTPFound(url) 93 94 form = wiki_form.render(page) 95 96 return dict(page=page, form=form)
中“wiki”顶部的模板
forms/tutorial/wiki_view.pt
:1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <title>Wiki: View</title> 5</head> 6<body> 7<h1>Wiki</h1> 8 9<a href="${request.route_url('wikipage_add')}">Add 10 WikiPage</a> 11<ul> 12 <li tal:repeat="page pages"> 13 <a href="${request.route_url('wikipage_view', uid=page.uid)}"> 14 ${page.title} 15 </a> 16 </li> 17</ul> 18</body> 19</html>
用于添加/编辑的另一个模板
forms/tutorial/wikipage_addedit.pt
:1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <title>WikiPage: Add/Edit</title> 5 <link rel="stylesheet" 6 href="${request.static_url('deform:static/css/bootstrap.min.css')}" 7 type="text/css" media="screen" charset="utf-8"/> 8 <link rel="stylesheet" 9 href="${request.static_url('deform:static/css/form.css')}" 10 type="text/css"/> 11 <tal:block tal:repeat="reqt view.reqts['css']"> 12 <link rel="stylesheet" type="text/css" 13 href="${request.static_url(reqt)}"/> 14 </tal:block> 15 <script src="${request.static_url('deform:static/scripts/jquery-2.0.3.min.js')}" 16 type="text/javascript"></script> 17 <script src="${request.static_url('deform:static/scripts/bootstrap.min.js')}" 18 type="text/javascript"></script> 19 20 <tal:block tal:repeat="reqt view.reqts['js']"> 21 <script src="${request.static_url(reqt)}" 22 type="text/javascript"></script> 23 </tal:block> 24</head> 25<body> 26<h1>Wiki</h1> 27 28<p>${structure: form}</p> 29<script type="text/javascript"> 30 deform.load() 31</script> 32</body> 33</html>
在处添加模板
forms/tutorial/wikipage_view.pt
查看wiki页面:1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <title>WikiPage: View</title> 5</head> 6<body> 7<a href="${request.route_url('wiki_view')}"> 8 Up 9</a> | 10<a href="${request.route_url('wikipage_edit', uid=page.uid)}"> 11 Edit 12</a> 13 14<h1>${page.title}</h1> 15<p>${structure: page.body}</p> 16</body> 17</html>
我们的测试
forms/tutorial/tests.py
不要运行,我们来修改它们:1import unittest 2 3from pyramid import testing 4 5 6class TutorialViewTests(unittest.TestCase): 7 def setUp(self): 8 self.config = testing.setUp() 9 10 def tearDown(self): 11 testing.tearDown() 12 13 def test_home(self): 14 from .views import WikiViews 15 16 request = testing.DummyRequest() 17 inst = WikiViews(request) 18 response = inst.wiki_view() 19 self.assertEqual(len(response['pages']), 3) 20 21 22class TutorialFunctionalTests(unittest.TestCase): 23 def setUp(self): 24 from tutorial import main 25 26 app = main({}) 27 from webtest import TestApp 28 29 self.testapp = TestApp(app) 30 31 def tearDown(self): 32 testing.tearDown() 33 34 def test_home(self): 35 res = self.testapp.get('/', status=200) 36 self.assertIn(b'<title>Wiki: View</title>', res.body) 37 38 def test_add_page(self): 39 res = self.testapp.get('/add', status=200) 40 self.assertIn(b'<h1>Wiki</h1>', res.body) 41 42 def test_edit_page(self): 43 res = self.testapp.get('/101/edit', status=200) 44 self.assertIn(b'<h1>Wiki</h1>', res.body) 45 46 def test_post_wiki(self): 47 self.testapp.post('/add', { 48 "title": "New Title", 49 "body": "<p>New Body</p>", 50 "submit": "submit" 51 }, status=302) 52 53 res = self.testapp.get('/103', status=200) 54 self.assertIn(b'<h1>New Title</h1>', res.body) 55 self.assertIn(b'<p>New Body</p>', res.body) 56 57 def test_edit_wiki(self): 58 self.testapp.post('/102/edit', { 59 "title": "New Title", 60 "body": "<p>New Body</p>", 61 "submit": "submit" 62 }, status=302) 63 64 res = self.testapp.get('/102', status=200) 65 self.assertIn(b'<h1>New Title</h1>', res.body) 66 self.assertIn(b'<p>New Body</p>', res.body)
运行测试:
$VENV/bin/pytest tutorial/tests.py -q .. 6 passed in 0.81 seconds
运行 Pyramid 应用程序时使用:
$VENV/bin/pserve development.ini --reload
在浏览器中打开http://localhost:6543/。
分析¶
此步骤有助于说明Static Assets的资产规范的实用性。我们有一个名为deform的外部包,其中包含需要发布的Static Assets。我们不需要知道它在磁盘上的位置。我们指向包,然后指向包内的路径。
我们只需要打个电话给 add_static_view
使该目录在URL上可用。对于特定于Pyramid的包,Pyramid提供了一个设施 (config.include()
)这甚至使包装的消费者不需要这样做。(变形不特定于棱锥。)
我们的表单有丰富的小部件,它们需要刚才提到的静态CSS和JavaScript。变形有 resource registry 它允许小部件指定需要哪些JavaScript和CSS。我们的 wikipage_addedit.pt
模板显示了我们如何迭代这些数据以生成包含所需资源的标记。
我们的添加和编辑视图使用一个名为 self-posting forms . 也就是说,相同的URL用于 GET
用于 POST
形式。无论您是第一次走向它还是单击了一个按钮,路由、视图和模板都是相同的URL。
在我们的视野里 if 'submit' in self.request.params:
看看这个表格是不是 POST
用户单击特定按钮的位置 <input name="submit">
.
然后表单控制器遵循典型模式:
如果你正在做
GET
,跳过并返回表单。如果你正在做
POST
,验证表单内容。如果表单无效,则通过使用提供的
POST
数据。如果验证成功,请执行一些操作并通过
HTTPFound
.
本质上,我们是在编写自己的表单控制器。其他基于Pyramid的系统,包括 pyramid_deform
,提供一个以表单为中心的视图类,它可以自动执行大部分分支和路由。
额外credit¶
尝试一个按钮,该按钮将转到特定wiki页面的“删除”视图。