views:

94

answers:

2

I've got a dashboard that namespaces the context for each dashboard item. Is there a quick way I can set all the values of a dictionary to the keys in a template?

I want to reuse templates and not always namespace my variables.

My context can be simplified to look something like this:

{
 "business": {"businesses": [], "new_promotions": []}, 
 "messages": {"one_to_one": [], "announcements": []
}

So in a with statement I want to set all the business items to be local for the including. To do this currently I have to set each variable individually:

{% with %}
  {% set businesses = business["businesses"] %}
  {% set new_promotions = business["new_promotions"] %}
  {% include "businesses.html" %}
{% endwith %}

I tried:

{% with %}
  {% for key, value in my_dict %}
    {% set key = value %}
  {% endfor %}
  {% include "businesses.html" %}
{% endwith %}

But they only have scope in the for loop so aren't scoped in the include...

A: 

I've found a work around - by creating a context function I can render the template and directly set the context or merge the context (not sure thats good practise though).

@jinja2.contextfunction
def render(context, template_name, extra_context, merge=False):
    template = jinja_env.get_template(template_name)

    # Merge or standalone context?
    if merge:
        ctx = context.items()
        ctx.update(extra_context)
    else:
        ctx = extra_context

    return jinja2.Markup(template.render(ctx))

So my templates look like:

{{ render("businesses.html", business) }}
Ross
+2  A: 

Long story short: you can't set arbitrary variables in the context. The {% set key = value %} is just setting the variable named key to the given value.

The reason is because Jinja2 compiles templates down to Python code. (If you want to see the code your template generates, download the script at http://ryshcate.leafstorm.us/paste/71c95831ca0f1d5 and pass it your template's filename.) In order to make processing faster, it creates local variables for every variable you use in the template (only looking up the variable in the context the first time it's encountered), as opposed to Django, which uses the context for all variable lookups.

In order to generate this code properly, it needs to be able to track which local or global variables exist at any given time, so it knows when to look up in the context. And setting random variables would prevent this from working, which is why contextfunctions are not allowed to modify the context, just view it.

What I would do, though, is instead of having your business-displaying code be an included template, is have it be a macro in another template. For example, in businesses.html:

{% macro show_businesses(businesses, new_promotions) %}
  {# whatever you're displaying... #}
{% endmacro %}

And then in your main template:

{% from "businesses.html" import show_businesses %}
{% show_businesses(**businesses) %}

Or, better yet, separate them into two separate macros - one for businesses, and one for new promotions. You can see a lot of cool template tricks at http://bitbucket.org/plurk/solace/src/tip/solace/templates/, and of course check the Jinja2 documentation at http://jinja.pocoo.org/2/documentation/templates.

LeafStorm
Thanks LeafStorm for your insights and the examples.The business.html template does already use macros, but not all the dashboard items do, but you're totally right it is good to split up the business logic and display.In this case and for me I think having a contextfunction that outputs the template based on the passed context is preferable as it means less template code and keeps things DRY.
Ross