Django multi level reverse lookup - django

I have a site where the items are classified by categories and subcategories. My models look like this:
from django.db import models
class Category(models.Model):
name = models.CharField(max_lenght=100)
class Subcategory(models.Model):
name = models.CharField(max_lenght=100)
category = models.ForeignKey('Category')
class Item(models.Model):
name = models.CharField(max_lenght=100)
subcategory = models.ForeignKey('Subcategory')
I need to display all the items of one category and all the subcategories with the items in the same page (it's a requisite of the project) and the lists should be in order, like this:
Category:
Item 1
Item 2
Item 3
Item 4
Item 5
Item 6
Subcategory 1
Item 1
Item 3
Item 4
Subcategory 2
Item 2
Item 5
Item 6
So far I have two possible queries but I can't get this done.
items = Item.objects.prefetch_related('subcategory', 'subcategory__category').filter(subcategory__category__slug=categorySlug)
This query gives me a QuerySet and I can do a for loop in my template to show all the items but I can't order them by subcategory.
{% for item in items %}
What I need is something like for subcategory in items.subcategory.
The next query is the inverse:
category = Category.objects.prefetch_related('subcategory_set', 'subcategory_set__item_set').get(slug=categorySlug)
With this query I can do a loop to show the items by subcategory
{% for subcategory in category.subcategory_set.all %}
{% for item in subcategory.item_set.all %}
What I can't do is to display all the items in order by category. I need something like for item in category.subcategory_set__item_set.all or for item in category.subcategory_set.all.item_set.all
I am aware that I can do it using multiple queries (in fact using the two queries at the same time), but I'm trying to use a single query to do this job. Is it possible?

You can just take a list of subcategories and use regroup
{% regroup subcategories by category as subcat_list %}
<ul>
{% for sub_cat in subcat_list %}
<li>{{ sub_cat.grouper }}
<ul>
{% for item in sub_cat.list %}
<li>{{ item.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
Can you do it with a single query? Not really since you need one to iterate over all subcategories and then another to for the categories to produce the first part of your list

Related

How to group a model data based on another model and paginate in django?

I have two models:
Category - Zero or multiple books can be in one category
Book - A book can have zero or one category
if I do Book.objects.all() I'll get something like [book11, book10, book9, ...] normally but I don't want that.
What I want is something like:
[
[book11, book2, book1],
[book10],
[book8, book6],
[book7],
[book4, book3],
...
]
Where
Books are grouped according to their category. The books that don't have a category will also include in the list as a single element group
Ordering would be according to book's creation in reverse
For better understanding here is my model structure:
class Category(models.Model):
name = models.CharField(max_length=50)
class Book(models.Model):
name = models.CharField(max_length=128)
category = models.ForeignKey(Category, on_delete=models.CASCADE,related_name='books', null=True, blank=True)
To do this grouping in Python code you can use itertools.groupby
from itertools import groupby
qs = Book.objects.order_by('category', '-created_at')
grouped_books = groupby(qs, lambda book: book.category)
for category, books in grouped_books:
print(category)
print(list(books))
You can also do this in a template using the regroup tag
In your view pass this queryset to your template context
books = Book.objects.order_by('category', '-created_at')
In your template
{% regroup books by category as category_list %}
<ul>
{% for category in category_list %}
<li>{{ category.grouper }}
<ul>
{% for book in category.list %}
<li>{{ book }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>

"How to get query in template from django many to many fields by using parent class??"

"I made a parent class model where i put some fields which are related with many to many fields. I want to get all individual data from many to many field using the query of parent class. When i do these i get the all query set of the field.
I tried ,match = Match.objects.all() in views function..
then i tried {{ match.mega_league.pool_price }} to get the value..but its not working on template...
models:
class Match(models.Model):
mega_league = models.ManyToManyField('MegaLeague', blank=True)
class MegaLeague(models.Model):
price_pool = models.IntegerField() winner = models.IntegerField()
views:
match = Match.objects.all()
templates:
{{ match.mega_league.pool_price }}
but it's not working..
'''
when i use {{ match.mega_league.pool_price }} this give me blank result but in database i have data for price_pool and winner also... i need the individual access for price_pool and winner..."
match is a queryset, a list of all Matches. You need to loop through them. Then for each match, mega_league is also a queryset, si you need to iterate through the leagues.
<ul>
{% for m in match %}
<li><ul>
{% for league in match.mega_league.all %}
<li>{{ league.pool_price }} </li>
{% endfor %}
</ul></li>
{% endfor %}
</ul>

Django manytomany field filter list

I am printing a list of beers matching some filters, and the bars where each is on tap. These are in a manytomany relationship. I need to filter this list of bars to only show those in a given state.
I can achieve this using if statements in the template, but then am unable to format the list to use commas with an 'and' before the final item (like https://stackoverflow.com/a/3649002/6180992), as I do not know the length of the list.
I have thought of three ways this might be possible, but cannot get any to work:
filtering the bars related field as well as the beers in the views
assembling the list in the template before looping through again to print it
filtering the bars related field in the template
Here are the relevant sections of code:
models.py
class Bar(models.Model):
bar = models.CharField(max_length=200, default='FinshnPig')
state = models.CharField(max_length=200,default='NY')
def __str__(self):
return self.bar
class Meta:
ordering = ('bar','region')
class Tap(models.Model):
bar = models.ManyToManyField(Bar,default='FinshnPig')
brewery = models.CharField(max_length=200)
beer = models.CharField(max_length=200)
state = models.CharField(max_length=200, default='NY')
def __str__(self):
return self.beer
views.py
f = TapFilter(request.GET, queryset=Tap.objects.filter(state="VIC"))
template:
{% for tap in filter %}
<li>
<b>{{ tap.beer }}</b>
<em>{{ tap.brewery }}</em>
#{% for bar in tap.bar.all %}{% if bar.state == "VIC" %}{{ bar.bar }}</b>{% endif %}{% include "taplists/comma.html" %}{% endfor %}
</li>
{% endfor %}
You can use prefetch for querying only the related bars before sending it to template, like so:
prefetch = Prefetch(
'bar',
queryset=Bar.objects.filter(state=CHOSEN_STATE),
to_attr='selected_states'
)
filter = Tap.objects.filter(state=CHOSEN_STATE).prefetch_related(prefetch)
Now inside your template use the custom attribute you assigned:
{% for tap in filter %}
# Now selected_bars only contains the bars in the state you want
{% for bar in tap.selected_bars.all %}
...
{% endfor %}
{% endfor %}
Additional info on Django docs for prefetch_related
Note: Using prefetch for ManyToMany relations will also increase the performance, as there won't be as many database lookups.

Dynamic html element in Django Template

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!

django: child categories and their parents

I've built a website that categorizes products in categories and children (sub) categories.
These are my models:
class Category(models.Model):
name = models.CharField(max_length=250)
parent = models.ForeignKey('self', related_name='children')
...
class Product(models.Model):
category = models.ForeignKey(Category)
name = models.CharField(max_length=250)
...
On the view that renders the categories, I have this:
def some_view(request, category):
category_list = Category.objects.filter(parent__isnull=True)
product_list = Product.objects.filter(category=category)
My template shows everything correctly:
<ul>
{% for category in category_list %}
<li>
{{ category.name }}
<ul>
{% for child in category.children.all %}
<li>{{ child.name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
I can successfully display the categories and their children. My problem is that I'm using sub-categories to further filter down products, but they should also belong to the main category. For example:
Books
--- Comics
--- Sci-Fi
--- ...
Music
--- Classical
--- Pop
--- ...
If I classify a product in "Books > Comics", I will get that product if I select "Comics" in the category listing in my template. But, selecting "Books" should also list that product because it is the top category, but it doesn't show any products unless I categorize them as the parent category "Books".
I'm not sure how to explain this in a better way, but I basically want to be able to show all products that belong to a sub-category, but when I select the main category, that product should be there as well, and I can't seem to make it work. Any suggestions, please?
Off the top off my head, the simplest to implement would be,
change the foreign key from product to category to many-to-many, then override your model save such that it automatically assigns the parent category to the product for every category.
But a better solution would be (i read your comment, im still saying this), implement django-mptt and use south [http://south.aeracode.org/docs/tutorial/part3.html] to handle the datamigration.