django templates: loop through list of classes/strings - django

Is there something like:
{% for class in "red", "green", "blue" %}
<div class="{{ class }}"></div>
{% endfor %}
in django templates?

Well, not directly but you can use cycle combined with a list on the fly:
{% for cnt in "123"|make_list %}
<div class="{% cycle 'red' 'green' 'blue' %}"></div>
{% endfor %}
... another option would be to give your css_classes more generic names like: color_1, color_2, color_3 and then:
{% for cnt in "123"|make_list %}
<div class="color_{{ cnt }}"></div>
{% endfor %}
This would de-couple your css-classes from fixed colors, which is maybe a good idea, if you change the colors later on your css.
---- Update ---
Ok, reading the answers on the link posted by nickromano, I now realize I was wrong. There IS a way of using expr for declaring a real list object on the template. And the split method is better than make_list + cycle :-)

Related

Passing parameters to template for NavBar menu in Django

I am trying to switch my head to start using MVC.
I have a Base HTML template, with an include NavBar code:
{% include "navBar.html" %}
So far, so good. Now, I want to send information to it (to the navBar.html template) regarding the Menu buttons. I have some simple buttons, and others with drop-button behavior.
My buttons are objects, they have information about name, href, position, etc, type (simple-button or drop-button)
So, I created a nested list in this way:
outer_list = []
for a in UserModule.objects.filter(user=user_id, is_active=True):
inner_list = []
inner_list.append(UserModule(a))
for b in Submodule.objects.filter(module=a.module, is_active=True):
inner_list.append(Submodule(b))
outer_list.append(inner_list)
return {'outer_list': outer_list}
So, my first element in every inner_list is the head of the possible drop-button or a simple button depending on his type attribute.
The list at the end is like this:
[0] Button-Simple-A
[1] Button-Drop-A => [sub-button-1, sub-button-2, sub-button-3]
[2] Button-Simple-B
and so on.
When I pass this outer_list to the template, the thing I have to do to arrange the buttons into the menu are crazy things. It has no sense to use MVC if I am going to write a lot of code in the template.
Until now, I am doing this over the template, and It is missing the first element identification, to recognize the button type, etc...
{% with isFirst=true %}
{% for inner_list in outer_list %}
{% for object in inner_list %}
{% if isFirst == true %}
<li><a href=#>Drop_parent</a></li>
{% else %}
<li><a href=#>Drop-Child</a></li>
{% endif %}
{% endfor %}
<li><a href=#>Static Button</a></li>
{% endfor %}
{% endwith %}
It is not finished at all, but I believe I am taking the wrong way.
Good advice will be thankful.
Thanks
Finally,
I got something that works. I would like to know if this is acceptable or there is another better way.
On the view side:
for a in UserModule.objects.filter(user=user_id, is_active=True):
x = Module.objects.get(id=a.module_id, is_active=True)
if x.type == 'drop-parent':
list.append(x)
for b in Submodule.objects.filter(module=a.module_id, is_active=True):
list.append(b)
list.append('drop_end')
return {'menu_list': list}
On the template site:
{% for object in menu_list %}
{% if object.type == 'drop-parent' %}
<li class="dropdown">
{{object.name}}
<div class="dropdown-content">
{% endif %}
{% if object.type == 'drop-child' %}
<a href=#>{{ object.name }}</a>
{% endif %}
{% if object == 'drop_end' %}
</div>
</li>
{% endif %}
{% endfor %}
Another question: Why adding the class identifier inside the list, I can not see the attributes in the template? I mean list.append(Module(x)) is not seen as an object Module inside template; otherwise, if I avoid adding the class I can access the attributes.
Thanks, I appreciate it

How to get a certain number of elements in django template

I wonder how I can get a specific number of items when i put an if statement inside a for loop
i know we can do {% for i in items|slice ":5"%} to get a number of items but when i do
{% for post in posts %}
{% for img in post_imgs %}
{% if img.link == post.link %}
<img class="class" src="{{img.img.url}}" style="width:100%">
{% endif %}
{% endfor %}
{% endfor %}
there's no way of doing that inside the if tag .. any solution
From this answer:
Changing the state of an object in a Django template is discouraged.
You should probably bite the bullet, calculate the condition
beforehand and pass extra state to the template so you can simplify
the template logic.
So just do your comparisons in python in your view, something like:
post_imgs_filtered = [img for img in post_imgs if img.link == post.link]
And then in your template:
{% for img in post_imgs_filtered|slice ":5" %}
<img class="class" src="{{img.img.url}}" style="width:100%">
{% endfor %}

Showing random objects in Django template

In my Django template as I am iterating through a list of objects, I'd like to have one list item say:
<li>Blah</li>
and then another do:
<li>Blah</li>
I see value|random as an option but for some reason this doesn't work:
{% ifequal [1, 2]|random 1 %}
{{ post.title }}
{% else %}
{{ post.title }}
{% endifequal %}
Doing this throws this error:
u'ifequal' takes two arguments
Is there any way to accomplish this? I would think it should be simple but I realize the Django templating language doesn't allow for variable assignments.
Thanks!
You can't put a list directly into the template like that, make_list is what you're after.
make_list returns a list of strings, so this would work.
{% if 12|make_list|random == '1' %}
<li>Blah</li>
{% else %}
<li>Blah</li>
{% endif %}

Building a list in Django templates

With this code:
{% for o in [1,2,3] %}
<div class="{% cycle 'row1' 'row2' %}">
{% cycle 'row1' 'row2' %}
</div>
{% endfor %}
I get a TemplateSyntaxError:
Could not parse the remainder: '[1,2,3]' from '[1,2,3]'
Is there a way of building a list in a template?
We can use split method on str object :
page.html :
{% with '1 2 3' as list %}
{% for i in list.split %}
{{ i }}<br>
{% endfor %}
{% endwith %}
Results :
1
2
3
You can do it via cunning use of the make_list filter, but it's probably a bad idea:
{% for o in "123"|make_list %}
<div class="{% cycle 'row1' 'row2' %}">
{% cycle 'row1' 'row2' %}
</div>
{% endfor %}
p.s. You don't seem to be using o anywhere, so I'm not sure what you're trying to do.
I made this template tag to achieve this goal.
from django import template
register = template.Library()
# use #register.assignment_tag
# only when you're working with django version lower than 1.9
#register.simple_tag
def to_list(*args):
return args
to use it in template:
{% load your_template_tag_file %}
{% to_list 1 2 3 4 5 "yes" as my_list %}
{% for i in my_list %}
{{ i }}
{% endfor %}
Reference here:
Django simple tags
The other answers here look like the ticket (at least for what I wanted), so I'll provide an answer as to WHY you might want to do something like this (and perhaps there's a better answer for my case than what's been provided):
I came across this question looking for a way to build 3 very similar, but not identical buttons using Bootstrap. One button might look like
<div class="btn-group">
<a class="btn btn-primary dropdown-toggle" data-toggle="dropdown" href="#">
Modality
<span class="caret"></span>
</a>
<ul class="dropdown-menu" id="Modality">
<li>Action</li>
</ul>
</div>
where the difference between buttons is limited to the text of the button (Modality, on its own line above) and the contents of the pertaining to the button, which we'll assume is filled dynamically by JS (referencing id="Modality").
If I need to make 10 of these, copy/pasting the HTML seems dumb and tedious, especially if I want to change anything about my button after the fact (like making all of them split-drop-downs) and it goes against DRY.
So, instead, in the template I could do something like
{% with 'Modality Otherbutton Thirdbutton' as list %}
{% for i in list.split %}
<!-- copy/paste above code with Modality replaced by {{ i }} -->
{% endfor %}
{% endwith %}
Now, granted, in this particular case the buttons add functionality to some related data grid, so the button names could be dynamically filled from django model-sourced data as well, but I'm not at that stage in my design right now, and you can see where this sort of functionality is desirable to maintain DRY.
The simplest is to do
{% for x in "123" %}
drodger is correct, you can't do that in the deliberately-crippled Django template lanuage. Either pass in the list as a context variable when you invoke the template or try a template tag like expr. Then you can say {% expr [1,2,3] as my_list %} and then use my_list in your for loop.
This maybe an inspiration. Use the buildin filter add.
{{ first|add:second }}
first is [1, 2, 3] and second is [4, 5, 6], then the output will be [1, 2, 3, 4, 5, 6].
This filter will first try to coerce both values to integers.
If this fails, it'll attempt to add the values together anyway.
This will work on some data types (strings, list, etc.) and fail on others.
If it fails, the result will be an empty string.
The official specification,https://docs.djangoproject.com/zh-hans/2.0/ref/templates/builtins/#built-in-filter-reference

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