【问题标题】:Getting started with Cherrypy and Jinja2Cherrypy 和 Jinja2 入门
【发布时间】:2013-05-26 12:02:16
【问题描述】:

这是我第一次深入研究 Python 中的 Web 开发。我唯一的其他经验是 PHP,我以前从未使用过框架,所以我觉得这非常令人生畏和困惑。

我有兴趣学习 CherryPy/Jinja2 为我的 NAS 制作 ZFS 监视器。我已经阅读了 CherryPy/Jinja2 上文档的基础知识,但我发现这些示例不连贯且过于简单,我真的不明白如何让这两件事优雅地“融合在一起”。

我的一些问题:

  1. 是否有一个简单的教程展示了如何让 CherryPy 和 Jinja2 很好地协同工作?我要么找到太简单的样本,比如 CherryPy / Jinja2 文档上的样本,要么找到复杂的方式。 (例如:https://github.com/jovanbrakus/cherrypy-example)。

  2. 是否有一种标准化或“预期”的方式来为 CherryPy 创建 Web 应用程序? (例如:我的目录结构应该是什么样子的?有没有办法声明静态的东西;甚至有必要吗?)

  3. 有没有人推荐过这方面的文献,或者在线文档是最好的资源吗?

【问题讨论】:

    标签: python-2.7 frameworks jinja2 cherrypy


    【解决方案1】:

    恭喜你选择了 Python,我相信你会像我一样爱上它。

    关于 CherryPy,我不是专家,但几天前也和你在同一条船上,我同意这些教程有些部分脱节。

    为了集成Jinja2,就像他们的doc page一样,HTML的sn-p应该被指定为模板文件,因此保存在路径/templates/index.html中。他们还使用了模板代码示例和控制器示例中不匹配的变量。

    下面是一个使用 CherryPy 和 Jinja2 的简单 hello world 的完整工作示例

    /main.py:

    import cherrypy
    from jinja2 import Environment, FileSystemLoader
    env = Environment(loader=FileSystemLoader('templates'))
    
    class Root:
        @cherrypy.expose
        def index(self):
            tmpl = env.get_template('index.html')
            return tmpl.render(salutation='Hello', target='World')
    
    cherrypy.config.update({'server.socket_host': '127.0.0.1',
                             'server.socket_port': 8080,
                            })
    
    cherrypy.quickstart(Root())
    

    /templates/index.html:

    <h1>{{ salutation }} {{ target }}</h1>
    

    然后在您的 shell/命令提示符下,使用以下命令为应用程序提供服务:

    python main.py
    

    在您的浏览器中,您应该可以在http://localhost:8080 看到它

    这有望帮助您将 Jinja2 模板连接到您的 CherryPy 应用程序。 CherryPy 确实是一个轻量级且非常灵活的框架,您可以在其中选择许多不同的方式来构建代码和文件结构。

    【讨论】:

    【解决方案2】:

    应用结构

    首先介绍一个项目的标准目录结构。没有,因为 CherryPy 没有强制要求,它也没有告诉您使用什么数据层、表单验证或模板引擎。这完全取决于您和您的要求。当然,这是一个很大的灵活性,因为它会给初学者带来一些困惑。以下是接近真实的应用程序目录结构的样子。

    .           — Python virtual environment
    └── website — cherryd to add this to sys.path, -P switch
        ├── application
        │   ├── controller.py — request routing, model use
        │   ├── model.py      — data access, domain logic
        │   ├── view          — template
        │   │   ├── layout
        │   │   ├── page
        │   │   └── part
        │   └── __init__.py — application bootstrap
        ├── public
        │   └── resource — static
        │       ├── css
        │       ├── image
        │       └── js
        ├── config.py — configuration, environments
        └── serve.py  — bootstrap call, cherryd to import this, -i switch
    

    然后站在virtual environment 的根目录下,你通常会在开发环境中执行以下操作来启动 CherryPy。 cherryd 是 CherryPy 建议的运行应用程序的方式。

    . bin/activate
    cherryd -i serve -P website
    

    模板

    现在让我们仔细看看模板目录以及它的外观。

    .
    ├── layout
    │   └── main.html
    ├── page
    │   ├── index
    │   │   └── index.html
    │   ├── news
    │   │   ├── list.html
    │   │   └── show.html
    │   ├── user
    │   │   └── profile.html
    │   └── error.html     
    └── part
        └── menu.html
    

    为了利用漂亮的 Jinja2 的 template inheritance 特性,这里是定义页面结构的布局,可以在特定页面中填充的插槽。您可能有网站布局和电子邮件通知布局。还有一个零件目录,可在不同页面上重复使用 sn-p。现在让我们看看与上述结构相对应的代码。

    我已将以下内容作为a runnable 提供,它更易于浏览文件,您可以运行并使用它。路径以. 开头,就像在第一部分的树中一样。

    网站/config.py

    # -*- coding: utf-8 -*-
    
    
    import os
    
    
    path   = os.path.abspath(os.path.dirname(__file__))
    config = {
      'global' : {
        'server.socket_host' : '127.0.0.1',
        'server.socket_port' : 8080,
        'server.thread_pool' : 8,
    
        'engine.autoreload.on' : False,
    
        'tools.trailing_slash.on' : False
      },
      '/resource' : {
        'tools.staticdir.on'  : True,
        'tools.staticdir.dir' : os.path.join(path, 'public', 'resource')
      }
    }
    

    网站/serve.py

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    
    
    from application import bootstrap
    
    
    bootstrap()
    
    
    # debugging purpose, e.g. run with PyDev debugger
    if __name__ == '__main__':
      import cherrypy
      cherrypy.engine.signals.subscribe()
      cherrypy.engine.start()
      cherrypy.engine.block()
    

    网站/应用程序/__init__.py

    这里值得注意的部分是一个 CherryPy 工具,它有助于避免与渲染模板相关的样板。您只需要从 CherryPy 页面处理程序返回一个带有模板数据的 dict。遵循convention-over-configuration原则,工具在没有提供模板名称时将使用classname/methodname.html,例如user/profile.html。要覆盖默认模板,您可以使用@cherrypy.tools.template(name = 'other/name')。另请注意,该工具会自动公开一个方法,因此您无需在顶部附加@cherrypy.expose

    # -*- coding: utf-8 -*-
    
    
    import os
    import types
    
    import cherrypy
    import jinja2
    
    import config
    
    
    class TemplateTool(cherrypy.Tool):
    
      _engine = None
      '''Jinja environment instance'''
    
    
      def __init__(self):
        viewLoader   = jinja2.FileSystemLoader(os.path.join(config.path, 'application', 'view'))
        self._engine = jinja2.Environment(loader = viewLoader)
    
        cherrypy.Tool.__init__(self, 'before_handler', self.render)
    
      def __call__(self, *args, **kwargs):
        if args and isinstance(args[0], (types.FunctionType, types.MethodType)):
          # @template
          args[0].exposed = True
          return cherrypy.Tool.__call__(self, **kwargs)(args[0])
        else:
          # @template()
          def wrap(f):
            f.exposed = True
            return cherrypy.Tool.__call__(self, *args, **kwargs)(f)
          return wrap
    
      def render(self, name = None):
        cherrypy.request.config['template'] = name
    
        handler = cherrypy.serving.request.handler
        def wrap(*args, **kwargs):
          return self._render(handler, *args, **kwargs)
        cherrypy.serving.request.handler = wrap
    
      def _render(self, handler, *args, **kwargs):
        template = cherrypy.request.config['template']
        if not template:
          parts = []
          if hasattr(handler.callable, '__self__'):
            parts.append(handler.callable.__self__.__class__.__name__.lower())
          if hasattr(handler.callable, '__name__'):
            parts.append(handler.callable.__name__.lower())
          template = '/'.join(parts)
    
        data     = handler(*args, **kwargs) or {}
        renderer = self._engine.get_template('page/{0}.html'.format(template))
    
        return renderer.render(**data) if template and isinstance(data, dict) else data
    
    
    def bootstrap():
      cherrypy.tools.template = TemplateTool()
    
      cherrypy.config.update(config.config)
    
      import controller
    
      cherrypy.config.update({'error_page.default': controller.errorPage})
      cherrypy.tree.mount(controller.Index(), '/', config.config)
    

    网站/应用程序/controller.py

    正如您所见,使用工具页面处理程序看起来相当干净,并且与其他工具保持一致,例如json_out.

    # -*- coding: utf-8 -*-
    
    
    import datetime
    
    import cherrypy
    
    
    class Index:
    
      news = None
      user = None
    
    
      def __init__(self):
        self.news = News()
        self.user = User()
    
      @cherrypy.tools.template
      def index(self):
        pass
    
      @cherrypy.expose
      def broken(self):
        raise RuntimeError('Pretend something has broken')
    
    
    class User:
    
      @cherrypy.tools.template
      def profile(self):
        pass
    
    
    class News:
    
      _list = [
        {'id': 0, 'date': datetime.datetime(2014, 11, 16), 'title': 'Bar', 'text': 'Lorem ipsum'},
        {'id': 1, 'date': datetime.datetime(2014, 11, 17), 'title': 'Foo', 'text': 'Ipsum lorem'}
      ]
    
    
      @cherrypy.tools.template
      def list(self):
        return {'list': self._list}
    
      @cherrypy.tools.template
      def show(self, id):
        return {'item': self._list[int(id)]}
    
    
    def errorPage(status, message, **kwargs):
      return cherrypy.tools.template._engine.get_template('page/error.html').render()
    

    在这个演示应用程序中,我使用blueprint css 文件来演示静态资源处理的工作原理。把它放在website/application/public/resource/css/blueprint.css。其余的没那么有趣,只是 Jinja2 模板的完整性。

    网站/应用程序/视图/布局/main.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta http-equiv='content-type' content='text/html; charset=utf-8' />
        <title>CherryPy Application Demo</title>
        <link rel='stylesheet' media='screen' href='/resource/css/blueprint.css' />
      </head>
      <body>
        <div class='container'>
          <div class='header span-24'>
            {% include 'part/menu.html' %}
          </div>
          <div class='span-24'>{% block content %}{% endblock %}</div>      
        </div>
      </body>
    </html>
    

    网站/应用程序/视图/页面/索引/index.html

    {% extends 'layout/main.html' %}
    {% block content %}
      <div class='span-18 last'>
          <p>Root page</p>      
      </div>
    {% endblock %}
    

    网站/应用程序/视图/页面/新闻/list.html

    {% extends 'layout/main.html' %}
    {% block content %}
      <div class='span-20 last prepend-top'>
        <h1>News</h1>
        <ul>
        {% for item in list %}
          <li><a href='/news/show/{{ item.id }}'>{{ item.title }}</a> ({{ item.date }})</li>
        {% endfor %}
        </ul>
      </div>
    {% endblock %}
    

    网站/应用程序/视图/页面/新闻/show.html

    {% extends 'layout/main.html' %}
    {% block content %}
      <div class='span-20 last prepend-top'>
        <h2>{{ item.title }}</h2>
        <div class='span-5 last'>{{ item.date }}</div>
        <div class='span-19 last'>{{ item.text }}</div>
      </div>
    {% endblock %}
    

    网站/应用程序/视图/页面/用户/profile.html

    {% extends 'layout/main.html' %}
    {% block content %}
      <div class='span-18'>
        <table>
          <tr><td>First name:</td><td>John</td></tr>
          <tr><td>Last name:</td><td>Doe</td></tr>
        <table>
      </div>
    {% endblock %}
    

    网站/应用程序/视图/page/error.html

    这是一个 404 页。

    {% extends 'layout/main.html' %}
    {% block content %}
      <h1>Error has happened</h1>
    {% endblock %}
    

    网站/应用程序/视图/part/menu.html

    <div class='span-4 prepend-top'>
      <h2><a href='/'>Website</a></h2>
    </div>
    <div class='span-20 prepend-top last'>
        <ul>
          <li><a href='/news/list'>News</a></li>
          <li><a href='/user/profile'>Profile</a></li>
          <li><a href='/broken'>Broken</a></li>
        </ul>
    </div>
    

    参考文献

    上面的代码与qooxdoo-website-skeleton 的后端部分非常接近。对于此类应用程序的完整 Debain 部署,cherrypy-webapp-skeleton 可能有用。

    【讨论】:

    • 使用您的模板工具,是否可以从函数内覆盖模板?例如,如果我在大多数情况下想要默认模板行为,但 if a certain set of conditions are met switch to another template path?
    • @贾斯汀是的。因为该工具包装了处理程序,所以您需要对其进行一些更改,这样您的处理程序的副作用就不会被覆盖。只需将data = handler(*args, **kwargs) or {} 放在TemplateTool._render 的第一行,您的代码就可以工作了。
    猜你喜欢
    • 2012-05-26
    • 2017-10-12
    • 2011-12-07
    • 1970-01-01
    • 1970-01-01
    • 2016-08-03
    • 2017-02-22
    • 2011-10-11
    • 2016-12-14
    相关资源
    最近更新 更多