Templates

Introduction

A set of Chameleon templates is used by the default widget set present in deform to make it easier to customize the look and feel of form renderings.

Alternative template engines

Deform is compatible with any template engine.

Even though Deform widgets themselves are rendered in Chameleon, you can place the forms on pages in any template language. For example, Websauna places Deform forms inside Jinja2 page templates.

The rendered forms are simply Python strings.

Adding more widget templates

If your application is supplying its own templates for Deform forms, you need to add those template paths to the Chameleon lookup. Chameleon template paths are stored in an in-process global variable.

from deform.renderer import configure_zpt_renderer

# Make Deform widgets aware of our widget template paths
configure_zpt_renderer(["websauna.system.form:templates/deform"])

Overriding the default templates

The default widget set uses templates that live in the templates directory of the deform package. If you are comfortable using the Chameleon templating system, but you need to override only some of these templates, you can create your own template directory and copy the template you wish to customize into it. You can then either configure your new template directory to be used for all forms or for specific forms as described below.

For relevant API documentation see the deform.ZPTRendererFactory class and the deform.Field class renderer argument.

Overriding for all forms

To globally override templates, use the deform.Field.set_zpt_renderer() class method to change the settings associated with the default ZPT renderer:

from pkg_resources import resource_filename
from deform import Form

deform_templates = resource_filename('deform', 'templates')
search_path = ('/path/to/my/templates', deform_templates)

Form.set_zpt_renderer(search_path)

Now the templates in /path/to/my/templates will be used in preference to the default templates whenever a form is rendered. Any number of template directories can be put into the search path and will be searched in the order specified with the first matching template found being used.

Overriding for specific forms

If you only want to change the templates used for a specific form, or even for the specific rendering of a form, you can pass a renderer argument to the deform.Form constructor, e.g.:

from deform import ZPTRendererFactory
from deform import Form
from pkg_resources import resource_filename

deform_templates = resource_filename('deform', 'templates')
search_path = ('/path/to/my/templates', deform_templates)
renderer = ZPTRendererFactory(search_path)

form = Form(someschema, renderer=renderer)

When the above form is rendered, the templates in /path/to/my/templates will be used in preference to the default templates. Any number of template directories can be put into the search path and will be searched in the order specified with the first matching template found being used.

Using an alternative templating system

A renderer is used by the each widget implementation in deform to render HTML from a set of templates. By default, each of the default Deform widgets uses a template written in the Chameleon ZPT templating language. If you would rather use a different templating system for your widgets, you can. To do so, you need to:

  • Write an alternate renderer that uses the templating system of your choice.
  • Optionally, convert all the existing Deform templates to your templating language of choice. This is only necessary if you choose to use the widgets that ship as part of Deform.
  • Set the default renderer of the deform.Form class.

Creating a Renderer

A renderer is simply a callable that accepts a single positional argument, which is the template name, and a set of keyword arguments. The keyword arguments it will receive are arbitrary, and differ per widget, but the keywords usually include field, a field object, and cstruct, the data structure related to the field that must be rendered by the template itself.

Here's an example of a (naive) renderer that uses the Mako templating engine:

1
2
3
4
5
from mako.template import Template

def mako_renderer(tmpl_name, **kw):
    template = Template(filename='/template_dir/%s.mak' % tmpl_name)
    return template.render(**kw)

Note

A more robust implementation might use a template loader that does some caching, or it might allow the template directory to be configured.

Note the mako_renderer function we have created actually appends a .mak extension to the tmpl_name it is passed. This is because Deform passes a template name without any extension to allow for different templating systems to be used as renderers.

Our mako_renderer renderer is now ready to have some templates created for it.

Converting the Default Deform Templates

The deform package contains a directory named templates. You can see the current trunk contents of this directory by browsing the source repository. Each file within this directory and any of its subdirectories is a Chameleon ZPT template that is used by a default Deform widget.

For example, the ZPT template textinput.pt, which is used by the deform.widget.TextInputWidget widget and renders a text input control, looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<span tal:define="name name|field.name;
                  css_class css_class|field.widget.css_class;
                  oid oid|field.oid;
                  mask mask|field.widget.mask;
                  mask_placeholder mask_placeholder|field.widget.mask_placeholder;
                  style style|field.widget.style;
                  autofocus autofocus|field.autofocus"
      tal:omit-tag="">
    <input type="text" name="${name}" value="${cstruct}"
           tal:attributes="class string: form-control ${css_class or ''};
                           style style;
                           autofocus autofocus;
                           attributes|field.widget.attributes|{};"
           id="${oid}"/>
    <script tal:condition="mask" type="text/javascript">
      deform.addCallback(
         '${oid}',
         function (oid) {
            $("#" + oid).mask("${mask}",
                 {placeholder:"${mask_placeholder}"});
         });
    </script>
</span>

If we created a Mako renderer, we would need to create an analogue of this template. Such an analogue should be named textinput.mak and might look like this:

1
2
3
4
5
<input type="text" name="${field.name}" value="${cstruct}"
% if field.widget.size:
size=${field.widget.size}
% endif
/>

Whatever the body of the template looks like, the resulting textinput.mak should be placed in a directory that is meant to house other Mako template files which are going to be consumed by Deform. You will need to convert each of the templates that exist in the Deform templates directory and its subdirectories, and put all of the resulting templates into your private mako templates directory too, retaining any directory structure (in other words, retaining the fact that there is a readonly directory and converting its contents).

Configuring Your New Renderer as the Default

Once you have created a new renderer and created templates that match all the existing Deform templates, you can now configure your renderer to be used by Deform. In startup code, add something like:

1
2
3
4
from mymakorenderer import mako_renderer

from deform import Form
Form.set_default_renderer(mako_renderer)

The deform widget system will now use your renderer as the default renderer.

Note that calling deform.Field.set_default_renderer() will cause this renderer to be used by default by all consumers in the process in which it is invoked. This is potentially undesirable. You may need the same process to use more than one renderer, perhaps because that same process houses two different Deform-using systems. In this case, instead of using the set_default_renderer method, you can write your application in such a way that it passes a renderer to the Form constructor:

1
2
3
4
5
6
from mymakorenderer import mako_renderer
from deform import Form

# ...
schema = SomeSchema()
form = Form(schema, renderer=mako_renderer)