15:更多视图类

将视图分组为类、共享配置、状态和逻辑。

背景

作为帮助构建更雄心勃勃的Web应用程序的使命的一部分,Pyramid为视图和视图类提供了更多的功能。

Pyramid文档讨论了作为Python“可调用”的视图。此可调用项可以是函数,也可以是具有 __call__ 或者是python类。在最后一种情况下,类上的方法可以用 @view_config 将类方法注册到 configurator 作为一种观点。

起初,我们的观点是简单的、独立的功能。许多时候,您的视图是相关的:查看同一数据或处理同一数据的不同方法,或者处理多个操作的RESTAPI。将这些分组为 view class 有道理:

  • 组视图。

  • 集中一些重复的默认值。

  • 分享一些州和帮手。

Pyramid视图 view predicates 根据诸如请求方法、表单参数等因素确定与请求匹配的视图。这些谓词提供了许多灵活性轴。

下面是一个包含四个操作的简单示例:查看指向表单的主页,保存更改,然后按“删除”按钮。

目标

  • 将相关视图分组到视图类中。

  • 使用类级别集中配置 @view_defaults .

  • 根据请求数据向多个视图发送一个路由/url。

  • 通过视图类在视图和模板之间共享状态和逻辑。

步骤

  1. 首先我们复制 templating 步骤:

    cd ..; cp -r templating more_view_classes; cd more_view_classes
    $VENV/bin/pip install -e .
    
  2. 我们的进路 more_view_classes/tutorial/__init__.py 需要一些替换模式:

     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('home', '/')
     8    config.add_route('hello', '/howdy/{first}/{last}')
     9    config.scan('.views')
    10    return config.make_wsgi_app()
    
  3. 我们的 more_view_classes/tutorial/views.py 现在有一个包含多个视图的视图类:

     1from pyramid.view import (
     2    view_config,
     3    view_defaults
     4    )
     5
     6
     7@view_defaults(route_name='hello')
     8class TutorialViews:
     9    def __init__(self, request):
    10        self.request = request
    11        self.view_name = 'TutorialViews'
    12
    13    @property
    14    def full_name(self):
    15        first = self.request.matchdict['first']
    16        last = self.request.matchdict['last']
    17        return first + ' ' + last
    18
    19    @view_config(route_name='home', renderer='home.pt')
    20    def home(self):
    21        return {'page_title': 'Home View'}
    22
    23    # Retrieving /howdy/first/last the first time
    24    @view_config(renderer='hello.pt')
    25    def hello(self):
    26        return {'page_title': 'Hello View'}
    27
    28    # Posting to /howdy/first/last via the "Edit" submit button
    29    @view_config(request_method='POST', renderer='edit.pt')
    30    def edit(self):
    31        new_name = self.request.params['new_name']
    32        return {'page_title': 'Edit View', 'new_name': new_name}
    33
    34    # Posting to /howdy/first/last via the "Delete" submit button
    35    @view_config(request_method='POST', request_param='form.delete',
    36                 renderer='delete.pt')
    37    def delete(self):
    38        print ('Deleted')
    39        return {'page_title': 'Delete View'}
    
  4. 我们的主视图需要一个模板 more_view_classes/tutorial/home.pt

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>Quick Tutorial: ${view.view_name} - ${page_title}</title>
    </head>
    <body>
    <h1>${view.view_name} - ${page_title}</h1>
    
    <p>Go to the <a href="${request.route_url('hello', first='jane',
            last='doe')}">form</a>.</p>
    </body>
    </html>
    
  5. 上一节中的其他视图,同上 more_view_classes/tutorial/hello.pt

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>Quick Tutorial: ${view.view_name} - ${page_title}</title>
    </head>
    <body>
    <h1>${view.view_name} - ${page_title}</h1>
    <p>Welcome, ${view.full_name}</p>
    <form method="POST"
          action="${request.current_route_url()}">
        <input name="new_name"/>
        <input type="submit" name="form.edit" value="Save"/>
        <input type="submit" name="form.delete" value="Delete"/>
    </form>
    </body>
    </html>
    
  6. 我们有一个编辑视图,在 more_view_classes/tutorial/edit.pt

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>Quick Tutorial: ${view.view_name} - ${page_title}</title>
    </head>
    <body>
    <h1>${view.view_name} - ${page_title}</h1>
    <p>You submitted <code>${new_name}</code></p>
    </body>
    </html>
    
  7. 最后,删除视图的模板位于 more_view_classes/tutorial/delete.pt

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>Quick Tutorial: ${page_title}</title>
    </head>
    <body>
    <h1>${view.view_name} - ${page_title}</h1>
    </body>
    </html>
    
  8. 我们的测试 more_view_classes/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 TutorialViews
    15
    16        request = testing.DummyRequest()
    17        inst = TutorialViews(request)
    18        response = inst.home()
    19        self.assertEqual('Home View', response['page_title'])
    20
    21class TutorialFunctionalTests(unittest.TestCase):
    22    def setUp(self):
    23        from tutorial import main
    24        app = main({})
    25        from webtest import TestApp
    26
    27        self.testapp = TestApp(app)
    28
    29    def test_home(self):
    30        res = self.testapp.get('/', status=200)
    31        self.assertIn(b'TutorialViews - Home View', res.body)
    
  9. 现在运行测试:

    $VENV/bin/pytest tutorial/tests.py -q
    ..
    2 passed in 0.40 seconds
    
  10. 运行 Pyramid 应用程序时使用:

    $VENV/bin/pserve development.ini --reload
    
  11. 在浏览器中打开http://localhost:6543/howdy/jane/doe。单击 SaveDelete 按钮,并在控制台窗口中查看输出。

分析

如您所见,这四个视图在逻辑上分组在一起。明确地:

  • 我们有一个 home 可在http://localhost:6543/上通过点击链接查看 hello 查看。

  • 当您转到时返回第二个视图 /howdy/jane/doe . 此URL映射到 hello 我们使用可选的 @view_defaults .

  • 当表单与 POST 方法。此规则在中指定 @view_config 为了这个观点。

  • 单击按钮时返回第四个视图,如 <input type="submit" name="form.delete" value="Delete"/> .

在此步骤中,我们将使用以下信息作为条件,说明如何确定要使用的视图:

  • HTTP请求的方法 (GETPOST 等)

  • 请求中的参数信息(提交的表单字段名)

我们还将视图配置的一部分集中到类级别, @view_defaults ,然后在一个视图中,仅为该视图重写该默认值。最后,我们通过共享将视图之间的共性置于视图类中:

  • 状态分配位置 TutorialViews.__init__

  • 计算值

然后在视图方法和模板(例如, ${{view.view_name}}${{view.full_name}}

注意,我们在模板中对如何生成URL进行了切换。我们以前对URL进行了硬编码,例如:

<a href="/howdy/jane/doe">Howdy</a>

home.pt 我们切换到:

<a href="${request.route_url('hello', first='jane',
    last='doe')}">form</a>

Pyramid有丰富的功能来帮助以灵活、不易出错的方式生成URL。

额外credit

  1. 为什么我们的模板可以 ${{view.full_name}} 也不必这么做 ${{view.full_name()}} 是吗?

  2. 这个 editdelete 两个视图都接收 POST 请求。为什么 edit 视图配置未捕获 POST 被使用 delete 是吗?

  3. 我们使用了Python。 @propertyfull_name . 如果我们在模板或视图代码中多次引用它,那么每次都会重新计算它。Pyramid是否提供将缓存属性的初始计算的内容?

  4. 可以将多个路由与同一视图关联吗?

  5. 还有一个 request.route_path 应用程序编程接口。这和 request.route_url 是吗?