Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot render template tag within lambda #157

Open
micahlmartin opened this issue Nov 5, 2013 · 5 comments
Open

Cannot render template tag within lambda #157

micahlmartin opened this issue Nov 5, 2013 · 5 comments

Comments

@micahlmartin
Copy link

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?

@cjerdonek
Copy link
Collaborator

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.

@micahlmartin
Copy link
Author

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.

@micahlmartin
Copy link
Author

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

@cjerdonek
Copy link
Collaborator

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?

@dikderoy
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants