Skip to content

Instantly share code, notes, and snippets.

@faassen
Last active August 29, 2015 14:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save faassen/ca56fb32f0ebc9d6e0da to your computer and use it in GitHub Desktop.
Save faassen/ca56fb32f0ebc9d6e0da to your computer and use it in GitHub Desktop.
draft of how to integrate template languages into Morepath

Templates

Introduction

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.

Details

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 as templates/foo.pt to refer to foo.pt in a templates 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.

Integrating a template language

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, so render_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 original render function which is passed in as an argument as original_render function. It could also create a WebOb response by itself.

Implementation notes

* We need to refactor Morepath to ensure original_rendercannot beNoneas 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 bytemplate_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 thetemplate_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 instatic_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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment