2013-05-30 15 views
9

Questa è la prima volta che passo allo sviluppo web in python. La mia unica altra esperienza è PHP, e non ho mai usato un framework prima, quindi trovo questo molto intimidatorio e confuso.Introduzione a Cherrypy e Jinja2

Sono interessato all'apprendimento di CherryPy/Jinja2 per creare un monitor ZFS per il mio NAS. Ho letto le nozioni di base sui documenti su CherryPy/Jinja2 ma trovo che i campioni siano disgiunti e troppo semplicistici, non capisco davvero come rendere queste due cose "si uniscono" con grazia.

Alcune domande che ho:

  1. C'è un semplice tutorial mostra come si fanno CherryPy e Jinja2 lavorare insieme piacevolmente? Sto cercando dei campioni troppo semplici, come gli esempi sui documenti CherryPy/Jinja2 o il modo per renderli complessi. (esempio: https://github.com/jovanbrakus/cherrypy-example).

  2. Esiste un modo standardizzato o "previsto" per creare applicazioni Web per CherryPy? (esempio: come dovrebbe essere la mia struttura di directory? C'è un modo per dichiarare cose statiche, è anche necessario?)

  3. Qualcuno ha raccomandato la documentazione per questo o la documentazione online è la migliore risorsa?

risposta

26

Congratulazioni per aver scelto Python, sono sicuro che imparerete ad amarlo come hanno I.

Per quanto riguarda CherryPy, io non sono un esperto, ma è stato anche nella stessa barca, come si alcuni giorni fa e sono d'accordo sul fatto che i tutorial siano un po 'scomposti in parti.

Per l'integrazione di Jinja2, come nel loro doc page, lo snippet di HTML dovrebbe essere specificato che si tratta del file di modello e come tale salvato nel percorso /templates/index.html. Hanno anche utilizzato variabili che non corrispondevano all'esempio di codice del modello e al campione del controller.

Il sotto è invece un esempio di lavoro completo di un semplice ciao mondo utilizzando CherryPy e 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> 

Quindi nella shell/prompt dei comandi, servire l'app utilizzando:

python main.py 

E nel browser si dovrebbe essere in grado di vederlo al http://localhost:8080

che si spera consente di connettersi Jinja2 template per il vostro CherryPy app. CherryPy è davvero una struttura leggera e molto flessibile, in cui puoi scegliere molti modi diversi per strutturare il tuo codice e le strutture dei file.

+2

Ho avuto problemi a trovare una buona struttura del progetto e questo post e i collegamenti inclusi hanno aiutato a chiarire un po 'le cose. https://groups.google.com/forum/?fromgroups#!topic/cherrypy-users/L7YXZD_55ec –

+0

Bello! questa introduzione è abbastanza per me per iniziare per CherryPy e Mako. –

+0

Voto positivo, ma per favore aiuto, perché il mio problema continua con Nginx http://stackoverflow.com/questions/23359095/how-to-put-cherrypy-wsgi-behind-nginx. – Alex

10

Struttura dell'applicazione

Prima sulla struttura di directory standard di un progetto. Non ce n'è, poiché CherryPy non lo impone, né ti dice quale livello dati, convalida moduli o motore modello utilizzare. Dipende tutto da te e dalle tue esigenze.E naturalmente visto che questa è una grande flessibilità in quanto provoca confusione ai principianti. Ecco come può apparire una struttura di directory dell'applicazione simile alla parola reale.

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

poi in piedi nella radice di virtual environment di solito non come segue per avviare CherryPy in ambiente di sviluppo. cherryd è il modo suggerito da CherryPy di ​​eseguire un'applicazione.

. bin/activate 
cherryd -i serve -P website 

Templating

Ora diamo un'occhiata più vicina alla directory dei modelli e che cosa può sembrare.

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

di sfruttare bella caratteristica di Jinja2 di template inheritance, qui ci sono i layout che definiscono la struttura di una pagina, gli slot che possono essere riempiti in una pagina particolare. Potresti avere un layout per un sito Web e un layout per le notifiche via email. C'è anche una directory per una parte, lo snippet riutilizzabile usato su diverse pagine. Ora vediamo il codice che corrisponde alla struttura sopra.

Ho reso disponibile la seguente anche come a runnable che è più facile da navigare i file, è possibile eseguire e giocare con esso. I percorsi iniziano con . come nell'albero della prima sezione.

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

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

sito/applicazione/__ init__.py

parte notevole: ecco un Strumento CherryPy che aiuta ad evitare lo schema relativo al renderin g modelli. Hai solo bisogno di restituire un dict dal gestore di pagine CherryPy con i dati per il modello. Seguendo il principio di convenzione sulla configurazione, lo strumento quando non fornito con il nome del modello utilizzerà classname/methodname.html ad es. user/profile.html. Per sovrascrivere il modello predefinito è possibile utilizzare @cherrypy.tools.template(name = 'other/name'). Si noti inoltre che lo strumento espone un metodo automatico, in modo da non avete bisogno di aggiungere @cherrypy.expose in cima

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

sito/applicazione/controller.py

Come si può vedere con l'utilizzo dello strumento i gestori di pagine sembrano piuttosto puliti e saranno coerenti con altri strumenti, ad es 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() 

In questa demo app ho usato blueprint file css, per dimostrare come statica lavori di movimentazione di risorse. Mettilo nel website/application/public/resource/css/blueprint.css. Il resto è meno interessante, solo i modelli Jinja2 per completezza.

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

sito/applicazione/view/page/index/index.html

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

sito/applicazione/view/page/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 %} 

sito/applicazione/view/page/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 %} 

sito web/applicazione/vista/pagina/utente/profilo.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 %} 

sito/applicazione/view/page/error.html

Si tratta di un 404-pagina.

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

sito/applicazione/view/parte/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> 

Riferimenti

codice sopra va a stretto contatto con la sezione di back-end qooxdoo-website-skeleton. Per l'implementazione completa di Debain di tale applicazione, cherrypy-webapp-skeleton potrebbe essere utile.

+0

Utilizzando lo strumento modello, sarebbe possibile sovrascrivere il modello all'interno della funzione? Ad esempio, se volessi il comportamento predefinito del modello nella maggior parte dei casi, ma [se un certo insieme di condizioni sono soddisfatte passa a un altro percorso del modello] (https://gist.github.com/anonymous/a0bebdac4245a564c1a5)? –

+1

@Justin Sì. Poiché lo strumento avvolge il gestore, è necessario modificarlo un po 'in modo che l'effetto collaterale del gestore non venga sovrascritto. Basta rendere 'data = handler (* args, ** kwargs) o {}' essere sulla prima riga in 'TemplateTool._render' e il tuo codice funzionerà. – saaj