定义视图¶
A view callable 在一个 traversal 基于 Pyramid 应用程序通常是一个简单的python函数,它接受两个参数: context 和 request . 假定视图可调用返回 response 对象。
备注
A Pyramid 视图也可以定义为接受 only 一 request 参数。您将看到另一个参数中使用的这个参数模式 Pyramid 教程和应用程序。任何一种呼叫约定都适用于 Pyramid 应用。根据需要,调用约定可以互换使用。
在 traversal -基于应用程序,URL映射到上下文 resource . 自从我们 resource tree 还表示应用程序的“域模型”,我们通常对上下文感兴趣,因为它表示应用程序的持久存储。因此,在本教程中,我们将视图定义为接受 context
在可调用参数列表中。如果你确实需要 context
在只将请求作为单个参数的视图函数中,可以通过 request.context
.
我们将定义几个 view callable 函数,然后将它们连接到 Pyramid 使用一些 view configuration .
在我们的 setup.py
文件¶
我们的应用程序中的视图代码将依赖于一个不依赖于原始“教程”应用程序的包。最初的“教程”应用程序是由CookiCutter生成的。它不知道我们的定制应用程序需求。
我们需要在 docutils
包装给我们 tutorial
包装的 setup.py
通过将此依赖项分配给 requires
中的参数 setup()
功能。
正常开放 setup.py
并编辑如下:
11requires = [
12 'docutils',
13 'plaster_pastedeploy',
14 'pyramid',
15 'pyramid_chameleon',
16 'pyramid_debugtoolbar',
17 'waitress',
18 'pyramid_retry',
19 'pyramid_tm',
20 'pyramid_zodbconn',
21 'transaction',
22 'ZODB',
23]
24
25tests_require = [
26 'WebTest',
27 'pytest',
28 'pytest-cov',
29]
只需添加突出显示的行。
运行 pip install -e .
¶
由于添加了新的软件依赖项,因此需要运行 pip install -e .
再次在根的内部 tutorial
打包以获取并注册新添加的依赖关系分发。
确保当前工作目录是项目的根目录(其中 setup.py
执行以下命令。
在UNIX上:
cd tutorial
$VENV/bin/pip install -e .
在Windows上:
cd tutorial
%VENV%\Scripts\pip install -e .
成功执行此命令将以与以下类似的控制台行结束:
Successfully installed docutils-0.16 tutorial
在中添加视图函数 views
包¶
是时候进行重大变革了。正常开放 tutorial/views/default.py
并编辑如下:
1from docutils.core import publish_parts
2from pyramid.httpexceptions import HTTPSeeOther
3from pyramid.view import view_config
4import re
5
6from ..models import Page
7
8
9# regular expression used to find WikiWords
10wikiwords = re.compile(r"\b([A-Z]\w+[A-Z]+\w+)")
11
12@view_config(context='..models.Wiki')
13def view_wiki(context, request):
14 return HTTPSeeOther(location=request.resource_url(context, 'FrontPage'))
15
16
17@view_config(context='..models.Page', renderer='tutorial:templates/view.pt')
18def view_page(context, request):
19 wiki = context.__parent__
20
21 def check(match):
22 word = match.group(1)
23 if word in wiki:
24 page = wiki[word]
25 view_url = request.resource_url(page)
26 return '<a href="%s">%s</a>' % (view_url, word)
27 else:
28 add_url = request.application_url + '/add_page/' + word
29 return '<a href="%s">%s</a>' % (add_url, word)
30
31 page_text = publish_parts(context.data, writer_name='html')['html_body']
32 page_text = wikiwords.sub(check, page_text)
33 edit_url = request.resource_url(context, 'edit_page')
34 return dict(page=context, page_text=page_text, edit_url=edit_url)
35
36
37@view_config(name='add_page', context='..models.Wiki',
38 renderer='tutorial:templates/edit.pt')
39def add_page(context, request):
40 pagename = request.subpath[0]
41 if 'form.submitted' in request.params:
42 body = request.params['body']
43 page = Page(body)
44 page.__name__ = pagename
45 page.__parent__ = context
46 context[pagename] = page
47 return HTTPSeeOther(location=request.resource_url(page))
48 save_url = request.resource_url(context, 'add_page', pagename)
49 page = Page('')
50 page.__name__ = pagename
51 page.__parent__ = context
52 return dict(page=page, save_url=save_url)
53
54
55@view_config(name='edit_page', context='..models.Page',
56 renderer='tutorial:templates/edit.pt')
57def edit_page(context, request):
58 if 'form.submitted' in request.params:
59 context.data = request.params['body']
60 return HTTPSeeOther(location=request.resource_url(context))
61
62 return dict(
63 page=context,
64 save_url=request.resource_url(context, 'edit_page'),
65 )
我们添加了一些导入,并创建了一个正则表达式来查找“wikiwords”。
我们摆脱了 my_view
视图函数及其修饰符,在我们选择 zodb
CookiCutter中的后端选项。这只是一个例子,与我们的应用无关。
然后我们加了四个 view callable 我们的功能 default.py
模块:
view_wiki()
-显示wiki本身。它将在根URL上应答。view_page()
-显示单个页面。add_page()
-允许用户添加页面。edit_page()
-允许用户编辑页面。
我们将在下面的部分中对每一个进行简要描述。
备注
文件名没有什么特别之处 default.py
. 在任意命名的文件中,项目的代码库中可能有许多视图可调用文件。实现视图可调用文件的文件 view
以它们的名称(或可能位于名为的应用程序包的python子包中) views
但这只是惯例。
这个 view_wiki
视图函数¶
下面是 view_wiki
视图函数及其修饰器:
12@view_config(context='..models.Wiki')
13def view_wiki(context, request):
14 return HTTPSeeOther(location=request.resource_url(context, 'FrontPage'))
备注
在我们的代码中,我们使用 进口 那就是 相对的 到我们的包 tutorial
. 这意味着我们可以在 import
和 context
声明。然而,在我们的叙述中,我们提到 班 因此我们使用 绝对的 形式。这意味着包的名称包含在内。
view_wiki()
是 default view 当对wiki的根URL发出请求时会调用它。它总是重定向到一个URL,该URL表示 FrontPage
.
我们为它提供了 @view_config
命名类的修饰器 tutorial.models.Wiki
作为背景。这意味着当 Wiki
资源是上下文,不 view name 请求中存在,则将使用此视图。与关联的视图配置 view_wiki
不使用 renderer
因为View Callable始终返回 response 对象而不是字典。当视图返回响应对象时,不需要渲染器。
这个 view_wiki
View Callable始终重定向到 Page
资源命名 FrontPage
. 为此,它返回 pyramid.httpexceptions.HTTPSeeOther
班级。此类的实例实现 pyramid.interfaces.IResponse
接口,类似于 pyramid.response.Response
. 它使用 pyramid.request.Request.route_url()
用于构造 FrontPage
页面资源(换句话说, http://localhost:6543/FrontPage
,并将其用作 location
的 HTTPSeeOther
响应,形成HTTP重定向。
这个 view_page
视图函数¶
这是密码 view_page
视图函数及其修饰器:
17@view_config(context='..models.Page', renderer='tutorial:templates/view.pt')
18def view_page(context, request):
19 wiki = context.__parent__
20
21 def check(match):
22 word = match.group(1)
23 if word in wiki:
24 page = wiki[word]
25 view_url = request.resource_url(page)
26 return '<a href="%s">%s</a>' % (view_url, word)
27 else:
28 add_url = request.application_url + '/add_page/' + word
29 return '<a href="%s">%s</a>' % (add_url, word)
30
31 page_text = publish_parts(context.data, writer_name='html')['html_body']
32 page_text = wikiwords.sub(check, page_text)
33 edit_url = request.resource_url(context, 'edit_page')
34 return dict(page=context, page_text=page_text, edit_url=edit_url)
这个 view_page
函数被配置为响应的默认视图 Page
资源。我们为它提供了 @view_config
命名类的修饰器 tutorial.models.Page
作为背景。这意味着当 Page
资源是上下文,而不是 view name 请求中存在,将使用此视图。我们通知 Pyramid 此视图将使用 templates/view.pt
模板文件作为 renderer
.
这个 view_page
函数生成 reStructuredText HTML格式的页面正文。身体被储存为 data
传递给视图的上下文的属性。上下文将是 Page
资源。然后它用HTML锚替换每个锚 WikiWord 使用已编译的正则表达式在呈现的HTML中引用。
名为 check
用作 wikiwords.sub
,表示应该调用它来为每个 WikiWord
在内容中找到匹配项。如果wiki(我们的网页 __parent__
)已包含与匹配的页 WikiWord
姓名 check
函数生成一个视图链接,用作替换值并返回它。如果wiki没有包含与匹配的网页 WikiWord
name,函数生成一个“add”链接作为替换值并返回它。
因此, page_text
变量现在是一个完整的HTML格式,包含各种视图和添加链接 WikiWord
基于当前页面资源的内容。
然后我们生成一个编辑URL,因为在这里比在模板中更容易。最后,我们在字典中总结出一些参数并返回它。
我们放入字典的参数包括 page
, page_text
和 edit_url
. 因此, 模板 与此视图关联可调用(通过 renderer=
在其配置中)将能够使用这些名称执行各种渲染任务。与此视图可调用关联的模板将是一个位于 templates/view.pt
.
注意这个视图Callable和 view_wiki
查看可调用。在 view_wiki
视图可调用,我们无条件返回 response 对象。在 view_page
View Callable,我们返回 词典 . 它是 总是 返回罚款 response 来自A的对象 Pyramid 查看。只有当存在 renderer 与视图配置中可调用的视图关联。
这个 add_page
视图函数¶
这是密码 add_page
视图函数及其修饰器:
37@view_config(name='add_page', context='..models.Wiki',
38 renderer='tutorial:templates/edit.pt')
39def add_page(context, request):
40 pagename = request.subpath[0]
41 if 'form.submitted' in request.params:
42 body = request.params['body']
43 page = Page(body)
44 page.__name__ = pagename
45 page.__parent__ = context
46 context[pagename] = page
47 return HTTPSeeOther(location=request.resource_url(page))
48 save_url = request.resource_url(context, 'add_page', pagename)
49 page = Page('')
50 page.__name__ = pagename
51 page.__parent__ = context
52 return dict(page=page, save_url=save_url)
这个 add_page
函数被配置为在上下文资源为 Wiki
以及 view name 是 add_page
. 我们为它提供了 @view_config
命名字符串的修饰符 add_page
作为其 view name (通过 name=
) tutorial.models.Wiki
作为其上下文,渲染器命名为 templates/edit.pt
. 这意味着当 Wiki
资源是上下文,并且 view name 已命名 add_page
由于遍历而存在,则将使用此视图。我们通知 Pyramid 此视图将使用 templates/edit.pt
模板文件作为 renderer
. 我们在添加和编辑视图之间共享相同的模板,因此 edit.pt
而不是 add.pt
.
这个 add_page
当用户单击 WikiWord
在系统中还没有表示为一个页面。这个 check
中的函数 view_page
视图生成指向此视图的URL。它还充当当我们想要添加页面资源时生成的表单的处理程序。这个 context
的 add_page
视图始终是 Wiki
资源(资源) not 一 Page
资源)。
请求 subpath 在里面 Pyramid 找到的名称序列 之后 这个 view name 在 PATH_INFO
作为 traversal . 例如,如果通过调用添加视图, http://localhost:6543/add_page/SomeName
然后 subpath 将是一个元组 ('SomeName',)
.
添加视图取零th 子路径的元素(wiki页面名称),然后将其别名为name属性,以知道要添加的页面的名称。
如果视图渲染是 not 表单提交的结果(如果 'form.submitted' in request.params
是 False
,然后视图呈现模板。为此,它生成一个 save_url
模板在呈现期间用作表单发布URL。我们在这里很懒惰,所以我们尝试使用相同的模板 (templates/edit.pt
)对于“添加”和“编辑”视图。为此,我们创建一个虚拟对象 Page
满足编辑表单所需的资源对象 some 页面对象公开为 page
. 然后我们设置 Page
对象的 __name__
和 __parent__
. 然后我们将把模板呈现为响应。
如果视图呈现 is 表单提交的结果(如果 'form.submitted' in request.params
是 True
,然后执行以下操作:
从表单数据中获取页面正文
body
.创建一个
Page
将子路径中的名称和页正文用作page
.设置
Page
对象的__name__
和__parent__
.将其保存到“我们的上下文”(即
Wiki
)使用__setitem__
上下文的方法。然后我们重定向回
view_page
新创建页面的视图(页面的默认视图)。
参见
在 previous chapter ,我们提到遍历图中的所有对象都必须 __name__
和A __parent__
属性。为资源提供位置感知。另请参见 位置感知资源 在 资源 完整讨论的章节。
这个 edit_page
视图函数¶
这是密码 edit_page
视图函数及其修饰器:
55@view_config(name='edit_page', context='..models.Page',
56 renderer='tutorial:templates/edit.pt')
57def edit_page(context, request):
58 if 'form.submitted' in request.params:
59 context.data = request.params['body']
60 return HTTPSeeOther(location=request.resource_url(context))
61
62 return dict(
63 page=context,
64 save_url=request.resource_url(context, 'edit_page'),
65 )
这个 edit_page
函数被配置为在上下文为 Page
资源和 view name 是 edit_page
. 我们为它提供了 @view_config
命名字符串的修饰符 edit_page
作为其 view name (通过 name=
) tutorial.models.Page
作为其上下文,渲染器命名为 templates/edit.pt
. 这意味着当 Page
资源是上下文,并且 view name 作为名为 edit_page
,将使用此视图。我们通知 Pyramid 此视图将使用 templates/edit.pt
模板文件作为 renderer
.
这个 edit_page
当用户单击视图窗体上的“编辑此页”按钮时,将调用函数。它呈现一个编辑表单。它还充当它所呈现的表单的可调用的表单后视图。这个 context
的 edit_page
查看遗嘱 总是 是一个 Page
资源(从不 Wiki
资源)。
如果视图执行是 not 表单提交的结果(如果 'form.submitted' in request.params
是 False
,然后视图呈现编辑表单,传递页面资源,以及 save_url
将用作生成表单的操作。
如果视图执行 is 表单提交的结果(如果 'form.submitted' in request.params
是 True
)视图抓取 body
元素,并将其设置为 data
页上下文的属性。然后它重定向到上下文(页面)的默认视图,该视图将始终是 view_page
查看。
修改 notfound_view
在里面 notfound.py
¶
我们还有一个视图需要修改。正常开放 tutorial/views/notfound.py
并做出强调线所显示的变化。
1from pyramid.view import notfound_view_config
2
3from ..models import Page
4
5
6@notfound_view_config(renderer='tutorial:templates/404.pt')
7def notfound_view(request):
8 request.response.status = 404
9 pagename = request.path
10 page = Page(pagename)
11 page.__name__ = pagename
12 return dict(page=page)
我们需要进口 Page
从我们的模型。我们最终返回 Page
对象AS page
在模板中 layout.pt
在标题标记中显示其名称。
添加模板¶
这个 view_page
, add_page
和 edit_page
我们添加的视图引用A template . 每个模板都是 Chameleon ZPT 模板。这些模板将位于 templates
教程包的目录。变色龙模板必须具有 .pt
延期应视为延期。
这个 layout.pt
模板¶
更新 tutorial/templates/layout.pt
内容如下,以强调的线条表示:
1<!DOCTYPE html metal:define-macro="layout">
2<html lang="${request.locale_name}">
3 <head>
4 <meta charset="utf-8">
5 <meta http-equiv="X-UA-Compatible" content="IE=edge">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <meta name="description" content="pyramid web application">
8 <meta name="author" content="Pylons Project">
9 <link rel="shortcut icon" href="${request.static_url('tutorial:static/pyramid-16x16.png')}">
10
11 <title><span tal:replace="page.__name__ | title"></span> - Pyramid tutorial wiki (based on TurboGears 20-Minute Wiki)</title>
12
13 <!-- Bootstrap core CSS -->
14 <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
15
16 <!-- Custom styles for this scaffold -->
17 <link href="${request.static_url('tutorial:static/theme.css')}" rel="stylesheet">
18
19 <!-- HTML5 shiv and Respond.js IE8 support of HTML5 elements and media queries -->
20 <!--[if lt IE 9]>
21 <script src="//oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js" integrity="sha384-0s5Pv64cNZJieYFkXYOTId2HMA2Lfb6q2nAcx2n0RTLUnCAoTTsS0nKEO27XyKcY" crossorigin="anonymous"></script>
22 <script src="//oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js" integrity="sha384-ZoaMbDF+4LeFxg6WdScQ9nnR1QC2MIRxA1O9KWEXQwns1G8UNyIEZIQidzb0T1fo" crossorigin="anonymous"></script>
23 <![endif]-->
24 </head>
25
26 <body>
27
28 <div class="starter-template">
29 <div class="container">
30 <div class="row">
31 <div class="col-md-2">
32 <img class="logo img-responsive" src="${request.static_url('tutorial:static/pyramid.png')}" alt="pyramid web framework">
33 </div>
34 <div class="col-md-10">
35 <div metal:define-slot="content">No content</div>
36 <div class="content">
37 <p>You can return to the
38 <a href="${request.application_url}">FrontPage</a>.
39 </p>
40 </div>
41 </div>
42 </div>
43 <div class="row">
44 <div class="links">
45 <ul>
46 <li><i class="glyphicon glyphicon-cog icon-muted"></i><a href="https://github.com/Pylons/pyramid">Github Project</a></li>
47 <li><i class="glyphicon glyphicon-globe icon-muted"></i><a href="https://web.libera.chat/#pyramid">IRC Channel</a></li>
48 <li><i class="glyphicon glyphicon-home icon-muted"></i><a href="https://pylonsproject.org">Pylons Project</a></li>
49 </ul>
50 </div>
51 </div>
52 <div class="row">
53 <div class="copyright">
54 Copyright © Pylons Project
55 </div>
56 </div>
57 </div>
58 </div>
59
60
61 <!-- Bootstrap core JavaScript
62 ================================================== -->
63 <!-- Placed at the end of the document so the pages load faster -->
64 <script src="//code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
65 <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
66 </body>
67</html>
因为我们使用的是模板引擎,所以我们可以将页面模板中的公共样板文件分解为可重用的组件。我们可以通过 METAL 宏和插槽。
cookiecutter定义了一个名为
layout
(第1行)。此宏由整个模板组成。我们改变了
title
标记使用name
的属性page
对象,或者如果它不存在,则页面标题(第11行)。cookiecutter定义了一个宏自定义点或 slot (第35行)。这个插槽在宏内部
layout
. 因此,可以用内容替换它,自定义宏。我们增加了一个
div
元素,其中包含允许用户返回首页的链接(第36-40行)。
参见
有关使用的更多信息,请参阅变色龙文档。 METAL 用于定义和使用宏和槽。
这个 view.pt
模板¶
重命名 tutorial/templates/mytemplate.pt
到 tutorial/templates/view.pt
并编辑强调的行,如下所示:
1<div metal:use-macro="load: layout.pt">
2 <div metal:fill-slot="content">
3
4 <div class="content">
5 <div tal:replace="structure page_text">
6 Page text goes here.
7 </div>
8 <p>
9 <a tal:attributes="href edit_url" href="">
10 Edit this page
11 </a>
12 </p>
13 <p>
14 Viewing <strong><span tal:replace="page.__name__">
15 Page Name Goes Here</span></strong>
16 </p>
17 </div>
18
19 </div>
20</div>
此模板由使用 view_page()
用于显示单个wiki页面。它包括:
使用宏加载整个模板
layout.pt
.模板填充名为
content
(第2行)与Adiv
元素。A
div
替换为的元素page_text
视图提供的值(第5行)。page_text
包含HTML,因此structure
关键字用于防止转义HTML实体,如更改>
到>
.指向“编辑”URL的链接,它调用
edit_page
正在查看的页面的视图(第9-11行)。A
span
其内容将替换为页面名称(如果存在)。
这个 edit.pt
模板¶
拷贝 tutorial/templates/view.pt
到 tutorial/templates/edit.pt
并编辑强调的行,如下所示:
1<div metal:use-macro="load: layout.pt">
2 <div metal:fill-slot="content">
3
4 <div class="content">
5 <p>
6 Editing <strong><span tal:replace="page.__name__">
7 Page Name Goes Here</span></strong>
8 </p>
9 <form action="${save_url}" method="post">
10 <div class="form-group">
11 <textarea tal:content="page.data"
12 class="form-control" name="body"
13 rows="10" cols="60"></textarea>
14 </div>
15 <div class="form-group">
16 <button type="submit"
17 name="form.submitted" value="Save"
18 class="btn btn-default">Save</button>
19 </div>
20 </form>
21 </div>
22
23 </div>
24</div>
此模板由使用 add_page()
和 edit_page()
用于添加和编辑wiki页面。它显示一个包含以下表单的页面:
10行乘60列
textarea
字段名body
在呈现时用任何现有页数据填充(第11-13行)。具有名称的提交按钮
form.submitted
(16-18行)。
提交后,表单将向 save_url
视图提供的参数(第9行)。视图将使用 body
和 form.submitted
价值观。
备注
我们的模板使用 request
对象,我们的教程视图都没有返回到它们的字典中。 request
是使用模板呈现器时模板中“默认”可用的几个名称之一。见 渲染期间使用的系统值 有关将模板用作渲染器时默认情况下可用的其他名称的信息。
Static Assets¶
我们的模板命名Static Assets,包括CSS和图像。我们不需要在包中创建这些文件 static
目录,因为它们是在我们创建项目时由cookiecutter提供的。
例如,CSS文件将通过 http://localhost:6543/static/theme.css
凭借对 add_static_view
中的指令 routes.py
文件。任何数量和类型的静态资产都可以放在这个目录(或子目录)中。它们可以通过URL或使用便利方法来引用 static_url
,例如 request.static_url('<package>:static/foo.css')
,在模板中。
在浏览器中查看应用程序¶
我们最终可以在浏览器中检查我们的应用程序(请参见 启动应用程序 )启动浏览器并访问以下每个URL,检查结果是否符合预期:
http://localhost:6543/ invokes the
view_wiki
查看。这总是重定向到view_page
视图FrontPage
Page
资源。http://localhost:6543/FrontPage/ invokes the
view_page
首页资源视图。这是因为它是 default view (一个没有name
为Page
资源。http://localhost:6543/FrontPage/edit_page invokes the edit view for the
FrontPage
Page
资源。http://localhost:6543/add_page/SomePageName invokes the add view for a
Page
.要生成错误,请访问http://localhost:6543/add_页面,该页面将生成
IndexError: tuple index out of range
错误。您将看到由 pyramid_debugtoolbar .要生成未找到的错误,请访问http://localhost:6543/wakawaka,它将调用
notfound_view
由CookiCutter提供的视图。