I want to display subcategories title and its items under parent category in Django e.g. I have 1 parent category then it hast 2 sub cats and then each of them has 3 sub cats. in every sub category there are 10 items.
Parental category1
item1
item2
subcategory 1
item3
item4
item5
child category 1 from subcategory1
item6
item7
subcategory 2
item8
item9
item10
here is my codes:
Models.py
from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
from mptt.models import MPTTModel, TreeForeignKey
class Category(MPTTModel):
name = models.CharField(max_length=settings.BLOG_TITLE_MAX_LENGTH, unique=True)
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children')
slug = models.SlugField(max_length=settings.BLOG_TITLE_MAX_LENGTH, null=True, blank=True)
description = models.TextField(null=True, blank=True)
class MPTTMeta:
order_insertion_by = ['name']
class Meta:
verbose_name_plural = 'Categories'
def __str__(self):
return self.name
def save(self, *args, **kwargs):
value = self.name
if not self.slug:
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('items-by-category', args=[str(self.slug)])
class Item(models.Model):
title = models.CharField(max_length=settings.BLOG_TITLE_MAX_LENGTH)
category = TreeForeignKey('Category', on_delete=models.CASCADE, null=True, blank=True)
description = models.TextField(null=True, blank=True)
slug = models.SlugField(
max_length=settings.BLOG_TITLE_MAX_LENGTH,
)
def __str__(self):
return self.title
def get_absolute_url(self):
kwargs = {
'slug': self.slug
}
return reverse('item-detail', kwargs=kwargs)
def save(self, *args, **kwargs):
if not self.slug:
value = self.title
self.slug = slugify(value, allow_unicode=True)
super().save(*args, **kwargs)
Views.py
from django.views import generic
from .models import Item, Category
class CategoryListView(generic.ListView):
model = Category
template_name = "blog/category_list.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['items'] = Item.objects.all()
return context
class ItemsByCategoryView(generic.ListView):
ordering = 'id'
paginate_by = 10
template_name = 'blog/items_by_category.html'
def get_queryset(self):
# https://docs.djangoproject.com/en/3.1/topics/class-based-views/generic-display/#dynamic-filtering
# the following category will also be added to the context data
self.category = Category.objects.get(slug=self.kwargs['slug'])
queryset = Item.objects.filter(category=self.category)
# need to set ordering to get consistent pagination results
queryset = queryset.order_by(self.ordering)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['category'] = self.category
return context
class ItemDetailView(generic.DetailView):
model = Item
template_name = 'blog/item_detail.html'
items_by_category.html
{% extends "blog/base.html" %}
{% block title %}{{category.name}} list of items{% endblock %}
{% block content %}
<main>
<section>
<div >
<h1>{{category}} items</h1>
<p>{{category.description}}.</p>
</div>
</section>
{% include "blog/_breadcrumbs.html" with ancestors=category.get_ancestors object=category%}
<div>
{% for item in object_list%}
<div>
<a href="{{item.get_absolute_url}}">
<p>{{item}}</p>
</a>
</div>
{% endfor %}
{% if is_paginated %}
<div >
<span >
{% if page_obj.has_previous %}
<a href="?page=1">« first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span >
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last »</a>
{% endif %}
</span>
</div>
{% endif %}
</div>
</main>
{% endblock %}
thanks
I tried to display parental categories but it doesn't show anything
CodePudding user response:
Here I tried to solve a problem like this...
CodePudding user response:
Looking at the docs there's a couple of steps you may need to follow:
First off, your queryset is only finding a single category and you also want the children of the category. MPTT models have some additional model functions to help you traverse the tree. In this case, get_descendants is relevant, as we can keep the original instance in the set. (nb: If you wanted everything from the tree on the page you could use get_family() instead)
self.category = Category.objects.get(slug=self.kwargs['slug'])
#using category instance, create recordset with cat and its children
self.category_and_children = self.category.get_descendants(include_self=True)
we have a full queryset to find the items in now, so we need to adapt our queryset
queryset = Item.objects.filter(category__in=self.category_and_children).order_by('category')
So this will provide all the items grouped by category. You could use this and get a flat list easily, but we want indentation. Unfortunately, because we are grabbing items rather than categories, we can't use the MPTT recursive template to loop though the categories and classify treeForeignKey children - to do that we'd have to make a queryset of categories and use prefetch_related to get the items, eg,
queryset = self.category_and_children.prefetch_related('item_set')
Now we can use something like this in our template:
{% load mptt_tags %}
<ul>
{% recursetree object.list %}
<li>
{{ node.name }}
{% for item in node.item_set.all %}
<div>
<a href="{{item.get_absolute_url}}">
<p>{{item}}</p>
</a>
</div>
{% endfor %}
{% if not node.is_leaf_node %}
<ul >
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>