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.
Related
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).
so i have a model which is,
class Category(SmartModel):
item=models.ManyToManyField(Item)
title=models.CharField(max_length=64,help_text="Title of category e.g BreakFast")
description=models.CharField(max_length=64,help_text="Describe the category e.g the items included in the category")
#show_description=check box if description should be displayed
#active=check box if category is still avialable
display_order=models.IntegerField(default=0)
def __unicode__(self):
return "%s %s %s %s " % (self.item,self.title, self.description, self.display_order)
and as you may see, it has a manytomany field
item=models.ManyToManyField(Item)
i want to return all the items in a template, here is my views.py for this
def menu(request):
categorys= Category.objects.all()
items= categorys.all().prefetch_related('item')
context={
'items':items,
'categorys':categorys
}
return render_to_response('menu.html',context,context_instance=RequestContext(request))
here is how am doing it in the templates,
<ul>
{% for item in items %}
<li>{{ item.item }}
</li>
</ul>
{% endfor %}
after all this,this is what it is returning in my web page,
<django.db.models.fields.related.ManyRelatedManager object at 0xa298b0c>
what am i doing wrong,I have really looked around but all in vain, hoping you can help me out and thanking you in advance
Exactly, you have a many to many manager. You need to actually query something... like all()
{% for item in items %}
{% for i in item.item.all %}
{{ i }}
{% endfor %}
{% endfor %}
Based on your variable naming, I think you're confusing the results of prefetch_related as a bunch of items. It is in fact returning a QuerySet of Category objects.
So it would be more intuitive to call them categories.
{% for category in categories %}
{% for item in category.item.all %}
{{ item }} {# ...etc #}
Try to use:
categorys= Category.objects.prefetch_related('item').all()
And then in template:
{% for category in categorys %}
{% for item in category.item.all %}
{{ item }}
{% endfor %}
{% endfor %}
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 %}
how do i implement the tree structure in django templates with out using django-mptt.
i have model.
class Person(TimeStampedModel):
name = models.CharField(max_length=32)
parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
now i want ..
Parent
Child 1
subchild 1.1
subchild 1.2
nextsubchild 1.2.1
Child 2
Child 3
there names should be click able to show their profile.
I just finished implementing this. I wanted a tree structure for a sub-navigation, but I did not want to do anything strange with recursive templates.
The solution I implemented is very simple: I simply recurse in the view (in my case a generic helper function) and flatten out the hierarchical structure into a simple list. Then, in my template I just use a for loop to iterate over the list.
Each element in the list can be one of three things: "in", the object, or "out". In my case, I'm constructing a series of ul li elements in the view, so when I encounter "in" I create a new ul, when I encounter "out" I close the ul. Otherwise, I render the item.
My template code looks like this:
<ul>
{% for item in sub_nav %}
{% if item == "in" %}
<ul>
{% else %}
{% if item == "out" %}
</ul>
</li>
{% else %}
<li>
<a href='{{item.full_url}}'>{{item.name}}</a>
{% if item.leaf %}
</li>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
</ul>
The code in the helper function looks like this:
def get_category_nav(request,categories=None):
"""Recursively build a list of product categories. The resulting list is meant to be iterated over in a view"""
if categories is None:
#get the root categories
categories = ProductCategory.objects.filter(parent=None)
categories[0].active=True
else:
yield 'in'
for category in categories:
yield category
subcats = ProductCategory.objects.select_related().filter(parent=category)
if len(subcats):
category.leaf=False
for x in get_category_nav(request,subcats):
yield x
else:
category.leaf=True
yield 'out'
Using those snippets, you should be able to build any sort of hierarchical tree you'd like without doing any recursion in the template, and keeping all the logic in the view.
I know there was already an accepted answer for this, but I thought I'd post the technique in case it helps anyone else.
from Django while loop question and
http://docs.djangoproject.com/en/dev/howto/custom-template-tags/#inclusion-tags
# view.py
#register.inclusion_tag('children.html')
def children_tag(person):
children = person.children.all()
return {'children': children}
# children.html
<ul>
{% for child in children %}
<li> {{ child }}</li>
{% if child.children.count > 0 %}
{% children_tag child %}
{% endif %}
{% endfor %}
</ul>
# your template
{% children_tag parent %}
These are great answers but I consolidated a bit and put it on the actual model.
class RecursiveThing(models.Model):
name = models.CharField(max_length=32)
parent = models.ForeignKey('self', related_name='children', blank=True, null=True)
def as_tree(self):
children = list(self.children.all())
branch = bool(children)
yield branch, self
for child in children:
for next in child.as_tree():
yield next
yield branch, None
And then in your template:
<ul>
{% for thing in things %}
{% for branch, obj in thing.as_tree %}
{% if obj %}
<li>{{ obj.name }}
{% if branch %}
<ul>
{% else %}
</li>
{% endif %}
{% else %}
{% if branch %}
</ul>
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
</ul>
It's very simple
All you have to do in your view is get all objects:
people = Person.objects.all()
Then in your template :
{% for person in people %}
<li>- {{person.name}} </li>
{% for child in person.children.all %}
<ul>* {{child.nom}} </ul>
{% endfor %}
</li>
{% endfor %}
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.