Django regroup of cursor results - django

Using Django, I have a fairly complex SQL query that is working fine and returning the results I want, so I am not keen to try to reproduce this using Django's model query notation.
I am passing my results to my template and am able to display them using the following notation:
{% for line in myresults %}
...
{{ line.0 }}
...
{{ line.1 }}
etc.
{% endfor %}
Is there any way to use the regroup tag on this result set?
If I try:
{% regroup myresults by myresults.0 as mylist %}
{% for item in mylist %}
{{ item.grouper }}
{% for line in item.list %}
...
{{ line.0 }}
{{ line.1 }}
etc.
I get all of the results as before (without any regrouping) but don't get the item.grouper (all I see is "None" as a single group rather than my multiple groups)
Is this a syntax issue or am I trying something that doesn't fit regroup's capabilities?
I have used regroup in the normal way with a list of objects, but the above has me stumped.
Any suggestions? Thanks in advance.

with {% regroup myresults by myresults.0 as mylist %} you group by the first item in the list myresults you must group by some field of the items.
Next example from here:
cities = [
{'name': 'Mumbai', 'population': '19,000,000', 'country': 'India'},
{'name': 'Calcutta', 'population': '15,000,000', 'country': 'India'},
{'name': 'New York', 'population': '20,000,000', 'country': 'USA'},
{'name': 'Chicago', 'population': '7,000,000', 'country': 'USA'},
{'name': 'Tokyo', 'population': '33,000,000', 'country': 'Japan'},
]
...and you'd like to display a hierarchical list that is ordered by country, like this:
India
Mumbai: 19,000,000
Calcutta: 15,000,000
USA
New York: 20,000,000
Chicago: 7,000,000
Japan
Tokyo: 33,000,000
You can use the {% regroup %} tag to group the list of cities by country. The following snippet of template code would accomplish this:
{% regroup cities by country as country_list %}
<ul>
{% for country in country_list %}
<li>{{ country.grouper }}
<ul>
{% for item in country.list %}
<li>{{ item.name }}: {{ item.population }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>

Related

Printing a list in a django template without a trailing comma

My Django template was printing a list with the elements separated by comma, but the last item always had a comma as well. I was able to solve the problem in the template by doing the following:
<li>Producer:
{% for producer in producers %}
{% if not forloop.last %}
{{ producer }},
{% else %}
{{ producer }}
{% endif %}
{% endfor %}
</li>
But after reading some posts on here, I think it would be better to do it in the views.py file. I don't know how to do that and couldn't really understand the other posts on here. Here is the corresponding views file:
def song(request, song_id):
"""Show a single song."""
song = Song.objects.get(id=song_id)
date_added = song.date_added
artist = song.artist
url = song.url
year = song.year
genres = song.genre.all()
country = song.country
producer = song.producer.all()
label = song.label
source = song.source
source_url = song.source_url
comments = song.comments
context = {'song': song, 'date_added': date_added, 'artist': artist,
'url': url, 'year': year, 'genres': genres, 'country': country,
'producers': producer, 'label': label, 'source': source,
'source_url': source_url, 'comments': comments}
return render(request, 'great_songs_app/song.html', context)
Is there a way to turn 'producer' into a dictionary and pass it to the template in a way that all the items except for the last will be separated by a comma?
You can use the join template built-in (ref https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#join)
So your example:
<li>Producer:
{% for producer in producers %}
{% if not forloop.last %}
{{ producer }},
{% else %}
{{ producer }}
{% endif %}
{% endfor %}
</li>
would simply become:
<li>Producer:
{{ producers|join:', ' }}
</li>
You can send it as common separated string. Also it should be producers instead of producer
context = {'song': song, 'date_added': date_added, 'artist': artist,
'url': url, 'year': year, 'genres': genres, 'country': country,
'producers': ','.join(map(str, song.producer.all())), 'label': label, 'source': source,
'source_url': source_url, 'comments': comments}

Query the same object multiple times in Django view [duplicate]

I have a QuerySet like:
items = Item.objects.all()
Item has a 'name' field. In the template I want to show:
A
Axes
Alcohol
B
Bazookas
C
Coins
Cartridges
S
Swords
Sparrows
So the items are ordered and group by the first letter. Missing letters are omitted. Does anyone have any ideas?
There's a template tag for this, if all you care about is its presentation on the page. First, define an organizational principle in the class. In your case, it's the first letter:
class Item(models.Model):
...
def first_letter(self):
return self.name and self.name[0] or ''
And then define a regroup in the template, using the first_letter call:
{% regroup items by first_letter as letter_list %}
<ul>
{% for letter in letter_list %}
<li>{{ letter.grouper }}
<ul>
{% for item in letter.list %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Just wanted to add that if you use this and your item has a lower-case first character it will be a separate group. I added upper to it.
return self.name and self.name.upper()[0] or ''
Alternatively you could use slice inline in the template without the need for a first_letter method on your model.
{% regroup items by name|slice:":1" as letter_list %}
<ul>
{% for letter in letter_list %}
<li>{{ letter.grouper }}
<ul>
{% for item in letter.list %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Even easier. You can group by first leter just in 'regroup':
{% regroup items|dictsort:"name" by name.0 as item_letter %}
<ul>
{% for letter in item_letter %}
<h4>{{ letter.grouper|title }}</h4>
{% for i in letter.list|dictsort:"name" %}
<li>{{ i.name }}</li>
{% endfor %}
{% empty %}
<span>There is no items yet...</span>
{% endfor %}
</ul>
name.0 in this case the same as item.name[0] in Python.
Tested in Django 1.10
For Django REST you can do like this,
import string
import collections
from rest_framework.response import Response
from rest_framework import status, viewsets
def groupby(self, request):
result = []
for i in list(string.ascii_uppercase):
c = City.objects.filter(name__startswith=i)
if c:
result.append((i, map((lambda x: x['name']),list(c.values('name')))
))
return Response(collections.OrderedDict(sorted(dict(result).items())), status=status.HTTP_200_OK)
City Models
class City(models.Model):
"""All features model"""
name = models.CharField(max_length=99)
Response
{
"A": [
"Adelanto",
"Azusa",
"Alameda",
"Albany",
"Alhambra",
"Anaheim"
],
"B": [
"Belmont",
"Berkeley",
"Beverly Hills",
"Big Sur",
"Burbank"
],
......
}
This is another take for doing in straight Django and Python. The other solution offered was terribly inefficient
import itertools
collector = {}
item_qs = Item.objects.all().order_by('name')
for alphabet_letter, items in itertools.groupby(item_qs, lambda x: x.name[0].lower()):
# you can do any modifications you need at this point
# you can do another loop if you want or a dictionary comprehension
collector[alphabet_letter] = items
What does this give you? A single query to the db.
Should I use a collector? No, you should maybe use a yield this is just a proof of concept.
What ever you do, DO NOT add a query call inside a loop.

Detect row difference (view or model)?

I would like to display a list of publications on my website; however, I would also like to diaplay a header stating the year for each set of publications published on that particular year.
So I would like for my end result to be like this (my reputation is 1 :( I could not upload the image):
https://dl.dropboxusercontent.com/u/10752936/Screen%20Shot%202013-06-21%20at%206.00.15%20PM.png
I have a table with three columns; id (primary key), title (the title of the article), and date (the date of publications)
In my template file; doing the following will print the header before every article:
{% for curr_pub in all_publications %}
<h1>{{ curr_pub.date.year }}</h1>
<li>{{ curr_pub.title }}</li>
{% endfor %}
I am passing all_publications ordered by '-date' which means that I can compare the year of the current row curr_pub with the previous one and check if it differs or not; and print (or not print) the header accordingly. It seems however, that I cannot do that in the template.
Since I am new to Django and Python, I wasn't sure what to do and this is where I need help; my thoughts were the following:
1) Add a function in the model (def is_it_first_publication(self):) that returns true or false - but I really wasn't able to do that :| - ...and I'm not sure if that is what I needed to do or not!
2) Second one is to do in in the view, and pass extra variable(s) to the template; here's an example (which works just fine for this case):
In the view:
def publications(request):
all_publications = Publications.objects.order_by('-date')
after_first_row_flag = False
f_year = 'Null'
list_of_ids_of_first_publications = []
for curr_pub in all_publications:
if after_first_row_flag:
if curr_pub.date.year != f_year:
list_of_ids_of_first_publications.append(curr_pub.id)
f_year = curr_pub.date.year
else:
# The year of first (or earliest) publication has to be added
#
list_of_ids_of_first_publications.append(curr_pub.id)
f_year = curr_pub.date.year
after_first_row_flag = True
template = loader.get_template('counters/publications.html')
context = RequestContext(request, {
'all_publications': all_publications,
'list_of_first_publications': list_of_ids_of_first_publications,
})
return HttpResponse(template.render(context))
In the template:
{% for curr_pub in all_publications %}
{% if curr_pub.id in list_of_first_publications %}
<h1> {{ curr_pub.date.year }} </h1>
{% endif %}
<li> Placeholder for [curr_pub.title] </li>
{% endfor %}
The regroup built in filter can do this for you without annotating your objects in the view. As the documentation says, it's kind of complicated.
https://docs.djangoproject.com/en/dev/ref/templates/builtins/#regroup
{% regroup all_publications by date.year as year_list %}
{% for year in year_list %}
<h1>{{ year.grouper }}</h1>
{% for publication in year.list %}
<li>{{ publication.title }}</li>
{% endfor %}
{% endfor %}
I think you want the regroup template tag;
{% regroup all_publications by date as publication_groups %}
<ul>
{% for publication_group in publication_groups %}
<li>{{ publication_group.grouper }}
<ul>
{% for publication in publication_group.list %}
<li>{{ publication.title }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Maybe the template tag regroup could help.
Alternatively, you could do this grouping by year in the view function (will try to provide code later).

How to iterate over nested dictionaries in django templates

I'm not sure the most efficient way to iterate over my nested dictionaries to print a matrix of the total and good values for every fruit for each date. Take for instance the two lists and dictionary below:
fruits = ['apples','oranges','bananas']
harvest_dates = ['2011-07-23','2011-07-22','2011-07-21']
harvest_data = {
'apples': {
'2011-07-23': {
'total': 100,
'good': 80},
'2011-07-22': {
'total': 97,
'good': 92},
'2011-07-21': {
'total': 90,
'good': 85}
},
'oranges': {
'2011-07-23': {
'total': 86,
'good': 82},
'2011-07-22': {
'total': 90,
'good': 75},
'2011-07-21': {
'total': 92,
'good': 92}
},
'bananas': {
'2011-07-23': {
'total': 10,
'good': 9},
'2011-07-22': {
'total': 12,
'good': 11},
'2011-07-21': {
'total': 9,
'good': 9}
}
}
I can easily do this in python:
for fruit in fruits:
for day in harvest_dates:
print "harvest: %s" % harvest_data[fruit][day]['total']
print "good crop: %s" % harvest_data[fruit][day]['good']
But I don't know how to access this data in django templates. I had been trying something such as:
{% for fruit in fruits %}
...
{% for day in harvest_dates %}
...
{{ harvest_data.fruit.day.total }}
{{ harvest_data.fruit.day.good }}
...
{% endfor %}
{% endfor %}
But it's simply not working.
{% for fruit in fruits %}
{{ harvest_data.fruit }} <--- this does not exist
{{ harvest_data[fruit] }} <--- this does not work
{% endfor %}
I'm a complete amateur and I'm probably going about this all wrong, but I've Google'd quite a bit and it's not clear to me what the best approach to getting the data I want is.
Since you're familiar with python, the following is logically how you would want to iterate through your dictionary in a Django template:
for key,value in harvest_data.items():
... print key
... for key2,value2 in value.items():
... print key2
... for key3,value3 in value2.items():
... print "%s:%s"%(key3,value3)
In your template, this translates as follows:
{% for key, value in harvest_data.items %}
{{ key }} <br>
{% for key2,value2 in value.items %}
{{ key2 }} <br>
{% for key3, value3 in value2.items %}
{{ key3 }}:{{ value3 }} <br>
{% endfor %}
{% endfor %}
{% endfor %}
The Django docs actually briefly include an example of how to iterate through dictionaries when describing how the for template tag works:
https://docs.djangoproject.com/en/dev/ref/templates/builtins/#for
as rolling stone says thats the way to iterate over dictionaries in templates, i would only change the key, value keywords for different keywords in every iteration like this:
{% for key, value in harvest_data.items %}
{{ key }} <br>
{% for key2,value2 in value.items %}
{{ key2 }} <br>
{% for key3, value3 in value2.items %}
{{ key3 }}:{{ value3 }} <br>
{% endfor %}
{% endfor %}
{% endfor %}
just for the sake of clarity :)
And if you want to line up your values i would suggest you use another data structure where you can sort by date, for example a something like this:
{ 'oranges' : [(date1, value1), (date2,value2)] ...}
Try to do the least possible operations in your templates, so dont do a sort or nested if's if you dont have to
Really old question, but I will add my 1.5c.
This is a good use case of the regroup tag (https://docs.djangoproject.com/en/1.9/ref/templates/builtins/#regroup) and a bit of data refactoring:
Have your data as a simple list of data points:
harvest_data = [
{'fruits': 'apples', 'date': '2011-07-23', 'total': 100, 'good': 80},
# ...
]
In your template, group by the chosen dimension(s):
{% regroup harvest_data by fruits as data_by_fruits %}
{% for data in data_by_fruits %}
<h1>{{ data.grouper }}</h1> # 'apples'
{% regroup data.list by date as data_by_fruits_date %}
{% for data_1 in data_by_fruits_date %}
<h2>{{ data_1.grouper }}</h2> # '2011-07-23'
{% for datapoint in data_1.list %}
total: {{ datapoint.total }} <br/>
good: {{ datapoint.good }} <br/>
{% endfor %}
{% endfor %}
{% endfor %}

Django grouping queryset by first letter?

I have a QuerySet like:
items = Item.objects.all()
Item has a 'name' field. In the template I want to show:
A
Axes
Alcohol
B
Bazookas
C
Coins
Cartridges
S
Swords
Sparrows
So the items are ordered and group by the first letter. Missing letters are omitted. Does anyone have any ideas?
There's a template tag for this, if all you care about is its presentation on the page. First, define an organizational principle in the class. In your case, it's the first letter:
class Item(models.Model):
...
def first_letter(self):
return self.name and self.name[0] or ''
And then define a regroup in the template, using the first_letter call:
{% regroup items by first_letter as letter_list %}
<ul>
{% for letter in letter_list %}
<li>{{ letter.grouper }}
<ul>
{% for item in letter.list %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Just wanted to add that if you use this and your item has a lower-case first character it will be a separate group. I added upper to it.
return self.name and self.name.upper()[0] or ''
Alternatively you could use slice inline in the template without the need for a first_letter method on your model.
{% regroup items by name|slice:":1" as letter_list %}
<ul>
{% for letter in letter_list %}
<li>{{ letter.grouper }}
<ul>
{% for item in letter.list %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
Even easier. You can group by first leter just in 'regroup':
{% regroup items|dictsort:"name" by name.0 as item_letter %}
<ul>
{% for letter in item_letter %}
<h4>{{ letter.grouper|title }}</h4>
{% for i in letter.list|dictsort:"name" %}
<li>{{ i.name }}</li>
{% endfor %}
{% empty %}
<span>There is no items yet...</span>
{% endfor %}
</ul>
name.0 in this case the same as item.name[0] in Python.
Tested in Django 1.10
For Django REST you can do like this,
import string
import collections
from rest_framework.response import Response
from rest_framework import status, viewsets
def groupby(self, request):
result = []
for i in list(string.ascii_uppercase):
c = City.objects.filter(name__startswith=i)
if c:
result.append((i, map((lambda x: x['name']),list(c.values('name')))
))
return Response(collections.OrderedDict(sorted(dict(result).items())), status=status.HTTP_200_OK)
City Models
class City(models.Model):
"""All features model"""
name = models.CharField(max_length=99)
Response
{
"A": [
"Adelanto",
"Azusa",
"Alameda",
"Albany",
"Alhambra",
"Anaheim"
],
"B": [
"Belmont",
"Berkeley",
"Beverly Hills",
"Big Sur",
"Burbank"
],
......
}
This is another take for doing in straight Django and Python. The other solution offered was terribly inefficient
import itertools
collector = {}
item_qs = Item.objects.all().order_by('name')
for alphabet_letter, items in itertools.groupby(item_qs, lambda x: x.name[0].lower()):
# you can do any modifications you need at this point
# you can do another loop if you want or a dictionary comprehension
collector[alphabet_letter] = items
What does this give you? A single query to the db.
Should I use a collector? No, you should maybe use a yield this is just a proof of concept.
What ever you do, DO NOT add a query call inside a loop.