How to get a certain number of elements in django template - django

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

Related

Wagtail ListBlock - how to access first (any) element in template?

I have ListBlock called imagesin Wagtail. It works well. If I put
{{ page.images }}
in a template, it render the html code like:
<ul>
<li>item1</li>
<li>item2</li>
</ul>
But I am unable to find out how to get the first item of the list isolated. Or at least how to iterate over the list manually.
I am pretty sure the solution is simple, however I am unable to google it, find in the docs, or understand from the wagtail source.
You haven't shared your model definition, but I'm going to guess it's something like:
class MyPage(Page):
images = StreamField([
('image_list', blocks.ListBlock(blocks.ImageChooserBlock)),
])
Using the standard pattern for manually looping over a StreamField value as shown in the Wagtail docs, this would be:
{% for block in page.images %}
{% if block.block_type == 'image_list' %}
{# at this point block.value gives you the images as an ordinary Python list #}
{# Output the first image using block.value.0: #}
{% image block.value.0 width-800 %}
{# Or loop over block.value manually with a 'for' loop #}
<ul>
{% for img in block.value %}
<li>{% image img width-800 %}</li>
{% endfor %}
</ul>
{% elif block.block_type == 'some_other_block' %}
...
{% else %}
...
{% endif %}
{% endfor %}
In this case, you probably only have one block type defined (image_list), so the if block.block_type == 'image_list' can be left out; but you'll still need the outer {% for block in page.images %}, because StreamField is still defined as a list of blocks, even if you only have one item in that list.

Django template nested for loop with if statement not working

I need to have a nested loop in my Django template, where the outer loop goes through a list of objects, and the inner loop goes through a list of those object id's, and I want to only do something for the id's on the inner list, it never executes however. I think it has something to do with the condition for the if statement, because if I replace it with a true statement it works but it doesn't work as it is now
(I have checked to see that the id's overlap)
{% for outer in outer_obj_list %}
{% for inner_id in inner_id_list %}
{% if outer.id == inner_id %}
// do something
console.log({{inner_id}});
console.log({{outer.id}});
{% endif %}
{% endfor %}
{% endfor %}
Syntax seems correct. I would just verbosely output everything.
Perhaps it should be something like this:
{% for main_obj in main_obj_list %}
main_obj: {{ main_obj }}
{% for obj_id in obj_id_list %}
obj_id: {{ obj_id}}
main_obj: {{ main_obj.id}}
{% if main_obj.id == obj_id %}
// do something
match: {{main_obj.id}} == {{obj_id}} ;
{% endif %}
{% endfor %}
{% endfor %}
Brother I am also face this problem so my clever mind get some clever solution about this problem we can do that with JavaScript easily so we need to run it in JavaScript and then.
{% for outer in outer_obj_list %}
{% for inner_id in inner_id_list %}
if(outer.id == inner_id.id ){
console.log({{inner_id.id}});
console.log({{outer.id}});
//And also if we reserve place in DOM then we can
//change the inner Html of them easily like.
//demo = document.getElementById("demo");
//demo.innerHTML = inner_Id.id or outer.id
}
{% endfor %}
{% endfor %}

In a django template I would like to use the for loop.counter0 to index into a list

I have something like the following:
{% for i in "xxxxxxxxx" %}
...
{% if passedInList.*forloop.counter0|add:"1"* %} Do Something {% endif %}
...
{% endfor %}
Obviously the if statement is incorrect. I have been using this method to create index names in forms for quite some time, but have never had to use the for loop counter index in an if statement.
Is this possible? If so, how would I go about doing so?
Thanks!
Try to use a with statement to save the forloop counter in a context variable, like so:
{% with index=forloop.counter %}
{% if passedInList.index %} do something {% endif %}
{% endwith %}
(Also, seems like you can use forloop.counter instead of forloop.counter0|add:"1")

Django template for loop. Member before

I want to create such loop:
{% for object in objects %}
{% if object.before != object %}
{{ object }} this is different
{% else %}
{{ object }} this is the same
{% endfor %}
Based on https://docs.djangoproject.com/en/dev/ref/templates/builtins/?from=olddocs#for I can't. Is there really no simple way to do this? Or I just need to use counter and check for objects[counter-1]?
P.S. .before is theoretical and objects is simple query list. I want to take and do something with the loop member that encountered before current loop member.
Check ifchanged template tag
There is a "simple way" to do this: write a custom template tag. They're really not hard. This would probably do the trick (untested):
#register.simple_tag
def compare_objects(object_list):
comparisons = []
for i in range(1, len(object_list)):
if object_list[i] > object_list[i-1]:
comparisons.append('bigger')
else:
comparisons.append('smaller')
return comparisons
The built-in template tags and filters don't make it easy (as of Django 1.4), but it is possible by using the with tag to cache variables and the add, slugify, and slice filters to generate a new list with only one member.
The following example creates a new list whose sole member is the previous member of the forloop:
{% for item in list %}
{% if not forloop.first %}
{% with forloop.counter0|add:"-1" as previous %}
{% with previous|slugify|add:":"|add:previous as subset %}
{% with list|slice:subset as sublist %}
<p>Current item: {{ item }}</p>
<p>Previous item: {{ sublist.0 }}</p>
{% endwith %}
{% endwith %}
{% endwith %}
{% endif %}
{% endfor %}
This isn't an elegant solution, but the django template system has two faults that make this hack unavoidable for those who don't what to write custom tags:
Django template syntax does not allow nested curly parenthesis. Otherwise, we could do this:
{{ list.{{ forloop.counter|add:-1 }} }}
The lookup operator does not accept values stored using with (and perhaps for good reason)
{% with forloop.counter|add:-1 as index %}
{{ list.index }}
{% endwith %}
This code should work just fine as a django template, as long as object has a property or no-argument method called before, and objects is iterable (and '<' is defined).
{% for object in objects %}
{% if object.before < object %}
this is bigger
{% else %}
this is smaller
{% 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)),
})