django: child categories and their parents - django

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.

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>

Accessing foreign key values in django ListView of GCBV

I have two models related by a Foreign key as follows. (Only shown important fields here.)
in model:
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
description = models.TextField()
...
class Price(models.Model):
category = models.ForeignKey(Category) # referred to above model
sub_type = models.CharField(max_length=4, choices=CHOICE_SUB_TYPE)
price = models.DecimalField()
...
I'm going to display Categories in a ListView along with related pricing details. In order do that, I need to set related pricing objects for each Category object. What is the best and efficient way to do this?
Your code is fine as it is, in templates, you have access to related models. When listing categories in your template you can:
<ul>
{% for cat in categories %}
<li>{{ cat.name }}</li>
<ul>
{% for price in cat.price_set.all %}
{{ price.price }}
{% endfor %}
</ul>
{% endfor %}
</ul>
For displaying categories when showing details for a price, you might want to Use a Single object Mixin with a ListView.
Recommended: Related object reference.

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.

Django multi level reverse lookup

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

Django regroup many to many field

I'm having some trouble making this work on the template, wonder if I could get some help:
I have a table with plants in it, and the plant table has a many-to-many relationship with the category table. Such that a plant can be in one or more categories.
I'm using Django 1.5, here are the models:
class Plant(models.Model):
scientific_name = models.CharField(max_length=128, unique=True)
category = models.ManyToManyField(Category)
...
class Category(models.Model):
category = models.CharField(max_length=128)
...
And the view:
class PlantListView(ListView):
context_object_name='plant_list'
template_name='plants/index.html'
model = Plant
def get_queryset(self):
return self.model.objects.all().order_by('category')
I've tried this:
{% regroup plant_list by category as category_list %}
<ul>
{% for category in category_list %}
<li>{{ category.grouper }}
<ul>
{% for plant in plant_list.list %}
<li>{{ plant.scientific_name }}</li>
{% endfor %}
</ul>
</li>
{% endfor %}
</ul>
But all I get is:
<django.db.models.fields.related.ManyRelatedManager object at 0x1053bfb90>
<django.db.models.fields.related.ManyRelatedManager object at 0x1053bfbd0>
<django.db.models.fields.related.ManyRelatedManager object at 0x1053bfd50>
Of course, what I really want is:
Annuals
Ageratum houstonianum
Abutilon hybridum
Acalypha hispida
Bulbs Perennial
Ageratum houstonianum
Allium giganteum
Allium karataviense
... etc ...
Note that the same plant can exist in multiple categories, and should be listed in each.
What am I doing wrong?
Thanks in advance!!
Well, turned out it was easier (and probably better) to make a Dict of all the results, with category as the index.