2013-05-30 32 views
9

Đây là lần đầu tiên tôi tìm hiểu về phát triển web trong python. Kinh nghiệm khác duy nhất của tôi là PHP, và tôi chưa bao giờ sử dụng một khung công tác trước đây, vì vậy tôi thấy điều này rất đáng sợ và khó hiểu.Bắt đầu với Cherrypy và Jinja2

Tôi quan tâm đến việc học CherryPy/Jinja2 để tạo màn hình ZFS cho NAS của tôi. Tôi đã đọc qua các khái niệm cơ bản của tài liệu trên CherryPy/Jinja2 nhưng tôi thấy rằng các mẫu được tách rời và quá đơn giản, tôi không thực sự hiểu làm thế nào để làm cho 2 điều này "đến với nhau" một cách duyên dáng.

Một số câu hỏi tôi có:

  1. Có một hướng dẫn đơn giản cho thấy cách bạn thực hiện CherryPy và Jinja2 làm việc cùng nhau độc đáo? Tôi đang tìm các mẫu quá đơn giản, giống như các mẫu trên tài liệu CherryPy/Jinja2, hoặc cách phức tạp. (ví dụ: https://github.com/jovanbrakus/cherrypy-example).

  2. Có cách chuẩn hóa hoặc "được mong đợi" để tạo ứng dụng web cho CherryPy không? (ví dụ: Cấu trúc thư mục của tôi trông như thế nào? Có cách nào để khai báo những điều tĩnh không?)

  3. Có ai đề nghị tài liệu này hoặc tài liệu trực tuyến là tài nguyên tốt nhất không?

Trả lời

26

Xin chúc mừng về cách chọn Python, tôi chắc chắn bạn sẽ học cách yêu nó như có I.

Về CherryPy, tôi không phải là một chuyên gia, nhưng cũng là trong cùng một thuyền như bạn một vài ngày trước và tôi đồng ý rằng các hướng dẫn được một chút rời rạc trong các bộ phận.

Để tích hợp Jinja2, như trong số doc page, đoạn mã HTML phải được chỉ định rằng đó là tệp mẫu và được lưu trong đường dẫn /templates/index.html. Họ cũng sử dụng các biến không khớp trong mẫu mã mẫu và mẫu điều khiển.

dưới đây là thay vì một mẫu làm việc hoàn toàn của một hello world đơn giản sử dụng CherryPy và Jinja2

/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> 

Sau đó trong dấu nhắc trình bao/lệnh, hãy phân phát ứng dụng bằng cách sử dụng:

python main.py 

Và trong trình duyệt của bạn, bạn sẽ có thể nhìn thấy nó ở http://localhost:8080

Đó hy vọng giúp bạn kết nối Jinja2 khuôn mẫu để ứng dụng CherryPy của bạn. CherryPy thực sự là một khung công tác nhẹ và rất linh hoạt, nơi bạn có thể chọn nhiều cách khác nhau để cấu trúc mã và cấu trúc tệp của mình.

+2

Tôi đã gặp khó khăn trong việc tìm ra cấu trúc dự án tốt và bài đăng này và các liên kết được bao gồm đã giúp làm rõ mọi thứ một chút. https://groups.google.com/forum/?fromgroups#!topic/cherrypy-users/L7YXZD_55ec –

+0

Tốt đẹp! phần giới thiệu này là đủ để tôi bắt đầu với CherryPy và Mako. –

+0

Upvote, nhưng xin vui lòng giúp đỡ, bởi vì vấn đề của tôi tiếp tục với Nginx http://stackoverflow.com/questions/23359095/how-to-put-cherrypy-wsgi-behind-nginx. – Alex

10

Cấu trúc ứng dụng

Đầu tiên về cấu trúc thư mục chuẩn của dự án. Không có gì, vì CherryPy không ủy thác nó, nó cũng không cho bạn biết lớp dữ liệu nào, xác thực biểu mẫu hoặc công cụ khuôn mẫu để sử dụng. Đó là tất cả vào bạn và yêu cầu của bạn.Và tất nhiên vì đây là một sự linh hoạt tuyệt vời vì nó gây ra một số nhầm lẫn cho người mới bắt đầu. Đây là cách cấu trúc thư mục ứng dụng thực tế gần giống như thế nào.

.   — 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 

Sau đó, đứng ở gốc của virtual environment bạn thường làm như sau để bắt đầu CherryPy trong môi trường phát triển. cherryd là cách đề xuất của CherryPy để chạy một ứng dụng.

. bin/activate 
cherryd -i serve -P website 

Templating

Bây giờ chúng ta hãy nhìn gần gũi hơn với các mẫu thư mục và những gì nó có thể trông như thế nào.

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

Để khai thác tính năng tuyệt vời Jinja2 của template inheritance, đây là bố cục xác định cấu trúc của một trang, các khe có thể được điền vào một trang cụ thể. Bạn có thể có bố cục cho trang web và bố cục cho thông báo qua email. Ngoài ra còn có một thư mục cho một phần, đoạn trích có thể tái sử dụng được sử dụng trên các trang khác nhau. Bây giờ hãy xem mã tương ứng với cấu trúc ở trên.

Tôi đã thực hiện những điều sau đây cũng có sẵn dưới dạng a runnable để điều hướng các tệp dễ dàng hơn, bạn có thể chạy và chơi với nó. Các đường dẫn bắt đầu bằng . như trong phần của cây đầu tiên.

website/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') 
    } 
} 

website/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() 

website/application/__ init__.py

phần đáng chú ý ở đây là một Công cụ CherryPy giúp tránh boilerplate liên quan đến renderin g mẫu. Bạn chỉ cần trả về dict từ trình xử lý trang CherryPy với dữ liệu cho mẫu. Theo nguyên tắc quy ước trên cấu hình, công cụ khi không được cung cấp với tên mẫu sẽ sử dụng classname/methodname.html ví dụ: user/profile.html. Để ghi đè mẫu mặc định, bạn có thể sử dụng @cherrypy.tools.template(name = 'other/name'). Cũng lưu ý rằng công cụ này cho thấy một phương pháp tự động, do đó bạn không cần phải thêm @cherrypy.expose trên

# -*- 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) 

website/application/controller.py

Như bạn có thể thấy với việc sử dụng công cụ này các trình xử lý trang trông khá sạch sẽ và phù hợp với các công cụ khác, ví dụ: 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() 

Trong ứng dụng demo này, tôi đã sử dụng blueprint tệp css, để minh họa cách xử lý tài nguyên tĩnh hoạt động. Đặt nó vào website/application/public/resource/css/blueprint.css. Phần còn lại là ít thú vị, chỉ cần mẫu Jinja2 cho đầy đủ.

website/application/view/layout/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> 

website/application/xem/trang/index/index.html

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

website/application/xem/trang/news/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 %} 

website/application/xem/trang/news/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 %} 

trang web/application/view/page/user/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 %} 

website/application/xem/trang/error.html

Đó là một trang 404.

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

website/application/xem/phần/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> 

Tài liệu tham khảo

Mã trên đi liền với phần backend của qooxdoo-website-skeleton. Đối với việc triển khai Debain đầy đủ của ứng dụng như vậy, cherrypy-webapp-skeleton có thể hữu ích.

+0

Sử dụng công cụ mẫu của bạn, có thể ghi đè mẫu từ bên trong hàm không? Ví dụ: nếu tôi muốn hành vi mẫu mặc định trong hầu hết các trường hợp, nhưng [nếu một nhóm điều kiện nhất định được đáp ứng chuyển sang một đường dẫn mẫu khác] (https://gist.github.com/anonymous/a0bebdac4245a564c1a5)? –

+1

@Justin Có. Bởi vì công cụ kết thúc tốt hơn trình xử lý, bạn cần phải thay đổi nó một chút để tác dụng phụ của trình xử lý của bạn không bị ghi đè. Chỉ cần tạo 'data = handler (* args, ** kwargs) hoặc {}' trên dòng đầu tiên trong 'TemplateTool._render' và mã của bạn sẽ hoạt động. – saaj