This is my first time delving into web development in python. My only other experience is PHP, and I never used a framework before, so I'm finding this very intimidating and confusing.
I'm interested in learning CherryPy/Jinja2 to make a ZFS Monitor for my NAS. I've read through the basics of the docs on CherryPy/Jinja2 but I find that the samples are disjointed and too simplistic, I don't really understand how to make these 2 things "come together" gracefully.
Some questions I have:
Is there a simple tutorial shows how you make CherryPy and Jinja2 work together nicely? I'm either finding samples that are too simple, like the samples on CherryPy / Jinja2 docs, or way to complex. (example: https://github.com/jovanbrakus/cherrypy-example).
Is there a standardized or "expected" way to create web applications for CherryPy? (example: What should my directory structure look like? Is there a way to declare static things; is it even necessary?)
Does anyone have recommended literature for this or is the online documentation the best resource?
Jinja2 is a modern day templating language for Python developers. It was made after Django's template. It is used to create HTML, XML or other markup formats that are returned to the user via an HTTP request. You can read more here.
CherryPy is designed on the concept of multithreading. This gives it the benefit of handling multiple tasks at the same time. It also takes a modular approach, following the Model View Controller (MVC) pattern to build web services; therefore, it's fast and developer-friendly.
CherryPy comes with its own web (HTTP) server. That is why CherryPy is self-contained and allows users to run a CherryPy application within minutes of getting the library. The web server acts as the gateway to the application with the help of which all the requests and responses are kept in track.
Jinja2 is a commonly-used templating engine for web frameworks such as Flask, Bottle, Morepath and, as of its 1.8 update, optionally Django as well. Jinja2 is also used as a template language by configuration management tool Ansible and the static site generator Pelican, among many other similar tools.
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.
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
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With