sort in jinja2 when attribute is not available - python

I have a set of articles (using pelican for generating static sites) which includes a category called hotels. I'd like to sort these hotels. The problem is that only the hotels have an attribute called 'city' while the other articles do not and this obviously leads to the following error:
Caught exception "'pelican.contents.Article object' has no attribute 'city'".
Here is the code I am using:
{% for article in articles|sort(attribute='city') %}
{% if article.category == 'hotels' %}
<a href="hotels/{{ article.slug }}.html">
<p>{{ article.title }}</p>
</a>
{% endif %}
{% endfor %}
Is there a way to check to see if the attribute exists and provide some default value so that it does not cause an error?

You may be able to move your if statement into your for loop as a filter:
for article in articles if article.category == 'hotels' | sort(attribute='city')

If you want to show only entries that have a 'city' attribute, and have that list sorted by 'city', do:
for article in articles|selectattr("city")|sort(attribute="city")

If you want to iterate over only the hotels, see Sean Vieira's answer. If you want to iterate over all articles, but have the hotels sorted while the rest are in arbitrary order, you can do it by using macros:
{% macro my_macro(article) %}
...
{% endmacro %}
{% for a in articles if a.category == 'hotels' | sort(attribute='city') %}
{{ my_macro(a) }}
{% endfor %}
{% for a in articles if a.category != 'hotels' %}
{{ my_macro(a) }}
{% endfor %}
This will include everything you defined in my_macro first for each hotel, in the desired order, then for each article that is not a hotel.

I found this page when was looking for a similar solution.
Eventually, I solved it a bit differently and it might be helpful for someone else.
In one of my templates for Pelican I added statistics collected by 'post_stats' plugin about approximate time to read. It looked like
~{{ article.stats['read_mins']|default("0") }} min read
But if the plugin is not loaded then the 'article' object doesn't have the 'stats' attribute and rendering fails.
Jinja has the builtin test for testing if a variable is defined.
So, I came up with this solution
~{{ article.stats['read_mins'] if article.stats is defined else "0" }} min read

Related

What {% %} and {{ }} mean in HTML?

I have recently started to learn Django, so I came across with some HTML templates, but those are pretty unfamiliar for me, they mostly consist of {% and {{
For example:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
Vote again?
What are they? Implementations from other languages or HTML syntax? I'd be happy to get some docs, websites or examples too.
The {% _________ %} are template tags. They are used to interpolate a tag into the space. Examples include extend, include, and load to name but a few. They often extend, insert, or give some sort logical functionality in some way (if conditions or loops, etc).
The {{ ___________ }} syntax is for template variables. This is used to interpolate a variable declared either as one of the built-in options or your own which you have created from any number of methods (models, view context, etc).
The Django docs are pretty thorough with use cases of all these. Another website which shortlists these to make them readily understandable is https://www.djangotemplatetagsandfilters.com/
It doesn't mean anything in html itself, it means something in Django template language. For example:
{{ choice.choice_text }}
will substitute value of that variable during template rendering.
The other one {% and %} is used for template processing, for example to indicate to template processor that some task needs to be completed. Good example is:
{% if error %}
{{ error }}
{% endif %}
That means that the variable error will be displayed (rendered) only if it exists, or to be more precise if it has some value.
The ones you are referring are jinja codes. They are very essential to work with Django and also they easy to understand when you have general understanding about python and django. You can find the documentation about jinga in this URL Jinja2 Documentation

Could not parse the remainder: '{{ list[loop.index0] }}'

I followed instructions in simmilar thread like: How do you index on a jinja template?
but my html template is not working and whole django project is not responding due to this.
Error that I'm getting:
Error during template rendering.
Could not parse the remainder: '[loop.index0]' from 'songs_titles[loop.index0]'
My code looks like this:
{% if converted_files_urls %}
<p>Titles: {{ songs_titles }}</p>
{% for n in converted_files_urls %}
<a href="{{ n }}" download>Download: {{ songs_titles[loop.index0] }}</a>
<br/>
{% endfor %}
{% endif %}
and the {{ songs_titles }} renders as list, so at least till here it works ok.
What am I doing wrong?
Actually you are looking for Jinja, that will not work on django.
In django template tag you should use forloop.counter0 and list indexing looks like
{{songs_titles.1}}
Need to set count in variable and then use it, for setting variable you could use -
{% with index=forloop.counter0 %}
{{ songs_titles.index}}
{% endwith %}
Still If you have any doubts you can comment it.
I finally resolved this by creating a custom template tag like here:
https://djangosnippets.org/snippets/2740/
But to be honest it sucks that that's the simplest working solution for now :/

Trying to understand Django's communication between view and template

(edited)
I come from a web2py background and find Django a much more complicated framework to learn and use than web2py.
After the first answer, I have adapted the description of my problem.
In my view I have:
def team(request):
hr = dict(name="Some Name", photo="/static/images/HR.jpg", url="http://some.website.com/?page_id=3602")
js = dict(name="Some Name2", photo="/static/images/JS.jpg", url="http://some.website.com/?page_id=3608")
context = {team:[hr,js]}
return render(request, "wos_2017_2/team.html", context)
In the template I have
<ul>
{% for person in context.team %}
<li> {{ person.name }} {{ person.photo }} {{ person.url }} </li>
{% endfor %}
</ul>
There is absolutely no output.
This works in normal python:
hr = dict(name="Some Name", photo="/static/images/HR.jpg", url="http://some.website.com/?page_id=3602")
js = dict(name="Some Name2", photo="/static/images/JS.jpg", url="http://some.website.com/?page_id=3608")
context = dict(team = [hr,js])
for i in context['team']:
print(i['name'], i['photo'], i['url'])
With output
Some Name /static/images/HR.jpg http://some.website.com/?page_id=3602
Some Name2 /static/images/JS.jpg http://some.website.com/?page_id=3608
Why am I not getting any result in Django?
Your first example is correct. Sadly you have a small typo in your first line of code:
hr = dict(name="Some Name, ...),
The line ends with a comma ,. Now hr is becomes a tuple with a single element: the dict. Without the comma this works:
{{ team.0.name }}
{{ team.1.name }}
With your updated answer you need to change context.team to team in your template:
{% for person in team %}
The context dictionary is 'unpacked' in the template.
I was not able to comment so I had to post an answer.
Only immutable data types can be used as keys, i.e. no lists or dictionaries can be used If you use a mutable data type as a key, you get an error message.Tuple as keys are okay.
With what i can tell is the problem is in your view code:
This
context = {team:[hr,js]}
should be this:
context = {"team":[hr,js]}
or
context = dict(team=[hr,js])
<ul>
{% for person in team %}
<li> {{ person.name }} {{ person.photo }} {{ person.url }} </li>
{% endfor %}
</ul>
Was the correct way to read the dictionary in the template.

How to dynamically add items to jinja variable in Flask?

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 %}

Django dictionary in templates: Grab key from another objects attribute

I have a dictionary called number_devices I'm passing to a template, the dictionary keys are the ids of a list of objects I'm also passing to the template (called implementations). I'm iterating over the list of objects and then trying to use the object.id to get a value out of the dict like so:
{% for implementation in implementations %}
{{ number_devices.implementation.id }}
{% endfor %}
Unfortunately number_devices.implementation is evaluated first, then the result.id is evaluated obviously returning and displaying nothing. I can't use parentheses like:
{{ number_devices.(implementation.id) }}
because I get a parse error. How do I get around this annoyance in Django templates?
Thanks for any help!
A workaround could be using the keys from number_devices and check in the for loop if it is equal to the key provided by number_devices.
{% for key in number_devices.keys %}
{% for implementation in implementations %}
{% ifequal key implementation.id %} you got it {% endifequal %}
{% endfor %}
{% endfor %}
Seems a bit ugly, but should work.

Categories