Python (Jinja2) variable inside a variable - python

I am trying to iterate over a dictionary in a Jinja2 template (in Ansible). One of the arrays or keys in the dictionary is 'abcd'
This {{ item.value.abcd.port }} works fine, but key 'abcd' varies in each dictionary.
I am looking to do something like below using a variable 'nginx_dir'.
{% set nginx_dir = item.value.keys().1 %}
{% set my_port = item.value.nginx_dir.port %}
Or without using a variable at all, something like this
{{ item.value.[item.value.keys().1].port }}

I had to use either of these to use a variable inside a variable.
{% set my_port = item.value.get(nginx_dir).port %}
{% set my_port = item.value[nginx_dir].port %}
I didn't wanted to hardcode my Jinja2 templates, this is exactly what I was looking for.

Related

How to pass individual jinja variable in a jinja loop to python function as a parameter?

I want to pass an individual jinja variable inside of a jinja loop to a python function as a parameter. In this case, I need to pass {{ book.isbn }} inside of a jinja loop, to a python function in order to use this value.
I have tried to use href="{{ url_for(...) }}" but it only triggers when I click the link. I need it to trigger every time the loop is running.
{% for book in books %}
{{ book.isbn }}
{% endfor %}
I want to get {{ book.isbn }}, so that I can process it and output another variable based on that value. I also want to do this for every "book" value inside the books list, therefore, I can't manually do this elsewhere.
I think if you pass your function as a keyword argument in your render method it should do the trick.
from jinja2 import Template
template = Template("""
{% for book in books %}
{{ custom_function(book.isbn) }}
{% endfor %}
""")
from collections import namedtuple
Book = namedtuple('Book', ['name', 'isbn'])
books = [
Book('a', 1),
Book('b', 2),
Book('c', 3)
]
def custom_function(isbn):
return isbn + 1
print(template.render(books=books, custom_function=custom_function))

Assign multiple variables in a with statement after returning multiple values from a templatetag

Is there a way to assign multiple variables in a with statement in a django template. I'd like to assign multiple variables in a with statement after returning multiple values from a templatetag
My use case is this:
{% with a,b,c=object|get_abc %}
{{a}}
{{b}}
{{c}}
{% endwith %}
I don't think it's possible without a custom templatetag.
However if your method returns always the same length you can do it more compact like this:
{% with a=var.0 b=var.1 c=var.2 %}
...
{% endwith %}
I'm not sure that this as allowed, however from docs multiple assign is allowed.
But you can assign these 3 variables to 1 variable, which will make it tuple object, which you can easily iterate by its index.
{% with var=object|get_abc %}
{{ var.0 }}
{{ var.1 }}
{{ var.2 }}
{% endwith %}
Its not supported and its not a flaw of Django Template Language that it doesn't do that, its Philosophy as stated in the docs:
Django template system is not simply Python embedded into HTML. This
is by design: the template system is meant to express presentation,
not program logic
What you could do is prepare your data on Python side and return appropriate format which will be easy to access in template, so you could return a dictionary instead and use dotted notation with key name:
{# assuming get_abc returns a dict #}
{% with var=object|get_abc %}
{{ var.key_a }}
{{ var.key_b }}
{{ var.key_c }}
{% endwith %}

List comprehensions in Jinja

I have two lists:
strainInfo, which contains a dictionary element called 'replicateID'
selectedStrainInfo, which contains a dictionary element called 'replicateID'
I'm looking to check if the replicateID of each of my strains is in a list of selected strains, in python it would be something like this:
for strain in strainInfo:
if strain.replicateID in [selectedStrain.replicateID for selectedStrain in selectedStrainInfo]
print('This strain is selected')
I'm getting the correct functionality in django, but I'm wondering if there's a way to simplify using a list comprehension:
{% for row in strainInfo %}
{% for selectedStrain in selectedStrainsInfo %}
{% if row.replicateID == selectedStrain.replicateID %}
checked
{% endif %}
{% endfor %}
{% endfor %}
List comprehensions are not supported in Jinja
You could pass the data, via Jinja, to JavaScript variables like so
var strainInfo = {{strainInfo|safe}};
var selectedStrainInfo = {{selectedStrainInfo|safe}};
and then do your clean up there.
Use Jinja's safe filter to prevent your data from being HTML-escaped.
Since v2.7 there is selectattr:
Filters a sequence of objects by applying a test to the specified attribute of each object, and only selecting the objects with the test succeeding.
If no test is specified, the attribute’s value will be evaluated as a boolean.
Example usage:
{{ users|selectattr("is_active") }}
{{ users|selectattr("email", "none") }}
Similar to a generator comprehension such as:
(u for user in users if user.is_active)
(u for user in users if test_none(user.email))
See docs.

How to get values from dictionary in jinja when key is a variable?

I'm trying to retrieve entries from a python dictionary in jinja2, but the problem is I don't know what key I want to access ahead of time - the key is stored in a variable called s.course. So my problem is I need to double-substitute this variable. I don't want to use a for loop because that will go through the dictionary way more than is necessary. Here's a workaround that I created, but it's possible that the s.course values could change so obviously hard-coding them like this is bad. I want it to work essentially like this:
{% if s.course == "p11" %}
{{course_codes.p11}}
{% elif s.course == "m12a" %}
{{course_codes.m12a}}
{% elif s.course == "m12b" %}
{{course_codes.m12b}}
{% endif %}
But I want it to look like this:
{{course_codes.{{s.course}}}}
Thanks!
You can use course_codes.get(s.course):
>>> import jinja2
>>> env = jinja2.Environment()
>>> t = env.from_string('{{ codes.get(mycode) }}')
>>> t.generate(codes={'a': '123'}, mycode='a').next()
u'123'
There is no need to use the dot notation at all, you can do:
"{{course_codes[s.course]}}"
I'm using Jinja with Salt, and I've found that something like the following works well:
{% for role in pillar.packages %}
{% for package in pillar['packages'][role] %}
install_{{ package }}:
pkg.installed:
- name: {{ package }}
{% endfor %}
{% endfor %}
That is, use the more verbose [ ] syntax and leave the quotes out when you need to use a variable.

In Jinja2 whats the easiest way to set all the keys to be the values of a dictionary?

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...
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.
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) }}

Categories

Resources