4:类型特定视图

通过对类注册视图来键入特定的视图。

背景

3:遍历层次结构 我们有3种“内容类型”(根、文件夹和文档),但是,所有这些都使用相同的视图和模板。

Pyramid 遍历允许您将视图绑定到特定的内容类型。这种使URL“面向对象”的能力是遍历的一个显著特征,并使创建URL空间更加自然。Once Pyramid finds the context 对象在URL路径中,开发人员在视图谓词中具有很大的灵活性。

目标

  • 使用装饰器 @view_config 其中使用 context 将特定视图与之关联的属性 context 特定类的实例。

  • 创建对特定类(也就是类型)唯一的视图和模板。

  • 学习测试写作中的模式以处理多种上下文。

步骤

  1. 我们将使用前面的步骤作为起点:

    $ cd ..; cp -r hierarchy typeviews; cd typeviews
    $ $VENV/bin/python setup.py develop
    
  2. 我们的观点 typeviews/tutorial/views.py 需要特定类型的注册:

     1from pyramid.location import lineage
     2from pyramid.view import view_config
     3
     4from .resources import (
     5    Root,
     6    Folder,
     7    Document
     8    )
     9
    10
    11class TutorialViews:
    12    def __init__(self, context, request):
    13        self.context = context
    14        self.request = request
    15        self.parents = reversed(list(lineage(context)))
    16
    17    @view_config(renderer='templates/root.jinja2',
    18                 context=Root)
    19    def root(self):
    20        page_title = 'Quick Tutorial: Root'
    21        return dict(page_title=page_title)
    22
    23    @view_config(renderer='templates/folder.jinja2',
    24                 context=Folder)
    25    def folder(self):
    26        page_title = 'Quick Tutorial: Folder'
    27        return dict(page_title=page_title)
    28
    29
    30    @view_config(renderer='templates/document.jinja2',
    31                 context=Document)
    32    def document(self):
    33        page_title = 'Quick Tutorial: Document'
    34        return dict(page_title=page_title)
    
  3. 我们的新内容子模板位于 typeviews/tutorial/templates/contents.jinja2

    1<h4>Contents</h4>
    2<ul>
    3    {% for child in context.values() %}
    4        <li>
    5            <a href="{{ request.resource_url(child) }}">{{ child.title }}</a>
    6        </li>
    7    {% endfor %}
    8</ul>
    
  4. 创建用于查看根目录的模板 typeviews/tutorial/templates/root.jinja2

    1{% extends "templates/layout.jinja2" %}
    2{% block content %}
    3
    4    <h2>{{ context.title }}</h2>
    5    <p>The root might have some other text.</p>
    6    {% include "templates/contents.jinja2" %}
    7
    8{% endblock content %}
    
  5. 现在制作一个用于查看文件夹的模板 typeviews/tutorial/templates/folder.jinja2

    1{% extends "templates/layout.jinja2" %}
    2{% block content %}
    3
    4    <h2>{{ context.title }}</h2>
    5    {% include "templates/contents.jinja2" %}
    6
    7{% endblock content %}
    
  6. 最后制作一个用于查看文档的模板 typeviews/tutorial/templates/document.jinja2

    1{% extends "templates/layout.jinja2" %}
    2{% block content %}
    3
    4    <h2>{{ context.title }}</h2>
    5    <p>A document might have some body text.</p>
    6
    7{% endblock content %}
    
  7. 需要更多的测试 typeviews/tutorial/tests.py

     1import unittest
     2
     3from pyramid.testing import DummyRequest
     4from pyramid.testing import DummyResource
     5
     6
     7class TutorialViewsUnitTests(unittest.TestCase):
     8    def _makeOne(self, context, request):
     9        from .views import TutorialViews
    10
    11        inst = TutorialViews(context, request)
    12        return inst
    13
    14    def test_site(self):
    15        request = DummyRequest()
    16        context = DummyResource()
    17        inst = self._makeOne(context, request)
    18        result = inst.root()
    19        self.assertIn('Root', result['page_title'])
    20
    21    def test_folder_view(self):
    22        request = DummyRequest()
    23        context = DummyResource()
    24        inst = self._makeOne(context, request)
    25        result = inst.folder()
    26        self.assertIn('Folder', result['page_title'])
    27
    28    def test_document_view(self):
    29        request = DummyRequest()
    30        context = DummyResource()
    31        inst = self._makeOne(context, request)
    32        result = inst.document()
    33        self.assertIn('Document', result['page_title'])
    34
    35
    36class TutorialFunctionalTests(unittest.TestCase):
    37    def setUp(self):
    38        from tutorial import main
    39        app = main({})
    40        from webtest import TestApp
    41        self.testapp = TestApp(app)
    42
    43    def test_it(self):
    44        res = self.testapp.get('/', status=200)
    45        self.assertIn(b'Root', res.body)
    46        res = self.testapp.get('/folder1', status=200)
    47        self.assertIn(b'Folder', res.body)
    48        res = self.testapp.get('/doc1', status=200)
    49        self.assertIn(b'Document', res.body)
    50        res = self.testapp.get('/doc2', status=200)
    51        self.assertIn(b'Document', res.body)
    52        res = self.testapp.get('/folder1/doc1', status=200)
    53        self.assertIn(b'Document', res.body)
    
  8. $ $VENV/bin/nosetests 应报告正在运行4个测试。

  9. 运行 Pyramid 应用程序时使用:

    $ $VENV/bin/pserve development.ini --reload
    
  10. 在浏览器中打开http://localhost:6543/。

分析

对于最重要的变化,我们的 @view_config 现在比赛在 context 视图谓词。我们可以说“在观看时使用此视图” this 类似的事情,“路由作为URL和视图之间的中间步骤的概念已经被消除。

Extra Credit

  1. 您应该计算Python端的子列表,还是通过在上下文上操作在模板端访问它?

  2. 如果需要不同的遍历策略呢?

  3. 在Zope, interfaces 用于注册视图。如何针对支持特定接口的实例注册 Pyramid 视图?你应该什么时候?

  4. 假设您需要在类的特定实例上使用一个更具体的视图,让一个更一般的视图覆盖所有其他实例。你有哪些选择?