django template forloop.counter multiple operations - django

I am trying to perform multiple mathematics operations on a forloop.counter in my Django template. Specifically I am trying to identify every 29th instance, after the 24th; so the 24th, 53rd, 82nd, 111th, 140th instances etc....
I have tried the following without success:
{% if forloop.counter == 24 or widthratio forloop.counter|add:"-24" 29 1 %}
{% if forloop.counter == 24 or forloop.counter|add:"-24"|divisibleby:29 %}
Does anyone have any suggestions that might help me acheive my aim? Any help is much appreciated!

You can write it like:
{% for element in collection %}
{% if forloop.counter|add:"-24"|divisibleby:"29" %}
...
{% endif %}
{% endfor %}
given the offset (here 24) is smaller than the period (here 29), this will work, otherwise we need an extra condition (for example with 31 and 29):
{% for element in collection %}
{% if forloop.counter >= 31 forloop.counter|add:"-31"|divisibleby:"29" %}
...
{% endif %}
{% endfor %}
For example:
>>> tp = '{% for element in collection %}{% if forloop.counter|add:"-24"|divisibleby:"29"%}A{% else %}B{% endif %}{% endfor %}'
>>> Template(tp).render(Context({'collection': range(100)}))
'BBBBBBBBBBBBBBBBBBBBBBBABBBBBBBBBBBBBBBBBBBBBBBBBBBBABBBBBBBBBBBBBBBBBBBBBBBBBBBBABBBBBBBBBBBBBBBBBB'
as you can see, for the iterations where the condition holds, the A is printed.
But in case you need to write complicated logic, in general you should reconsider the design, and look if you can not "move logic" to the view instead.

Another interesting option, besides Willem's, is to first write a modulo filter:
#register.filter
def modulo(num, val):
return num % val
And then in your template use the filter:
{% if forloop.counter|modulo:29 0 %}
However in general you can and should do such operations in your view, not your template. It's not clear why you're going about doing it this way, so you may have your reasons, but try to avoid it if you can.

Related

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.

Check if object is in manytomany list in template

How can I check if a certain object/id is in a list?
I want something to be displayed if the ID of the connected object is not "6".
Tried with something like this:
{% if user.benefits.all != "6" %}
You do not have a benefit with ID 6.
{% endif %}
It is better to not put much logic into templates. View (or model) - is a better place for that.
For example in view you can check, that user.benefits has element with id=6 by this code:
has_benefit = user.benefits.filter(id=6).count() > 0
context['has_benefit'] = has_benefit
Now in template just use this new context variable:
{% if not has_benefit %}
You do not have a benefit with ID 6.
{% endif %}
UPDATED:
If you still want to do it in template, it is better to create a custom template filter:
from django import template
register = template.Library()
#register.filter(name='has_benefit')
def has_benefit(user, benefit_id):
b_id = int(benefit_id)
return user.benefits.filter(id=b_id).count() > 0
Now in template load your templatetags module using {% load module_name %} and use:
{% if not user|has_benefit:"6" %}
You do not have a benefit with ID 6.
{% endif %}
{% for benefit in user.benefits.all %}
{% if benefit.id != 6 %}
You do not have a benefit with id 6
{% endif %}
{% endfor %}
But this will loop through all the benefits and print it every time the condition passes.
So, you should write a template tag which returns you a list of ids for all benefits for a particular user and once you have that list you can do:
{% if 6 not in list_of_benefit_ids %}
You do not have a benefit with id 6
{% endif %}

Django template tags: set .active on the first element of a forloop that satisfies a condition [duplicate]

This question already has answers here:
ForLoop Iterations as List Index in Django Template
(3 answers)
Closed 8 years ago.
I have the following problem: A user is taking a course on my website. A course consists of several chapters and each chapter consists of several steps. If the user is logged in and completes a step, the backend saves the progress. When the user comes back to the site, I want him to continue where he left off. To do this I have to add the css class "active" to the corresponding navigation step and the content tab of each chapter. My starting point was to test with an if statement if the user had already finished the step. That worked great, but adds .active to all the steps that have not been finished yet. I only want to add it to the first step that hasn't been finished yet. I have actually solved this problem but it is a rather brute hack and I had to include a custom template tag that allows me to set a variable in the template:
{% load custom_tags %}
{% for chapter in chapters %}
<div>
<ul class="unstyled wizard clearfix">
{% set active_wizard_steps = 0 %}
{% for step in chapter.steps.all %}
<li class="{% if not step.read and active_wizard_steps == 0 %}active{% set active_wizard_steps = 1 %}{% endif %}{% if step.read %} finished{% endif %}"></li>
{% endfor %}
</ul>
</div>
{% endfor %}
I know that this can also be done by passing a list or dict to the template with all the active steps and then testing in the template if a step is in that list. But that would have meant rewriting a lot of view code so I decided to do it completely in the template.
So my question is: what would be the best practice way to solve this problem? I'm asking just out of curiousity and because the problem was actually really fun to figure out. Thanks a lot!
Add a method to Step:
class Step(models.Model):
# ...
def is_active(self):
# do your sauce
And in the template:
class="{% if step.is_active %}active{% endif %}"
We already have convenient helpers like get_absolute_url in models, why not add this one ?
If you don't like it, you could do a template filter and use it as such:
class="{% if step|is_active %}active{% endif %}"
Update
Here's another method, make a template filter as such:
#register.filter
def set_active_steps(steps):
active_wizard_steps = 0
for step in steps:
if not step.read and active_wizard_steps == 0:
step.is_active = True
return steps
Then, in your template:
{% for step in chapter.steps.all|set_active_steps %}
... {% if step.is_active %}...{% endif %}
{% endfor %}
I know you already solved your issue, I'm posting another answer for the sake of completion.
If you need inside a {% for %}{% endfor %} to set a class by matching the current wizard step against the current item in the loop, you can use the following code:
{% for step in wizard.steps.all %}
<li class="{% if wizard.steps.step1 == forloop.counter %}active{% elif wizard.steps.step1 > forloop.counter %} complete{% endif %}">
<span class="step">{{ forloop.counter }}</span>
</li>
{% endfor %}

Numeric for loop in Django templates

How do I write a numeric for loop in a Django template? I mean something like
for i = 1 to n
I've used a simple technique that works nicely for small cases with no special tags and no additional context. Sometimes this comes in handy
{% for i in '0123456789'|make_list %}
{{ forloop.counter }}
{% endfor %}
{% with ''|center:n as range %}
{% for _ in range %}
{{ forloop.counter }}
{% endfor %}
{% endwith %}
Unfortunately, that's not supported in the Django template language. There are a couple of suggestions, but they seem a little complex. I would just put a variable in the context:
...
render_to_response('foo.html', {..., 'range': range(10), ...}, ...)
...
and in the template:
{% for i in range %}
...
{% endfor %}
My take on this issue, i think is the most pythonic. Create a my_filters.py in your apps templatetags directory.
#register.filter(name='times')
def times(number):
return range(number)
Usage in your template:
{% load my_filters %}
{% for i in 15|times %}
<li>Item</li>
{% endfor %}
You can pass a binding of
{'n' : range(n) }
to the template, then do
{% for i in n %}
...
{% endfor %}
Note that you'll get 0-based behavior (0, 1, ... n-1).
(Updated for Python3 compatibility)
Maybe like this?
{% for i in "x"|rjust:"100" %}
...
{% endfor %}
I'm just taking the popular answer a bit further and making it more robust. This lets you specify any start point, so 0 or 1 for example. It also uses python's range feature where the end is one less so it can be used directly with list lengths for example.
#register.filter(name='range')
def filter_range(start, end):
return range(start, end)
Then in your template just include the above template tag file and use the following:
{% load myapp_filters %}
{% for c in 1|range:6 %}
{{ c }}
{% endfor %}
Now you can do 1-6 instead of just 0-6 or hard coding it. Adding a step would require a template tag, this should cover more uses cases so it's a step forward.
You can pass :
{ 'n' : range(n) }
To use template :
{% for i in n %}
...
{% endfor %}
I tried very hard on this question, and I find the best answer here:
(from how to loop 7 times in the django templates)
You can even access the idx!
views.py:
context['loop_times'] = range(1, 8)
html:
{% for i in loop_times %}
<option value={{ i }}>{{ i }}</option>
{% endfor %}
You don't pass n itself, but rather range(n) [the list of integers from 0 to n-1 included], from your view to your template, and in the latter you do {% for i in therange %} (if you absolutely insist on 1-based rather than the normal 0-based index you can use forloop.counter in the loop's body;-).
Just incase anyone else comes across this question… I've created a template tag which lets you create a range(...): http://www.djangosnippets.org/snippets/1926/
Accepts the same arguments as the 'range' builtin and creates a list containing
the result of 'range'.
Syntax:
{% mkrange [start,] stop[, step] as context_name %}
For example:
{% mkrange 5 10 2 as some_range %}
{% for i in some_range %}
{{ i }}: Something I want to repeat\n
{% endfor %}
Produces:
5: Something I want to repeat
7: Something I want to repeat
9: Something I want to repeat
You should use "slice" in template, a example like this:
in views.py
contexts = {
'ALL_STORES': Store.objects.all(),
}
return render_to_response('store_list.html', contexts, RequestContext(request, processors=[custom_processor]))
in store_list.html:
<ul>
{% for store in ALL_STORES|slice:":10" %}
<li class="store_item">{{ store.name }}</li>
{% endfor %}
</ul>
This method supports all the functionality of the standard range([start,] stop[, step]) function
<app>/templatetags/range.py
from django import template
register = template.Library()
#register.filter(name='range')
def _range(_min, args=None):
_max, _step = None, None
if args:
if not isinstance(args, int):
_max, _step = map(int, args.split(','))
else:
_max = args
args = filter(None, (_min, _max, _step))
return range(*args)
Usage:
{% load range %}
<p>stop 5
{% for value in 5|range %}
{{ value }}
{% endfor %}
</p>
<p>start 5 stop 10
{% for value in 5|range:10 %}
{{ value }}
{% endfor %}
</p>
<p>start 5 stop 10 step 2
{% for value in 5|range:"10,2" %}
{{ value }}
{% endfor %}
</p>
Output
<p>stop 5
0 1 2 3 4
</p>
<p>start 5 stop 10
5 6 7 8 9
</p>
<p>start 5 stop 10 step 2
5 7 9
</p>
This essentially requires a range function. A Django feature ticket was raised (https://code.djangoproject.com/ticket/13088) for this but closed as "won't fix" with the following comment.
My impression of this idea is that it is trying to lead to programming in the template. If you have a list of options that need to be rendered, they should be computed in the view, not in the template. If that's as simple as a range of values, then so be it.
They have a good point - Templates are supposed to be very simple representations of the view. You should create the limited required data in the view and pass to the template in the context.
{% for _ in ''|center:13 %}
{{ forloop.counter }}
{% endfor %}
If the number is coming from a model, I found this to be a nice patch to the model:
def iterableQuantity(self):
return range(self.quantity)
You can use:
{% with ''|center: i as range %}
For those who are looking to simple answer, just needing to display an amount of values, let say 3 from 100 posts for example just add {% for post in posts|slice:"3" %} and loop it normally and only 3 posts will be added.
This shows 1 to 20 numbers:
{% for i in "x"|rjust:"20"|make_list %}
{{ forloop.counter }}
{% endfor %}
also this can help you:
(count_all_slider_objects come from views)
{% for i in "x"|rjust:count_all_slider_objects %}
{{ forloop.counter }}
{% endfor %}
or
{% with counter=count_all_slider_objects %}
{% if list_all_slider_objects %}
{% for slide in list_all_slider_objects %}
{{forloop.counter|add:"-1"}}
{% endfor%}
{% endif %}
{% endwith %}
You can pass range(n) instead of n in the context in views.py. This will give you an iterable list.
context['range']= range(n)
Then you can iterate in your template this way:
{% for i in range %}
<!-- your code -->
{% endfor %}
{% for i in range(10) %}
{{ i }}
{% endfor %}

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