08:使用模板生成HTML

大多数Web框架不在编程代码中嵌入HTML。相反,它们将数据传递到模板系统中。在这一步中,我们将了解在Pyramid中使用HTML模板的基础知识。

背景

哎哟。我们一直在创造自己的 Response 并用HTML填充响应体。通常不会将HTML字符串直接嵌入到Python中,而是使用模板语言。

Pyramid并不要求特定的数据库系统、表单库等。它鼓励可替换性。这同样适用于模板化,这是幸运的:开发人员对模板语言有很强的看法。对于Pyramid1.5A2,Pyramid甚至不捆绑模板语言!

然而,它确实与Jinja2、Mako和变色龙有着密切的联系。在这一步中,我们将看到如何添加 pyramid_chameleon 到项目,然后更改视图以使用模板。

目标

  • 启用 pyramid_chameleon Pyramid附加。

  • 从模板文件生成HTML。

  • 将模板连接为视图代码的“渲染器”。

  • 将视图代码更改为只返回数据。

步骤

  1. 让我们从使用前一个包作为新项目的起点开始:

    cd ..; cp -r views templating; cd templating
    
  2. 这一步取决于 pyramid_chameleon ,因此将其作为依赖项添加到 templating/setup.py

     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    'pyramid',
     7    'pyramid_chameleon',
     8    'waitress',
     9]
    10
    11# List of dependencies installed via `pip install -e ".[dev]"`
    12# by virtue of the Setuptools `extras_require` value in the Python
    13# dictionary below.
    14dev_requires = [
    15    'pyramid_debugtoolbar',
    16    'pytest',
    17    'webtest',
    18]
    19
    20setup(
    21    name='tutorial',
    22    install_requires=requires,
    23    extras_require={
    24        'dev': dev_requires,
    25    },
    26    entry_points={
    27        'paste.app_factory': [
    28            'main = tutorial:main'
    29        ],
    30    },
    31)
    
  3. 现在我们可以激活开发模式分布:

    $VENV/bin/pip install -e .
    
  4. 我们需要连接 pyramid_chameleon 作为渲染器调用 templating/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')
     9    config.scan('.views')
    10    return config.make_wsgi_app()
    
  5. 我们的 templating/tutorial/views.py 不再包含HTML:

     1from pyramid.view import view_config
     2
     3
     4# First view, available at http://localhost:6543/
     5@view_config(route_name='home', renderer='home.pt')
     6def home(request):
     7    return {'name': 'Home View'}
     8
     9
    10# /howdy
    11@view_config(route_name='hello', renderer='home.pt')
    12def hello(request):
    13    return {'name': 'Hello View'}
    
  6. 相反,我们有 templating/tutorial/home.pt 作为模板:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <title>Quick Tutorial: ${name}</title>
    </head>
    <body>
    <h1>Hi ${name}</h1>
    </body>
    </html>
    
  7. 为方便起见,更改 templating/development.ini 自动重新加载模板的步骤 pyramid.reload_templates

    [app:main]
    use = egg:tutorial
    pyramid.reload_templates = true
    pyramid.includes =
        pyramid_debugtoolbar
    
    [server:main]
    use = egg:waitress#main
    listen = localhost:6543
    
  8. 我们的单元测试 templating/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 home
    15
    16        request = testing.DummyRequest()
    17        response = home(request)
    18        # Our view now returns data
    19        self.assertEqual('Home View', response['name'])
    20
    21    def test_hello(self):
    22        from .views import hello
    23
    24        request = testing.DummyRequest()
    25        response = hello(request)
    26        # Our view now returns data
    27        self.assertEqual('Hello View', response['name'])
    28
    29
    30class TutorialFunctionalTests(unittest.TestCase):
    31    def setUp(self):
    32        from tutorial import main
    33        app = main({})
    34        from webtest import TestApp
    35
    36        self.testapp = TestApp(app)
    37
    38    def test_home(self):
    39        res = self.testapp.get('/', status=200)
    40        self.assertIn(b'<h1>Hi Home View', res.body)
    41
    42    def test_hello(self):
    43        res = self.testapp.get('/howdy', status=200)
    44        self.assertIn(b'<h1>Hi Hello View', res.body)
    
  9. 现在运行测试:

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

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

分析

啊,看起来好多了。我们有一个关注Python代码的视图。我们的 @view_config decorator指定 renderer 指向我们的模板文件。然后,我们的视图只返回数据,这些数据随后被提供给我们的模板。请注意,我们对两个视图使用了相同的模板。

注意对测试的影响。我们可以集中精力与视图代码签订面向数据的契约。