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.ptis loaded from the same directory as the Python module. You can also use paths such astemplates/foo.ptto refer tofoo.ptin atemplatessubdirectory. - When the
person_defaultview 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
extensionis 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_renderfunction as passed into the view decorator, sorender_htmlfor instance. It takes the object to render and the request and returns a webob response object.
- It needs to return a
renderfunction which takes the object to render (output from view function) and the request. The implementation of this can use the originalrenderfunction which is passed in as an argument asoriginal_renderfunction. It could also create a WebOb response by itself.
- We need to refactor Morepath to ensure original_render`` cannot be
Noneas it is now with the plainviewdirective. 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_engineis 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_languagedirective, and theviewdirective 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.