I am new to Python Django.
I have a django collection objects, which is arranged in descending order of category_id as follows,
category_id name
1 apple
1 orange
2 car
2 bus
2 truck
3 ifosys
3 wipro
How can I arrange each category items within <ul></ul> element.
Expected result,
<h4>From catgeory 1</h4>
<ul>
<li>apple</li>
<li>orange</li>
</ul>
<h4>From catgeory 2</h4>
<ul>
<li>car</li>
<li>bus</li>
<li>truck</li>
</ul>
<h4>From catgeory 3</h4>
<ul>
<li>ifosys</li>
<li>wipro</li>
</ul>
Thanks in advance
Build your context dict with the category ID as key and value as list.
{
'1': ["apple", "orange"],
'2'" ["car", "bus"],
...
...
'8': ["Foo", "Bar"],
}
And then iterate it at template. Template is just to render the data with some simple iterations and filters. You shouldn't burden the template with object build or parsing.
If I understood the question correctly, by collection of Django objects you mean a QuerySet. Then, as it was already mentioned, you need to group objects by category_id. The straightforward way would be to iterate over the Queryset and create a dictionary, in views.py:
grouped_objects = {}
for obj in obj_collection:
if obj.category_id in grouped_objects.keys():
grouped_objects[obj.category_id].append(obj)
else:
grouped_objects[obj.category_id] = [obj]
Now the grouped_objects dict is ready to be processed by template. I don't think that would be your entire context, so just pass it as a regular variable. Then you only need to use basic iteration tags:
{% for category, object_list in grouped_objects.items %}
<h4>From category {{category}}</h4>
<ul>
{% for obj in object_list %}
<li>{{ obj.name }}</li>
{% endfor %}
</ul>
{% endfor %}
Good luck!
Related
We have the following model structure:
Parent (name, slug, country, children[ManyToMany on Child])
Child
(name, country, children[ManyToMany on Grandchild])
Grandchild (name,
country)
Now, in my view, I want to make sure we are only dealing with data for the current country (country=kwargs["country"]) at all levels. Now, I have been using the following (obfuscated code) in order to filter the children. This allows me to simply reference "children" in the template in order to access the filtered child records, however I'm having trouble determining a best-practice solution for applying this same filtering at the grandchild level.
Below is an example of what is working for filtering children, but how do I perform this same filtering within the template when looping through the grandchildren? I don't want a front-end developer to have to understand the data structure - ideally, I would want them to be able to loop through children, and within that loop, loop through child.grandchildren, which will already be filtered.
View class:
class ParentView(DetailView):
model = Parent
def get_object(self):
return self.model.objects.filter(slug=self.kwargs["slug"], country=self.kwargs["country"])
def get_context_data(self, **kwargs):
context = super(ParentView, self).get_context_data(**kwargs)
context["children"] = self.object.children.filter(country=self.country)
return context
Template sample:
{% for child in children %}
<li>{{child.name}}
<ul>
{# This list of grandchildren is NOT filtered #}
{% for grandchild in child.children%}
<li>{{grandchild.name}}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
Please let me know if I can provide additional information.
In the template:
{% for child in children %}
<li>{{ child.name }}
<ul>
{% for grandchild in child.children.all %}
<li>{{ grandchild.name }}</li>
{% endfor %}
</ul>
</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.
I have a model from which I need to list all the objects from a given time period.
For example: 10-05-2014 to 10-12-2014
And then I need to display the objects date wise. Meaning first display objects created on the start_date(10-05-2014) up until end_date(10-12-2014)
For example:
10-05-2014:
objects list for that day
11-05-2014:
objects list for that day
and so on until end_date
Query:
MyModel.objects.unsettled.filter(
created_on__range=[start_date, end_date]
)
But my problem is how do I list the query set in increasing order by date wise in my template. So that all the objects created on same date will be shown under that date. In short I want to display the list in sections divided by date.
MyModel.objects.unsettled.filter(created_on__range=[start_date, end_date]).order_by("created_on"). But it will just sort the list. How do I group the results.??
Use the {% ifchanged %} template tag:
{% for obj in obj_list %}
{% ifchanged %}
<h2>{{ obj.created_on|date }}</h2>
{% endifchanged %}
<div>{{ obj.name }}</div>
{% endfor %}
Another (slightly more complex) option is to use the {% regroup %} tag.
I have added a custom field for my model (status). But I want to use a custom query for children:
my template tag is:
def get_top_menu(context):
item = Item.objects.all()
try:
item = Item.objects.filter(position__position='top')
except ObjectDoesNotExist:
item = Item.objects.none()
return {
'nodes': item,
}
and template:
<ul class="root">
{% recursetree nodes %}
<li>
{{ node.name }}
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
as doc
How can I using a custom query for children?
Take a look at the MPTTModel instance methods docs (under Models and Managers section). There is a get_children() method which creates a QuerySet containing the immediate children of the model instance, in tree order. The benefit of using this method over the reverse relation provided by the ORM to the instance’s children is that a database query can be avoided in the case where the instance is a leaf node (it has no children).
I have a reusable HTML fragment that I use to list items. So to list items in a view I just do:
variables = RequestContext(request, {
'items': items,
}
return render_to_response('template_in_question',variables)
and the fragment is:
{% for item in items %}
<p>Item: {{item.name}} </p>
{% endfor %}
So far so good. However, there are views where I want to use the same reusable fragment twice. For example, if I want to list the most sold items and the latest items, I need to create two copies of that reusable fragment:
The view would be like this:
variables = RequestContext(request, {
'most_sold_items': most_sold_items,
'latest_items': latest_items
}
and in the HTML would need to have two reusable HTML templates:
{% for item in most_sold_items %}
<p>Item: {{item.name}}</p>
{% endfor %}
and a second one
{% for item in latest_items %}
<p>Item: {{item.name}}</p>
{% endfor %}
So my question is: How can I use, in the same view, two or more item lists, and use a common HTML template for that? For example, in the view above pass "most_sold_items" and "latest_items" and somehow use just one HTML template to list each separately?
You can do it with the include tag. Basically, you'd end up with:
<h1>Most sold items</h1>
{% include "items.html" with items=most_sold_items only %}
<h1>Latest items</h1>
{% include "items.html" with items=latest_items only %}