Django regroup many to many field - django

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.

Related

Django PostgreSQL – Efficiently fetch recursive category structure

I have a model which looks like this:
class Category(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField()
parent = models.ForeignKey(
'categories.Category',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='categories'
)
basically, in the parent field, it references itself. If a parent is set to None, it's the root category.
I use it to build a hierarchy of categories.
What would be the most efficient way to:
fetch all the objects through the hierarchy
display them in a template?
For some reason, select_related does not seem to lead to performance improvements here.
I also found this: How to recursively query in django efficiently?
But had a really hard time applying it to my example, because I still don't really understand what's going on. This was my result:
WITH RECURSIVE hierarchy(slug, parent_id) AS (
SELECT slug, parent_id
FROM categories_category
WHERE parent_id = '18000'
UNION ALL
SELECT sm.slug, sm.parent_id
FROM categories_category AS sm, hierarchy AS h
WHERE sm.parent_id = h.slug
)
SELECT * FROM hierarchy
Would appreciate any help.
Thanks!
One possible solution can be using https://django-mptt.readthedocs.io/en/latest/overview.html#what-is-django-mptt
MPTT is a technique for storing hierarchical data in a database. The
aim is to make retrieval operations very efficient.
The trade-off for this efficiency is that performing inserts and moving items around the tree is more involved, as there’s some extra work required to keep the tree structure in a good state at all times.
from django.db import models
from mptt.models import MPTTModel, TreeForeignKey
class Category(MPTTModel):
name = models.CharField(max_length=50)
slug = models.SlugField()
parent = TreeForeignKey(
'self',
null=True,
blank=True,
on_delete=models.CASCADE,
related_name='children'
)
class MPTTMeta:
order_insertion_by = ['name']
You can use the django-mptt template tag as this:
{% load mptt_tags %}
<ul>
{% recursetree categories %}
<li>
{{ node.name }}
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
There is a tutorial and more information int library docs.
I had the same problem and ended up creating the following function that hits the database once, then sorts out the heirarchy and returns a dict:
def get_category_tree():
categories = Category.objects.order_by('name')
itemtree = {}
# Add 'children' attribute to each category; populate dict
for category in categories:
category.children = {}
itemtree[category.pk] = category
# Add categories to 'children'
for key,value in itemtree.items():
if value.parent_id:
itemtree[value.parent_id].children[key] = value
# Return top-level items
return {k:v for k,v in itemtree.items() if not v.parent_id}
Each value of the returned dict is a top-level Category object which has a children attribute.
You can render it in the template by looping through the dict values. The following example will handle three levels of heirarchy:
<ul>
{% for level1 in category_tree.values %}
<li>
{{ level1.name }}
{% if level1.children %}
<ul>
{for level2 in level1.children.values %}
<li>{{ level2.name }}
{% if level2.children %}
<ul>
{for level3 in level2.children.values %}
<li>{{ level3.name }}</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
{% endif %}
</li>
{% endfor %}
</ul>
If you need to render many levels of the heirarchy, you could consider using template recursion. Have a read of the following question and answers to determine if that might be suitable: Represent a tree of objects in Django template

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.

access to data ManyToMany django template

I try to display a menu category ---> Subcategory ---> products.
I use a context_processor to display all the categories and submenus .
I need to get products based on category and subcategory
I can only display django.db.models.fields.related.ManyRelatedManager at object ....
class Categorias(models.Model):
nome_categoria = models.CharField(max_length=100)
class Subcategoria(models.Model):
nome_subcategoria = models.CharField(max_length=100)
class Product(models.Model):
categoria = models.ManyToManyField('Categorias')
subcategoria = models.ManyToManyField('Subcategoria')
context.py
def menu(request):
return {'menucategoria': Categorias.objects.all(),}
def submenu(request):
return {'submenu': Subcategoria.objects.all(),}
menu.html
{% for c in menucategoria %}
<ul>
<li class="block">
<input type="checkbox" name="item" id="{{c.id}}" />
<label for="{{c.id}}">{{c}}</label>
<ul class="options">
{% for p in produtos.subcategoria.all %}
<li>{{p}}</li>
{% endfor %}
</ul>
</li>
</ul>
{% endfor %}
{% for p in produtos.subcategoria.all %}
In Python you would get a TypeError: 'Manager' object is not iterable exception, but in templates if fails silently...
There are some more tweaks to be done... You seem to got it wrong with the related_name. Related name is used for reversing relationships, not following them. So probably this is what you're after:
class Categoria(models.Model): # singular!
nome_categoria = models.CharField(max_length=100)
class Subcategoria(models.Model):
nome_subcategoria = models.CharField(max_length=100)
class Product(models.Model):
# using ForeignKey instead of ManyToMany. Guessed so because "categoria" is singular, right?
categoria = models.ForeignKey('Categoria', related_name='produtos') # plural in related_name, and "products" not "category"
subcategoria = models.ForeignKey('Subcategoria', related_name='produtos') # plural in related_name, and "products" not "category"
Now you can do stuff like:
{% for p in categoria.produtos.all %}
somestuff...
{% for sc in p.subcategoria.all %}
somemorestuff...
P.S.
You can leave out the related_name altogether. A default related name will be used: product_set in this example.

How do I exclude current object in ManyToMany query?

I have two basic models, Story and Category:
class Category(models.Model):
title = models.CharField(max_length=50)
slug = models.SlugField()
...
class Story(models.Model):
headline = models.CharField(max_length=50)
slug = models.SlugField()
categories = models.ManyToManyField(Category)
...
And my view for story detail:
from django.views.generic import date_based, list_detail
from solo.apps.news.models import Story
def story_detail(request, slug, year, month, day):
"""
Displays story detail. If user is superuser, view will display
unpublished story detail for previewing purposes.
"""
stories = None
if request.user.is_superuser:
stories = Story.objects.all()
else:
stories = Story.objects.live()
return date_based.object_detail(
request,
year=year,
month=month,
day=day,
date_field='pub_date',
slug=slug,
queryset=stories,
template_object_name = 'story',
)
On the view for a given story object -- I'm using a generic detail view -- I'd like to display a list of stories related to the current story via the categories applied to the current story.
Here's how I'm doing this currently in the story detail template:
{% for category in story.category.all %}
<ul id="related_stories">
{% for story in category.story_set.all|slice:"5" %}
<li>{{ story.headline }}</li>
{% endfor %}
</ul>
{% endfor %}
This provides me what I need except I'd like to avoid displaying the linked headline for the story I'm viewing currently.
I believe this is done via the "exclude" filter, but I'm not sure if this belongs on the Category or Story model as a method, or how to construct it.
Any help would be appreciated!
Do this:
class Story(models.Model):
...
#property
def related_story_set(self):
category_id_list = self.category.values_list("id", flat=True)
return Story.objects.filter(category__id__in=category_id_list).exclude(id=self.id)
Then you can do this in the template:
<ul id="related_stories">
{% for related_story in story.related_story_set.all|slice:"5" %}
<li>{{ related_story.headline }}</li>
{% endfor %}
</ul>
You could just check in the template if the currently iterated story is the original story:
{% for category in story.category.all %}
<ul id="related_stories">
{% for substory in category.story_set.all|slice:"5" %}
{% if substory != story %}
<li>{{ story.headline }}</li>
{% endif %}
{% endfor %}
</ul>
{% endfor %}
You asked to put it in a model method:
class Story(models.Model):
...
def get_categories_with_stories(self):
categories = self.category.all()
for category in categories:
category.stories = category.story_set.exclude(id=self.id)[:5]
return categories
This doesn't solve your expensive query issue, but that wasn't a part of the question.
{% for category in story.get_categories_with_stories %}
<ul id="related_stories">
{% for substory in category.stories %}
<li>{{ story.headline }}</li>
{% endfor %}
</ul>
{% endfor %}