I have Python 3.2 set up with Apache via mod_wsgi. I have CherryPy 3.2 serving a simple "Hello World" web page. I'd like to start templating using Jinja2 as I build out the site. I'm new to Python and therefore don't know much about Python, CherryPy, or Jinja.
Using the code below, I can load the site root (/
) and the products page (/products
) with their basic text. That at least lets me know I've got Python, mod_wsgi, and CherryPy set up somewhat properly.
Because the site will have many pages, I'd like to implement the Jinja template in a way that prevents me from having to declare and render the template in each page handler class. As far as I can tell, the best way to do that is by wrapping the PageHandler, similar to these examples:
I've implemented the code in the second example, but it doesn't change anything.
[more details after code]
wsgi_handler.py - A mash-up of a few tutorials and examples
import sys, os
abspath = os.path.dirname(__file__)
sys.path.append(abspath)
sys.path.append(abspath + '/libs')
sys.path.append(abspath + '/app')
sys.stdout = sys.stderr
import atexit
import threading
import cherrypy
from cherrypy._cptools import HandlerWrapperTool
from libs.jinja2 import Environment, PackageLoader
# Import from custom module
from core import Page, Products
cherrypy.config.update({'environment': 'embedded'})
env = Environment(loader=PackageLoader('app', 'templates'))
# This should wrap the PageHandler
def interpolator(next_handler, *args, **kwargs):
template = env.get_template('base.html')
response_dict = next_handler(*args, **kwargs)
return template.render(**response_dict)
# Put the wrapper in place(?)
cherrypy.tools.jinja = HandlerWrapperTool(interpolator)
# Configure site routing
root = Page()
root.products = Products()
# Load the application
application = cherrypy.Application(root, '', abspath + '/app/config')
/app/config
[/]
request.dispatch: cherrypy.dispatch.MethodDispatcher()
core module classes
class Page:
exposed = True
def GET(self):
return "got Page"
def POST(self, name, password):
return "created"
class Products:
exposed = True
def GET(self):
return "got Products"
def POST(self, name, password):
return "created"
Based on what I read on a Google Group I figured I might need to "turn on" the Jinja tool, so I updated my config to this:
/app/config
[/]
tools.jinja.on = True
request.dispatch: cherrypy.dispatch.MethodDispatcher()
After updating the config, the site root and products pages display an CherryPy generated error page "500 Internal Server Error". No detailed error messages are found in the logs (at least not in the logs I'm aware of).
Unless it came pre-installed, I know I probably need the Jinja Tool that's out there, but I don't know where to put it or how to enable it. How do I do that?
Am I going about this the right way, or is there some better way?
Edit (21-May-2012):
Here is the Jinja2 template I'm working with:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
I figured it out.
In the interpolator
function, the next_handler
function call the original PageHandler (Page.GET
or Products.GET
in this case). Those original PageHandlers return strings while the interpolator
function is treating the response like a python dict (dictionary), hence the double asterisk when it's passed to template.render
as **response_dict
.
The Jinja template has a placeholder for title
, so it needs a title to be defined. Passing a plain string to the render function doesn't define what title should be. We need to pass an actual dict to the render function (or nothing at all, but what good is that?).
Note: For either of these fixes, the jinja tool does need to be enabled, as shown in the question by setting tools.jinja.on
to True in the config.
Define the title as the render function is called. To do this I need to change this line:
return template.render(**response_dict) # passing the string as dict - bad
to this:
return template.render(title=response_dict) # pass as string and assign to title
Like this, the template renders with my PageHandler text as the page title.
Because the template will grow to be more complex, one render function probably won't always be able to correctly assign the necessary placeholders. It's probably a good idea to let the original page handler return an actual dict with the template's many placeholders assigned.
Leave the interpolator function as it was:
def interpolator(next_handler, *args, **kwargs):
template = env.get_template('base.html')
response_dict = next_handler(*args, **kwargs)
return template.render(**response_dict)
Update Page and Products to return actual dicts that define the value's for the template's placeholders:
class Page:
exposed = True
def GET(self):
dict = {'title' : "got Page"}
return dict
def POST(self, name, password):
# This should be updated too
# I just haven't used it yet
return "created"
class Products:
exposed = True
def GET(self):
dict = {'title' : "got Products"}
return dict
def POST(self, name, password):
# This should be updated too
# I just haven't used it yet
return "created"
Like this, the template renders with my PageHandler title text as the page title.