Home > Software engineering >  how to display subcategories title and its items under parent category in django?
how to display subcategories title and its items under parent category in django?

Time:12-15

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">&laquo; 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 &raquo;</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...

clone Github repo from enter image description here

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>
  • Related