You can use the special template
argument with the html
directive. Here is an example that uses the `Chameleon template
engine`_ using the more.chameleon
extension:
from more.chameleon import ChameleonApp class App(ChameleonApp): pass @App.html(model=Person, template='person.pt') def person_default(self, request): return { 'name': self.name }
person.pt
is a file sitting in the same directory as the Python
module that contains this:
<html> <body> <p>Hello ${name}!</p> </body> </html>
Then given a person with a name
attribute of "world"
, the
output is the following HTML:
<html> <body> <p>Hello world!</p> </body> </html>
So the template is applied on the output of the view function. This results in a rendered template that is returned as a response.
Templates are loaded during configuration time. The file extension of
the extension (such as .pt
) indicates the template engine to use.
Morepath itself does not support any template language out of the box,
but lets you register a template language engine for a file
extension. You can reuse a template language integration in the same
way you reuse any Morepath code: by subclassing the app class that
implements it in your app.
The template language integration works like this:
- During startup time,
person.pt
is loaded from the same directory as the Python module. You can also use paths such astemplates/foo.pt
to refer tofoo.pt
in atemplates
subdirectory. - When the
person_default
view is rendered, its return value is passed into the template, along with the request. The template language integration code then makes this information available for in the template -- the details are up to the integration (and should be documented there).
The template
argument works not just with html
but also with
view
, json
, and any other view functions you may have.
A template in Morepath is actually just a convenient way to generate a
render
function for a view. That render
function is then used
just like when you write it manually: it's given the return value of
the view function along with a request object, and should return a
WebOb response.
Here is an example of how you can integrate the Chameleon template engine
for .pt
files:
@App.template_engine(extension='.pt') def get_chameleon_render(app, template_path, original_render): # construct Chameleon config dict from settings somehow config = make_config(app.registry.settings) template = chameleon.PageTemplateFile(template_path, config) def render(obj, request): variables = { 'request': request } return original_render(template(options=obj, **variables), request) return render
Some details:
- The
extension
is what hooks this up to uses of the template system. - The decorated function gets three arguments:
app
: the app that invoked this. Can be useful to get the settings.template_path
: the absolute path to the template to load.- the
original_render
function as passed into the view decorator, sorender_html
for instance. It takes the object to render and the request and returns a webob response object.
- It needs to return a
render
function which takes the object to render (output from view function) and the request. The implementation of this can use the originalrender
function which is passed in as an argument asoriginal_render
function. It could also create a WebOb response by itself.
- We need to refactor Morepath to ensure original_render`` cannot be
None
as it is now with the plainview
directive. See issue #283
. * If the template file is missing the system shouldn't even start
up. So, we do a check for this in the framework. This creates the appropriate error message about this in Morepath.
The function decorated by
template_engine
is only called once for a template in an app, during configuration time. This means in the appropriate compiled template caching behavior should the template engine not already take care of this (unless auto-reload is configured for debugging purposes).The template engine functions should be registered by the
template_language
directive, and theview
directive should depend on it, so that it can call them once the views are being registered.Some (many?) template languages have ways to lead them reuse templates internally. An example of this is Chameleon, which has a 'load' expression. By default this loads a template relative to the current template, which is actually all right.
An alternative approach is to pass in an object which makes various macros available. I think this is something we should avoid doing in the framework.
Should integrations pass in 'application_url'? It's useful to be able to link to directly, but perhaps better is to use request.link() with an instance of the root object.
Should integrations pass in
static_url
? It can be useful to be able to link to things like images. I think it is better to let this functionality exist on request through BowerStatic integration.