Getting started with Cherrypy and Jinja2
Congratulations on choosing Python, I'm sure you'll learn to love it as have I.
Regarding CherryPy, I'm not an expert, but was also in the same boat as you a few days ago and I'd agree that the tutorials are a little disjointed in parts.
For integrating Jinja2, as in their doc page, the snippet of HTML should have been specified that it is the template file and as such saved in the path /templates/index.html. They also used variables that didn't match up in the template code sample and controller sample.
The below is instead a complete working sample of a simple hello world using CherryPy and 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>
Then in your shell/command prompt, serve the app using:
python main.py
And in your browser you should be able to see it at http://localhost:8080
That hopefully helps you to connect Jinja2 templating to your CherryPy app. CherryPy really is a lightweight and very flexible framework, where you can choose many different ways to structure your code and file structures.
Application structure
First about standard directory structure of a project. There is none, as CherryPy doesn't mandate it, neither it tells you what data layer, form validation or template engine to use. It's all up to you and your requirements. And of course as this is a great flexibility as it causes some confusion to beginners. Here's how a close to real-word application directory structure may look like.
. — 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
Then standing in the root of virtual environment you usually do the following to start CherryPy in development environment. cherryd
is CherryPy's suggest way of running an application.
. bin/activate
cherryd -i serve -P website
Templating
Now let's look closer to the template directory and what it can look like.
.
├── layout
│ └── main.html
├── page
│ ├── index
│ │ └── index.html
│ ├── news
│ │ ├── list.html
│ │ └── show.html
│ ├── user
│ │ └── profile.html
│ └── error.html
└── part
└── menu.html
To harness nice Jinja2's feature of template inheritance, here are layouts which define structure of a page, the slots that can be filled in a particular page. You may have layout for a website and layout for email notifications. There's also a directory for a part, reusable snippet used across different pages. Now lets see the code that corresponds the structure above.
I've made the following also available as a runnable which is easier to navigate files, you can run and play with it. The paths start with .
like in the first section's tree.
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
Notable part here is a CherryPy tool which helps to avoid boilerplate related with rendering templates. You just need return a dict
from CherryPy page handler with data for the template. Following convention-over-configuration principle, the tool when not provided with template name will use classname/methodname.html
e.g. user/profile.html
. To override the default template you can use @cherrypy.tools.template(name = 'other/name')
. Also note that the tool exposes a method automatically, so you don't need to append @cherrypy.expose
on top
# -*- 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
As you can see with use of the tool page handlers look rather clean and will be consistent with other tools, e.g. 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 this demo app I used blueprint css file, to demonstrate how static resource handling works. Put it in website/application/public/resource/css/blueprint.css
. The rest is less interesting, just Jinja2 templates for completeness.
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/view/page/index/index.html
{% extends 'layout/main.html' %}
{% block content %}
<div class='span-18 last'>
<p>Root page</p>
</div>
{% endblock %}
website/application/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 %}
website/application/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 %}
website/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/view/page/error.html
It's a 404-page.
{% extends 'layout/main.html' %}
{% block content %}
<h1>Error has happened</h1>
{% endblock %}
website/application/view/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>
References
Code above goes closely with backend section of qooxdoo-website-skeleton. For full-blown Debain deployment of such application, cherrypy-webapp-skeleton may be useful.