Detect if a forloop has zero loops - sourcery

I'm trying to handle a case where a forloop with a where clause results in zero loops.
I've tried using set and map in various ways unsuccessfully, possibly one of those is the solution but I just couldn't get it right.
{% for variable in type.allVariables where variable.type.implements["SomeProtocol"]["name"] == "SomeProtocol" %}
// Add code for each variable
{% endfor %}
// Add backup code if forloop didn't do a single loop

Found the answer in the stencil documentation of all places - who knew!
The for tag can take an optional {% empty %} block that will be displayed if the given list is empty or could not be found.
{% for user in users %}
<li>{{ user }}</li>
{% empty %}
<li>There are no users.</li>
{% endfor %}

Related

Django for...empty with different container when empty

I have a common pattern in which I have a container in HTML (e.g., ul, table ...) with elements from a queryset. However, when the list is empty, I don't want to display the container itself.
I seek to get the equivalent of
{% if query.exists %}
<ul class="list-of-stuff">
{% for x in query %}
<li>{{ x }}</li>
{% endfor %}
</ul>
{% else %}
<p class="information">
The list is empty.
</p>
{% endif %}
but without extra query (query.exists), much like for...empty does. However, one large drawback of for...empty is that I cannot remove or change the container when the list is empty.
How can I get an equivalent of the above with only one execution of my query in a clean way (i.e., without displaying the container tag if forloop.first and such)? I'm open for implementing new tags or filters or using a lightweight library with these.
You can inspect the truthiness of the query itself, this will load the data into memory, so then it is only a single query. We thus check {% if query %}, not {% if query.exists %}:
{% if query %}
<ul class="list-of-stuff">
{% for x in query %}
<li>{{ x }}</li>
{% endfor %}
</ul>
{% else %}
<p class="information">
The list is empty.
</p>
{% endif %}
Indeed, a QuerySet has truthiness True if it contains at least one element. It will thus make the query, and load the data into memory. If there are no records, then the {% if query %} fails, and thus we render that the list is empty. In case there is at least one row, then {% if query %} succeeds, and then we do not have to make an extra query to enumerate over the query.

Put items on top in django template for loop

The model has a field named "is_highlighted", i want to put all items on top if is_highlighted == True when iterate thru the object list.
You can give 2 for loops to do this. In first for loop check the condition true.
{% for x in list %}
{% if x.is_highlighted == True %}
....
..
{% endif %}
{% endfor %}
display others in second for loop
{% for x in list %}
{% if x.is_highlighted != True %}
...
...
You can also pass 2 query sets through context. I don't know if it's best or any other way. simply this will work.

empty tag {% empty %} in django template doesn't work when "if" condition is nested inside for loop

<ul>
{% for entry in entries %}
{% if title in entry %}
<li>{{ entry }}</li>
{% endif %}
{% empty %}
<li>Sorry! No result matches your search.</li>
{% endfor %}
</ul>
This is my html template.
Want to create a list where the list items have to meet certain condition, so I nested if condition inside for loop. The list works, but empty tag doesn't.
When the list is empty, it doesn't show anything.
How can I make it work?
You seem to have misunderstood the {% empty %} tag. It only works if entries is empty or not found. It doesn't matter if any HTML is generated inside the for loop, so it is independent of your if clause.
What you can do is to filter entries in your view code to only return entries with title in them.

Django template iteration / loop count based on condition

I'm trying to create an iteration like the a $i==0; $i++; from PHP in Django, based on a condition.
{% for item in event.products %}
{% if item.category = "Treat" %}
Now - I want to be able to tell how many times has this condition been met (category = treat) and how to stop the for loop after 2 items that match that loop.
Thanks!
django template system does not permit breaks in for loops nor setting counters, even if elsewhere it is shown how to overcome this limitation in some cases or how to create new template tags that could help you, maybe you can calculate beforehand your requirements and prepare the list to be printed by slicing it in your view.
I agree with #DRC that this business logic is best done in your view code and not in the template.
If you still need a template solution:
{% regroup event.products by item.category as grouped_products %}
{% for group in grouped_products %}
{% if group.grouper == "Treat" %}
{% for item in group.list|slice:":2" %}
{{ item.imageURL }}
{% endfor %}
{% endif %}
{% endfor %}
Documentation for slice and regroup.

Alternate Row Coloring in Django Template with More Than One Set of Rows

Django templates offer the builtin tag cycle for alternating between several values at different points in a template (or for loop in a template) but this tag does not reset when it is accessed in a scope outside of the cycles definition. I.e., if you have two or more lists in your template, the rows of all of which you'd like to use some css definitions odd and even, the first row of a list will pick up where the last left off, not with a fresh iteration from the choices (odd and even)
E.g., in the following code, if the first blog has an odd number of entries, then the first entry in a second blog will start as even, when I want it to start at odd.
{% for blog in blogs %}
{% for entry in blog.entries %}
<div class="{% cycle 'odd' 'even' %}" id="{{entry.id}}">
{{entry.text}}
</div>
{% endfor %}
{% endfor %}
I've tried obviating this by patching with the resetcycle tag offered here:
Django ticket: Cycle tag should reset after it steps out of scope
to no avail. (The code didn't work for me.)
I've also tried moving my inner loop into a custom tag, but this also did not work, perhaps because the compile/render cycle moves the loop back into the outer loop? (Regardless of why, it didn't work for me.)
How can I accomplish this simple task!? I'd prefer not to create a data structure in my view with this information pre-compiled; that seems unnecessary. Thanks in advance.
The easiest workaround (until the resetcycle patch gets fixed up and applied) is to use the built-in "divisibleby" filter with forloop.counter:
{% for entry in blog.entries %}
<div class="{% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}" id="{{ entry.id }}">
{{ entry.text }}
</div>
{% endfor %}
A little more verbose, but not hard to understand and it works great.
https://docs.djangoproject.com/en/1.8/ref/templates/builtins/#cycle
{% for o in some_list %}
<tr class="{% cycle 'row1' 'row2' %}">
...
</tr>
{% endfor %}
Give up and use Jinja2 Template System
I gave up on django template language, it's very restricted in what you can do with it.
Jinja2 uses the same syntax that the django template uses, but adds many enhancements over it.
EDIT/NOTE ( I know it sounds like a big switch for just a minor issue, but in reality I bet you always find yourself fighting the default template system in django, so it really is worthwhile and I believe it will make you more productive in the long run. )
You can read this article written by its author, although it's technical, he mentions the problem of the {% cycle %} tag in django.
Jinja doesn't have a cycle tag, it has a cycle method on the loop:
{% for user in users %}
<li class="{{ loop.cycle('odd', 'even') }}">{{ user }}</li>
{% endfor %}
A major advantage of Jinja2 is that it allows you to use logic for the presentation, so if you have a list of pictures, you can put them in a table, because you can start a new row inside a table every N elements, see, you can do for example:
{% if loop.index is divisibleby(5) %}
</tr>
{% if not loop.last %}
<tr>
{% endif %}
{% endif %}
you can also use mathematical expressions:
{% if x > 10 %}
and you can access your python functions directly (but some setup is required to specify which functions should be exposed for the template)
{% for item in normal_python_function_that_returns_a_query_or_a_list() %}
even set variables ..
{% set variable_name = function_that_returns_an_object_or_something() %}
You can use tagged cycle and resetcycle (new in Django 1.11) calls (from https://docs.djangoproject.com/en/1.11/ref/templates/builtins/#std:templatetag-resetcycle ):
{% for blog in blogs %}
{% cycle 'odd' 'even' as rowcolors silent %}
{% resetcycle rowcolors %}
{% for entry in blog.entries %}
{% cycle rowcolors %}
<div class="{{ rowcolors }}" id="{{entry.id}}">
{{ entry.text }}
</div>
{% endfor %}
{% endfor %}
I end up doing so, with the forloop.counter0 - It works great!
{% for product in products %}
{% if forloop.counter0|divisibleby:4 %}<div class="clear"></div>{% endif %}
<div class="product {% if forloop.counter0|divisibleby:4 %}col{% else %}col20{% endif %}">
Lorem Ipsum is simply dummy text
</div>
{% endfor %}
The easiest answer might be: "give up and use jQuery." If that's acceptable it's probably easier than fighting with Django's templates over something so simple.
There's a way to do it server-side with an iterator that doesn't keep a simultaneous copy of all the entries:
import itertools
return render_to_response('template.html',
{
"flattened_entries": itertools.chain(*(blog.entries for blog in blogs)),
})