How to dynamically add items to jinja variable in Flask? - python

I'm building a website using Flask with its jinja2 templating engine and I'm dynamically building the menu (as described here):
{%
set navigation_bar = [
('/', 'index', 'Home'),
('/aboutus/', 'aboutus', 'About Us'),
('/faq/', 'faq', 'FAQ')
]
%}
{% set active_page = active_page|default('index') -%}
<ul>
{% for href, id, title in navigation_bar %}
<li{% if id == active_page %} class="active"{% endif %}>
{{ title|e }}
</li>
{% endfor %}
</ul>
Now if a user is logged in I want to show some additional things. So at runtime I want to add items to the navigation_bar variable. I tried something like this:
{% if g.user.is_authenticated() %}
{% navigation_bar.append(('/someotherpage', 'someotherpage', 'SomeOtherPage')) -%}
{% endif %}
But unfortunately this results in the following error: TemplateSyntaxError: Encountered unknown tag 'navigation_bar'. Jinja was looking for the following tags: 'endblock'. The innermost block that needs to be closed is 'block'.
So: does anybody know how I can add additional items to a jinja2 variable at runtime? All tips are welcome!
[bonus question]
I was also wondering, what does the - do at the end of {% set active_page = active_page|default('index') -%}?

The error occurs because Jinja can't identify block. Each Jinja block should start from block name. do block from do extension meets your needs. To use it you should add
do extension to jinja extensions. You can do this like so:
app.jinja_env.add_extension('jinja2.ext.do')
And then you can use do extension. Your example should looks like this:
{% if g.user.is_authenticated() %}
{% do navigation_bar.append(('/someotherpage', 'someotherpage', 'SomeOtherPage')) %}
{% endif %}
Here's another simple example.
You will find answer to your bonus question here. In short - removes whitespaces from start or end of block (this depends on where it is located).

To complete the answer of Slava Bacherikov, if you don't have the Jinja "do extension", you can use the tag set:
{% if g.user.is_authenticated() %}
{# use a dummy variable name, we juste need the side-effect of method call #}
{% set _z = navigation_bar.append(('/someotherpage', 'someotherpage', 'SomeOtherPage')) %}
{% endif %}

Related

Why does Django template block/include include external <table> tag?

Consider the below code:
<table id='table1'>
{% if model_name == 'TransactionsTable' %}
{% block transactions_create %}
{% include "snippets/transactions_create_and_update.html" with form=form %}
{% endblock transactions_create %}
{% endif %}
</table>
I have another <table>, 'table2', within transactions_create_and_update.html.
<table id=table2>abc</table>
Table2 was appearing outside of it's parent element. See image below - table2 should be inside of table1
So after some testing I tried closing table1 early (first line):
<table id='table1'></table>
{% if model_name == 'TransactionsTable' %}
{% block transactions_create %}
{% include "snippets/transactions_create_and_update.html" with form=form %}
{% endblock transactions_create %}
{% endif %}
Then it looks like this
I don't really understand what is going on in either scenario.
From the docs - think this is part of the answer but I don't know what to do with it:
The include tag should be considered as an implementation of “render
this subtemplate and include the HTML”, not as “parse this subtemplate
and include its contents as if it were part of the parent”. This means
that there is no shared state between included templates – each
include is a completely independent rendering process.
Blocks are evaluated before they are included. This means that a
template that includes blocks from another will contain blocks that
have already been evaluated and rendered - not blocks that can be
overridden by, for example, an extending template.
I had too many 'includes'. It turned out that one-level deeper I had a element that wasn't closed properly.

is there a way to set name of a block using variables in django templates?

I am making some generic templates for my projects like the message template given below.
{% extends base_name %}
{% block main-contents %}
<h2>{{ message_heading }}</h2>
<div class="alert alert-{{ box_color|default:"info" }}">
{{ message }}
{% if btn_1_text and btn_1_url %}
{{ btn_1_text }}
{% endif %}
{% if btn_2_text and btn_2_url %}
{{ btn_2_text }}
{% endif %}
</div>
{% endblock %}
I can set the name of the base template through template variables. My question is whether there is a method to set the name of the block using template variables. Usually I use the block name main-contents for almost all of my project. But that is not granted for all the projects. If this is not possible using template is there a way to rename the block using python code ?
I found a hack. I dont know if this has any after effects. Can any one verify this ?
def change_block_names(template, change_dict):
"""
This function will rename the blocks in the template from the
dictionary. The keys in th change dict will be replaced with
the corresponding values. This will rename the blocks in the
extended templates only.
"""
extend_nodes = template.nodelist.get_nodes_by_type(ExtendsNode)
if len(extend_nodes) == 0:
return
extend_node = extend_nodes[0]
blocks = extend_node.blocks
for name, new_name in change_dict.items():
if blocks.has_key(name):
block_node = blocks[name]
block_node.name = new_name
blocks[new_name] = block_node
del blocks[name]
tmpl_name = 'django-helpers/twitter-bootstrap/message.html'
tmpl1 = loader.get_template(tmpl_name)
change_block_names(tmpl1, {'main-contents': 'new-main-contents})
This seems to work for now. I want to know if this method has any after effects or other issues.

flask and wtf - how can I direct tag attributes for fields?

After fiddling with wtforms, fields use widgets to actually render them to html. I wrote some custom field/widget to draw html in a way that I'd more like to. But here's a question:
suppose I want to render them with pre-defined css class or give actual details myself.
How can I achieve this? and on what phase of handling requests(at Form class declaration? or when setting attributes to give Form some Fields? or when I'm actually calling them in jinja2 templates) I should do that?
I use a Jinja macro something like this:
{% macro field_with_errors(field) %}
{% set css_class=kwargs.pop('class', '') %}
{% if field.type in ('DateField', 'DateTimeField') %}
{{ field(class='date ' + css_class, **kwargs) }}
{% elif field.type == 'IntegerField' %}
{{ field(class='number ' + css_class, **kwargs) }}
{% else %}
{{ field(class=css_class, **kwargs) }}
{% endif %}
{% if field.errors %}
<ul class="errors">{% for error in field.errors %}<li>{{ error|e }}</li>{% endfor %}</ul>
{% endif %}
{% endmacro %}
usage is something like:
{{ field_with_errors(form.foo, placeholder='bar') }}
This lets me avoid boilerplate, but also lets me keep the display decisions in the template space.
Have a look at the rendering fields section.
Alternatively, you can add attributes to be rendered in the Jinja2 (etc.) template:
<div class="input-prepend">
{{ form.address(placeholder="example.com", id="address", autofocus="autofocus", required="required") }}
</div>
There's nothing to prevent you from using a variable for the ID value above, instead of address, then rendering the template with a keyword argument to populate it.

Checking if something exists in items of list variable in Django template

I have a list of sections that I pass to a Django template. The sections have different types. I want to say "if there is a section of this type, display this line" in my template, but having an issue. What I'm basically trying to do is this.
{% if s.name == "Social" for s in sections %}
Hello Social!
{% endif %}
But of course that's not working. Any idea how to basically in one line loop through the items in a list and do an if statement?
ADDITIONAL INFO: I could potentially have multiple "Social" sections. What I'm trying to do in the template is say "if there are any social sections, display this div. If not, don't display the div." But I dont want the div to repeat, which is what would happen with the above code.
Ideally what you would do is create a list that the template gets as such:
l = [s.name for s in sections]
And in the template, use:
{% if 'Social' in l %}
You're trying to put more logic into a template than they are meant to have. Templates should use as little logic as possible, while the logic should be in the code that fills the template.
You can't use list comprehensions in templates:
{% for s in sections %}
{% if s.name == 'Social' %}
Hello Social!
{% endif %} {# closing if body #}
{% endfor %} {# closing for body #}
{% if sections.0.name == "Social" %}
Hello Social!
{% endif %}

How do I access a python list from a django templatetag?

I have created a templatetag that loads a yaml document into a python list. In my template I have {% get_content_set %}, this dumps the raw list data. What I want to be able to do is something like
{% for items in get_content_list %}
<h2>{{items.title}}</h2>
{% endfor %}`
If the list is in a python variable X, then add it to the template context context['X'] = X and then you can do
{% for items in X %}
{{ items.title }}
{% endfor %}
A template tag is designed to render output, so won't provide an iterable list for you to use. But you don't need that as the normal context + for loop are fine.
Since writing complex templatetags is not an easy task (well documented though) i would take {% with %} tag source and adapt it for my needs, so it looks like
{% get_content_list as content %
{% for items in content %}
<h2>{{items.title}}</h2>
{% endfor %}`

Categories