Determine order of iteration in django template for loop - django

I am populating a list in my view:
hits_object = {}
hits_object['Studio'] = hitlist('Studio',days)
hits_object['Film'] = hitlist('Film',days)
hits_object['Actor'] = hitlist('Actor',days)
hits_object['Historical Event'] = hitlist('Event',days)
hits_object['Pop Culture'] = hitlist('Pop_Culture',days)
Then I am displaying it in my template:
{% for model, hits in hits_object.items %}
{% if hits %}
<u> Most {{ model }} views in last {{ days }} days</u>
<ol>
{% for hit in hits %}
<li>{{ hit.name }} - {{ hit.count }}</li>
{% endfor %}
</ol>
</u>
{% endif %}
{% endfor %}
The problem is that the models display in a seemingly random order: first Actor, then Studio, Historical Event, Film, etc.
How can I force the for loop in the template to iterate the object in a specific order?

Dictionaries are unordered. If you need to preserve insertion order, use an ordered dict implementation - there's one in django.utils.datastructures.SortedDict, for example.
Or, since you don't seem to be using the key of the dictionary but are just iterating through, appending to a simple list would seem to be easier.

As Daniel explained, dictionaries are accessed randomly. Here is one way to do what you want:
hits_object = list()
hits_objects.append(
(hitlist('Studio',days),
hitlist('Film',days),
hitlist('Actor',days),
hitlist('Event',days),
hitlist('Pop_Culture',days))
In your view:
{% for studio,film,actor,event,pop_culture in hits_objects %}
# do something...
{% 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.

Django DRY List iteration

As someone who is just starting to use Django in my scarce time, I appreciate in advance your time in helping me learn how to make my code cleaner and correct.
I have two lists comprised of two querysets as follows:
company_list = [Company_stats.objects.filter(period__exact=P, company_name__in=master_names)]
industry_list = [Industry_stats.objects.filter(period__exact=P, industry_name__in=master_names)]
I iterate through both lists in my template to create a small table.
{%for c in company_list%}
{%for z in c %}
{{ z.company_name }}
{{ z.nxt_m_ret_est }}
{{ z.nxt_m_ret_rat }}
{% endfor %}
{% endfor %}
{%for c in industry_list%}
{%for z in c %}
{{ z.industry_name }}
{{ z.nxt_m_ret_est }}
{{ z.nxt_m_ret_rat }}
{% endfor %}
{% endfor %}
This works fine, however, since I am using the same code except for z.industry_name vs. z.company_name I was wondering whether you could help me figure out a better way to do this.
I have tried combining the lists into one list with both querysets in it and that works except for the obvious issue that I don't know how to tell it to retrieve z.company_name or z.industry_name depending on the queryset where the data is coming from, because everything became part of the same list.
Once you've changed the field to name on both models you can put both querysets into the same list and then iterate over that.
master_list = [model.objects.filter(period__exact=P, name__in=master_names) for model in (Company_stats, Industry_stats)]
...
{% for l in master_list %}
{% for i in l %}
{{ i.name }}
{{ i.nxt_m_ret_est }}
{{ i.nxt_m_ret_rat }}
{% endfor %}
{% endfor %}
If you want to have the code be more generic, it would help to make your names more generic. Would it be possible for you to change industry_name and company_name to just name throughout your system?

django template and dictionary of lists

I'm using django's template system, and I'm having the following problem:
I pass a dictionary object, example_dictionary, to the template:
example_dictionary = {key1 : [value11,value12]}
and I want to do the following:
{% for key in example_dictionary %}
// stuff here (1)
{% for value in example_dictionary.key %}
// more stuff here (2)
{% endfor %}
{% endfor %}
However, this does not enter on the second for loop.
Indeed, if I put
{{ key }}
on the (1), it shows the correct key, however,
{{ example_dictionary.key }}
shows nothing.
In this answer, someone proposed using
{% for key, value in example_dictionary.items %}
However, this does not work in this case because I want (1) to have information regarding the particular key.
How do I achieve this? Am I missing something?
I supose that you are looking for a nested loop. In external loop you do something with dictionary key and, in nested loop, you iterate over iterable dictionary value, a list in your case.
In this case, this is the control flow that you need:
{% for key, value_list in example_dictionary.items %}
# stuff here (1)
{% for value in value_list %}
# more stuff here (2)
{% endfor %}
{% endfor %}
A sample:
#view to template ctx:
example_dictionary = {'a' : [1,2]}
#template:
{% for key, value_list in example_dictionary.items %}
The key is {{key}}
{% for value in value_list %}
The key is {{key}} and the value is {{value}}
{% endfor %}
{% endfor %}
Results will be:
The key is a
The key is a and the value is 1
The key is a and the value is 2
If this is not that you are looking for, please, use a sample to ilustrate your needs.

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

Django template for loop iterating two item

I try to implement a forloop in Django tempalte iterating two items per cycle such that
{% for c in cList%}
<ul class="ListTable">
<li>
{{ c1.name }}
</li>
<li>
{{ c2.name }}
</li>
</ul>
{% endfor %}
I know my code is not a proper way to do that but I couldn't find anyway.
I really appreciate for any suggestion
Thanks
If you can control the list structure that is cList, why don't you just make it a list of tuples of 2 elements or a list of list of 2 elements, like
#in the view
cList = [(ob1, ob2),
(ob3, ob4)]
and the in the template
{% for c1, c2 in cList %}
<ul class="ListTable">
<li>
{{ c1.name }}
</li>
<li>
{{ c2.name }}
</li>
</ul>
{% endfor %}
Also you can use the zip function to facilitate the creation of cList, or define a
function which create that kind of structure from a list of objects, like
def pack(_list):
new_list = zip(_list[::2], _list[1::2])
if len(_list) % 2:
new_list.append((_list[-1], None))
return new_list
One option is to use the the built-in template tag cycle
and do something like:
{% for c in c_list %}
{% cycle True False as row silent %}
{% if row %}
<ul class="ListTable">
{% endif %}
<li>
{{ c.name }}
</li>
{% if not row or forloop.last %}
</ul>
{% endif %}
{% endfor %}
Note: if you have odd number of element on the list the last table will have only one element with this option, sice we are checking for forloop.last
I tried to implement cyraxjoe solution which does work, but theres only one problem with it...
a = [1,2,3] will return [(1,2)] but will remove the 3.
So i was asking around in irc freenode #python for a solution and i got this:
it = iter(a); nested = [list(b) for b in itertools.izip_longest(it, it)]
print nested
[[1, 2], [3, None]]
I was also told to look up the documentation for the itertools module, and search for the "grouper" recipe. which does something similar but i havent tried it yet.
I hope this helps :)
*Credits to except and lvh from the #python channel