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:
1from mako.template import Template
2
3def mako_renderer(tmpl_name, **kw):
4 template = Template(filename='/template_dir/%s.mak' % tmpl_name)
5 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<span tal:define="name name|field.name;
2 css_class css_class|field.widget.css_class;
3 oid oid|field.oid;
4 mask mask|field.widget.mask;
5 mask_placeholder mask_placeholder|field.widget.mask_placeholder;
6 style style|field.widget.style;
7 autofocus autofocus|field.autofocus"
8 tal:omit-tag="">
9 <input type="text" name="${name}" value="${cstruct}"
10 tal:attributes="class string: form-control ${css_class or ''};
11 style style;
12 autofocus autofocus;
13 attributes|field.widget.attributes|{};"
14 id="${oid}"/>
15 <script tal:condition="mask" type="text/javascript">
16 deform.addCallback(
17 '${oid}',
18 function (oid) {
19 $("#" + oid).mask("${mask}",
20 {placeholder:"${mask_placeholder}"});
21 });
22 </script>
23</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<input type="text" name="${field.name}" value="${cstruct}"
2% if field.widget.size:
3size=${field.widget.size}
4% endif
5/>
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:
1from mymakorenderer import mako_renderer
2
3from deform import Form
4Form.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:
1from mymakorenderer import mako_renderer
2from deform import Form
3
4# ...
5schema = SomeSchema()
6form = Form(schema, renderer=mako_renderer)