定义视图

A view callable 在一个 traversal 基于 Pyramid 应用程序通常是一个简单的python函数,它接受两个参数: contextrequest . 假定视图可调用返回 response 对象。

备注

A Pyramid 视图也可以定义为接受 onlyrequest 参数。您将看到另一个参数中使用的这个参数模式 Pyramid 教程和应用程序。任何一种呼叫约定都适用于 Pyramid 应用。根据需要,调用约定可以互换使用。

traversal -基于应用程序,URL映射到上下文 resource . 自从我们 resource tree 还表示应用程序的“域模型”,我们通常对上下文感兴趣,因为它表示应用程序的持久存储。因此,在本教程中,我们将视图定义为接受 context 在可调用参数列表中。如果你确实需要 context 在只将请求作为单个参数的视图函数中,可以通过 request.context .

我们将定义几个 view callable 函数,然后将它们连接到 Pyramid 使用一些 view configuration .

参见

本章将介绍更多的概念,如前面所述。另见章节 资源 有关资源和章节的完整描述 遍历 有关遍历如何在Pyramid中工作的技术细节。

在我们的 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 . 这意味着我们可以在 importcontext 声明。然而,在我们的叙述中,我们提到 因此我们使用 绝对的 形式。这意味着包的名称包含在内。

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 ,并将其用作 locationHTTPSeeOther 响应,形成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,因为在这里比在模板中更容易。最后,我们在字典中总结出一些参数并返回它。

我们放入字典的参数包括 pagepage_textedit_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 nameadd_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。它还充当当我们想要添加页面资源时生成的表单的处理程序。这个 contextadd_page 视图始终是 Wiki 资源(资源) notPage 资源)。

请求 subpath 在里面 Pyramid 找到的名称序列 之后 这个 view namePATH_INFO 作为 traversal . 例如,如果通过调用添加视图, http://localhost:6543/add_page/SomeName 然后 subpath 将是一个元组 ('SomeName',) .

添加视图取零th 子路径的元素(wiki页面名称),然后将其别名为name属性,以知道要添加的页面的名称。

如果视图渲染是 not 表单提交的结果(如果 'form.submitted' in request.paramsFalse ,然后视图呈现模板。为此,它生成一个 save_url 模板在呈现期间用作表单发布URL。我们在这里很懒惰,所以我们尝试使用相同的模板 (templates/edit.pt )对于“添加”和“编辑”视图。为此,我们创建一个虚拟对象 Page 满足编辑表单所需的资源对象 some 页面对象公开为 page . 然后我们设置 Page 对象的 __name____parent__ . 然后我们将把模板呈现为响应。

如果视图呈现 is 表单提交的结果(如果 'form.submitted' in request.paramsTrue ,然后执行以下操作:

  • 从表单数据中获取页面正文 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 nameedit_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 当用户单击视图窗体上的“编辑此页”按钮时,将调用函数。它呈现一个编辑表单。它还充当它所呈现的表单的可调用的表单后视图。这个 contextedit_page 查看遗嘱 总是 是一个 Page 资源(从不 Wiki 资源)。

如果视图执行是 not 表单提交的结果(如果 'form.submitted' in request.paramsFalse ,然后视图呈现编辑表单,传递页面资源,以及 save_url 将用作生成表单的操作。

如果视图执行 is 表单提交的结果(如果 'form.submitted' in request.paramsTrue )视图抓取 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_pageadd_pageedit_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 &copy; 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.pttutorial/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行)与A div 元素。

  • A div 替换为的元素 page_text 视图提供的值(第5行)。 page_text 包含HTML,因此 structure 关键字用于防止转义HTML实体,如更改 >&gt; .

  • 指向“编辑”URL的链接,它调用 edit_page 正在查看的页面的视图(第9-11行)。

  • A span 其内容将替换为页面名称(如果存在)。

这个 edit.pt 模板

拷贝 tutorial/templates/view.pttutorial/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行)。视图将使用 bodyform.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,检查结果是否符合预期: