11

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

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

我有一些问题:

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

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

  3. 有没有人为此推荐过文献,或者在线文档是最好的资源?

4

2 回答 2

34

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

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

为了集成 Jinja2,就像在他们的文档页面中一样,应该指定 HTML 片段它是模板文件,因此保存在路径 /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())

/模板/index.html:

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

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

python main.py

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

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

于 2013-05-31T07:41:41.893 回答
13

应用结构

首先关于项目的标准目录结构。没有,因为 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

然后站在虚拟环境的根目录下,您通常执行以下操作以在开发环境中启动 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 的模板继承特性,这里有定义页面结构的布局,可以在特定页面中填充的插槽。您可能有网站布局和电子邮件通知布局。还有一个用于不同页面的可重复使用片段的目录。现在让我们看看与上述结构相对应的代码。

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

网站/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 工具,它有助于避免与渲染模板相关的样板。您只需dict要从 CherryPy 页面处理程序返回一个带有模板数据的数据。遵循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()

在这个演示应用程序中,我使用了蓝图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 %}

网站/应用程序/视图/页面/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可能很有用。

于 2014-11-17T13:14:35.917 回答