Skip to content

Instantly share code, notes, and snippets.

@yoloseem
Last active December 15, 2015 22:59
Show Gist options
  • Save yoloseem/5336709 to your computer and use it in GitHub Desktop.
Save yoloseem/5336709 to your computer and use it in GitHub Desktop.
Jinja2 htmlfill Extension
"""Jinja2 x htmlfill
Known bugs: Can't skip empty dictionaries when htmlfilling is not needed.
# AUTHOR: HYUNJUN KIM <kim@hyunjun.kr>
# LICENSE: Distributed under MIT License.
"""
import formencode.htmlfill
import jinja2
import jinja2.ext
import jinja2.nodes
class FormExtension(jinja2.ext.Extension):
"""Jinja2 Extension for convenient using of HTML ``<form>`` element.
Works with `FormEncode htmlfill`__ ::
{% form 'user.signin', {'method': 'POST'}
with {'username': 'james'}, {'username': 'Invalid Username'} %}
<input type="text" name="username" />
<form:error name="username">
<input type="password" name="password" />
{% endform %}
Above code will be rendered like below ::
<form action="u/signin" method="POST">
<input type="text" name="username" value="james" />
<p class="error">Invalid Username</p>
<input type="password" name="password" />
</form>
__ http://www.formencode.org/en/latest/htmlfill.html
.. seealso::
gist --- `formencode htmlfill in jinja2 template`__
__ https://gist.github.com/4595277
"""
tags = set(['form'])
def parse(self, parser):
lineno = parser.stream.next().lineno
endpoint = parser.parse_expression()
endpoint_args = []
form_attrs = []
defaults = jinja2.nodes.Dict()
errors = jinja2.nodes.Dict()
while parser.stream.current.type != 'block_end':
while parser.stream.skip_if('comma'):
name = parser.stream.next_if('name')
if name and parser.stream.skip_if('assign'):
keyword = jinja2.nodes.Keyword(name.value,
parser.parse_expression())
endpoint_args.append(keyword)
else:
if name:
parser.stream.push(parser.stream.current)
parser.stream.current = name
form_attrs.append(parser.parse_expression())
name = parser.stream.next_if('name')
if name is None:
break
if name.value == 'with':
defaults = parser.parse_expression()
if parser.stream.skip_if('comma'):
errors = parser.parse_expression()
if isinstance(defaults, jinja2.nodes.Name):
defaults = jinja2.nodes.Getattr(jinja2.nodes.ContextReference(),
defaults.name, self.environment)
if isinstance(errors, jinja2.nodes.Name):
errors = jinja2.nodes.Getattr(jinja2.nodes.ContextReference(),
errors.name, self.environment)
body = parser.parse_statements(['name:endform'], drop_needle=True)
def output(*strings):
data = map(jinja2.nodes.TemplateData, strings)
return jinja2.nodes.Output(data)
body = [jinja2.nodes.CallBlock(
self.call_method('_htmlfill_support', (defaults, errors)),
[], [], body)]
action = jinja2.nodes.Call(jinja2.nodes.Name('url_for', 'load'),
[endpoint], endpoint_args, None, None)
form_attrs = self.call_method('_merge_dict', form_attrs)
for_form_attrs = jinja2.nodes.For(
jinja2.nodes.Tuple([jinja2.nodes.Name('__fattrname__', 'store'),
jinja2.nodes.Name('__fattrval__', 'store')],
'store'),
form_attrs,
[output(u' '),
jinja2.nodes.Output([jinja2.nodes.Name('__fattrname__', 'load')]),
output(u'="'),
jinja2.nodes.Output([jinja2.nodes.Name('__fattrval__', 'load')]),
output(u'"')],
[], None, False
)
start = [output(u'<form action="'),
jinja2.nodes.Output([action]),
output(u'" method="POST"'),
for_form_attrs,
output(u'>')]
body[:0] = start
body.append(output(u'</form>'))
return body
def _merge_dict(self, *dicts):
result = {}
for d in dicts:
result.update(d)
return result.iteritems()
def _htmlfill_support(self, defaults, errors, caller):
formatters = {
'default': lambda msg: u'<ul class="errors"><li>{0}</li></ul>' \
.format(msg)
}
return formencode.htmlfill.render(caller(), defaults, errors,
error_formatters=formatters)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment