pystache
pystache copied to clipboard
Cannot render template tag within lambda
I have a case where I'm rendering a dictionary that also includes a lambda.
# Data
{
'money': '12345',
'format_currency': format_currency
}
# Template
{{/format_currency}}{{money}}{{/format_currency}}
def format_currency(value):
# value={{money}}
# how can I render the value?
The problem here is that I can't actually extract the value of {{money}}
because I don't have access to a rendering engine and the context. While trying to figure out a solution I came across this http://stackoverflow.com/a/8851957/17744. This essentially indicates that I should instead be writing something like this:
def lambda_renderer(f):
@wraps(f)
def wrapper(text, engine, context):
value = engine.render(text, context)
return f(value)
return wrapper
def format_currency(value):
# return value formatted as currency
{
'money': '12345',
'format_currency': lambda_renderer(format_currency)
}
What is everyones thoughts on this? Am I doing something wrong? How else would you extract the value from the template?
This is just a quick thought, but have you played around with using a view class as discussed in the README? You might be able to store references to the objects you need more easily there (e.g. the renderer instance). There's also this code comment about accessing the current context via the renderer, so that might also help.
I started with the view classes at first, but I felt like it was a little bit of unnecessary overhead. Also, In some cases I'm rendering mustache views within a django view using a custom template tag like this. This also allows me to create a custom django context processor that will attach global variables and settings to any context being rendered. This is how I'm actually using the format_currency
method. It's attached via the custom context processor.
For now I've just monkey patched the method to suite my needs but I think it's something that should be added.
def _new_render(self, engine, context):
"""
Monkey patching this method so that lambda methods can be passed
the rendering engine and the context to properly render values
"""
values = engine.fetch_section_data(context, self.key)
parts = []
for val in values:
if callable(val):
# START HACK #
template_part = self.template[self.index_begin:self.index_end]
args = inspect.getargspec(val).args
result = None
if len(args) == 3:
result = val(self.template[self.index_begin:self.index_end], engine, context)
else:
result = val(self.template[self.index_begin:self.index_end])
# END HACK #
val = engine._render_value(result, context, delimiters=self.delimiters)
parts.append(val)
continue
context.push(val)
parts.append(self.parsed.render(engine, context))
context.pop()
return unicode(''.join(parts))
_SectionNode.render = _new_render
I agree that Pystache should be enhanced to support this use case more easily. Issue #158 is from someone trying to do something similar. I'm not sure about the right API yet though. Any thoughts on a cleaner approach?
Why not to provide an API similar to one described in official mustache docs ?
Lambdas
When the value is a callable object, such as a function or lambda, the object will be invoked and passed the block of text. The text passed is the literal block, unrendered. {{tags}} will not have been expanded - the lambda should do that on its own. In this way you can implement filters or caching.
Template:
{{#wrapped}}
{{name}} is awesome.
{{/wrapped}}
Hash:
{
"name": "Willy",
"wrapped": function() {
return function(text, render) {
return "<b>" + render(text) + "</b>"
}
}
}
Output:
<b>Willy is awesome.</b>
i.e. this should be just
# in context of renderer:
...
if instanceof(callable,val):
template_part = self.template[self.index_begin:self.index_end]
render = lambda text: self.engine.render(text, context)
result = val(template_part, render)
...
and API should declare requirement for lambda/function of 2 arguments