import collections
import formencode.htmlfill
import jinja2
import jinja2.ext
from jinja2 import nodes
__all__ = ['FormFillExtension']
[docs]class FormFillExtension(jinja2.ext.Extension):
"""Jinja2 extension for filling HTML forms via :mod:`formencode.htmlfill`.
For example, this code:
.. code-block:: html+jinja
{% formfill {'username': 'robert', 'email': 'robert153@usrobots.com'}
with {'username': 'This name is invalid'} %}
<form action="/register" method="POST">
<input type="text" name="username" />
<form:error name="username">
<input type="password" name="password" />
<input type="email" name="email" />
</form>
{% endformfill %}
will be rendered like below:
.. code-block:: html
<form action="/register" method="POST">
<input type="text" name="username" class="error" value="robert" />
<span class="error-message">This name is invalid</span>
<input type="password" name="password" value="" />
<input type="email" name="email" value="robert153@usrobots.com" />
</form>
**Syntax:**
.. code-block:: jinja
{% formfill <defaults> [with <errors>] %}
body
{% endformfill %}
:param defaults: a :term:`mapping` that contains default values of the
input field (including ``select`` and ``textarea``)
surrounded in the template tag.
Keys contain a value of ``name`` attribute of the input
field, and values contain its default value.
:param errors: a :term:`mapping` that contains error messages of the
input fields. this value will also effect ``class``
attribute of the input field.
:returns: rendered forms
This extension provides the additional variables in the Jinja2 environment:
.. attribute:: jinja2.Environment.formfill_config
The default rendering configuration of the ``formfill`` tag.
This property accepts the same arguments of
:func:`formencode.htmlfill.render`, except ``form``, ``defaults``,
``errors`` and ``error_formatters``.
.. attribute:: jinja2.Environment.formfill_error_formatters
The :term:`mapping` of error formatters and its name.
Formatters are functions or callable objects that take the error text
as a single argument, and returns a formatted text as a string.
.. seealso:: http://www.formencode.org/en/latest/htmlfill.html#errors
"""
tags = frozenset(['formfill'])
def __init__(self, environment):
super(FormFillExtension, self).__init__(environment)
environment.extend(
formfill_config={},
formfill_error_formatters=dict(DEFAULT_ERROR_FORMATTERS),
)
def parse(self, parser):
token = next(parser.stream)
defaults = parser.parse_expression()
if parser.stream.skip_if('name:with'):
errors = parser.parse_expression()
else:
errors = nodes.Const({})
body = parser.parse_statements(['name:endformfill'], drop_needle=True)
return nodes.CallBlock(
self.call_method('_formfill_support', [defaults, errors]),
[], [], body).set_lineno(token.lineno)
def _formfill_support(self, defaults, errors, caller):
if isinstance(defaults, jinja2.runtime.Undefined):
defaults = {}
if isinstance(errors, jinja2.runtime.Undefined):
errors = {}
if not isinstance(defaults, collections.Mapping):
raise TypeError("argument 'defaults' should be "
"collections.Mapping, not {0!r}".format(defaults))
if not isinstance(errors, collections.Mapping):
raise TypeError("argument 'errors' should be collections.Mapping, "
"not {0!r}".format(errors))
rv = caller()
return formencode.htmlfill.render(
rv, defaults, errors,
error_formatters=self.environment.formfill_error_formatters,
**self.environment.formfill_config)
def default_formatter(error):
"""Escape the error, and wrap it in a span with class ``error-message``"""
quoted = formencode.htmlfill.escape_formatter(error)
return u'<span class="error-message">{0}</span>'.format(quoted)
DEFAULT_ERROR_FORMATTERS = dict(formencode.htmlfill.default_formatter_dict)
DEFAULT_ERROR_FORMATTERS.update(
default=default_formatter,
)