Search code examples
pythondjangofiltercategories

Subcategories display in Django


Hope you can help me with this one.I am very new to Python/Django so my code might be quite bad. I am creating a website/blog and on my navigation bar I have a list of the categories which contain dropdown menues with the subcategories. When I click on each subcategory I want it to display just the posts from that subcategory. Here is part of my code for this:

"Models":

from django.db import models
from django.utils import timezone
from django.utils.text import slugify
from django.urls import reverse
from ckeditor_uploader.fields import RichTextUploadingField


class Post(models.Model):
    NO_CATEGORY = 'No Category'
    GETTING_STARTED = 'Getting Started'
    DIY = "DIY"
    GARDENS = 'Gardens'
    TERRARIUMS = 'Terrariums'
    CATEGORIES = (
        (NO_CATEGORY, 'No Category'),
        (GETTING_STARTED, 'Getting Started'),
        (DIY, 'DIY'),
        (GARDENS, 'Gardens'),
        (TERRARIUMS, 'Terrariums'),
        )
    title = models.CharField(max_length=200, unique=True)
    slug = models.SlugField(unique=True, default='', blank=True)
    image = models.ImageField(upload_to='images/')
    content = RichTextUploadingField(null=True, blank=True)
    summary = models.CharField(max_length=150, unique=True, null=True)
    category = models.CharField(choices=CATEGORIES, max_length=15, default=NO_CATEGORY)
    subcategory = models.ForeignKey('Subcategory', on_delete=models.SET_NULL, null=True)
    created_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return self.title

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super(Post, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('core:post_detail', args=[str(self.slug)])

    class Meta:
        ordering = ['-created_date']


class Subcategory(models.Model):
    NO_CATEGORY = 'No Category'
    TOOLS = 'Tools'
    HOW_TO = 'How To'
    SUPPLIES = 'Supplies'
    FURNITURE = 'Furniture'
    ACCESSORIES = 'Accessories'
    DECOR = 'Decor'
    MINIATURE = 'Miniature'
    MICRO = 'Micro'
    OPEN = 'Open'
    ENCLOSED = 'Enclosed'
    SUBCATEGORIES = [
        (NO_CATEGORY, 'No Category'),
        ('Getting Started', ((TOOLS, 'Tools'), (HOW_TO, 'How To'), (SUPPLIES, 'Supplies'),)),
        ('DIY', ((FURNITURE, 'Furniture'), (ACCESSORIES, 'Accessories'), (DECOR, 'Decor'),)),
        ('Gardens', ((MINIATURE, 'Miniature'), (MICRO, 'Micro'),)),
        ('Terrariums', ((OPEN, 'Open'), (ENCLOSED, 'Enclosed'),)),
        ]
    name = models.CharField(choices=SUBCATEGORIES, max_length=15, default=NO_CATEGORY)
    slug = models.SlugField(max_length=150, unique=True, null=True)

    class Meta:
        verbose_name = "Subcategory"
        verbose_name_plural = "Subcategories"

    def get_posts(self):
        return Post.objects.filter(subcategories_name=self.name)

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        return reverse('core:post_subcategory', kwargs={'slug': self.slug})


class Picture(models.Model):
    title = models.CharField(max_length=100, unique=True)
    image = models.ImageField(upload_to='images/gallery/')
    created_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return f'{self.title}'

    class Meta:
        ordering = ['-created_date']

"Views":

from django.shortcuts import render
from .models import Post, Subcategory, Picture
from django.core.paginator import Paginator
from django.views import generic


def base(request):
    return render(request, 'core/base.html')


def home(request):
    posts = Post.objects.all()
    paginator = Paginator(posts, 5)
    page = request.GET.get('page')
    posts = paginator.get_page(page)
    return render(request, 'core/home.html', {'posts': posts})


class PostList(generic.ListView):
    model = Post
    queryset = Post.objects.all().order_by('-created_date')
    template_name = 'home.html'


def post_detail(request, slug=None):
    post = Post.objects.get(slug=slug)
    return render(request, 'core/post_detail.html', {'post': post})


class PostDetail(generic.DetailView):
    model = Post
    template_name = 'post_detail.html'


def post_subcategory(request):
    subcategories = Subcategory.objects.all()
    paginator = Paginator(subcategories, 5)
    page = request.GET.get('page')
    subcategories = paginator.get_page(page)
    return render(request, 'core/post_subcategory.html', {'subcategories': subcategories})


class SubcategoryDetailView(generic.DetailView):
    model = Subcategory
    context_object_name = 'subcategory'
    template_name = 'core/post_subcategory.html'

    def get_queryset(self):
        return Post.objects.filter(subcategory_id=self.kwargs.get('slug'))


def base_gallery(request):
    pictures = Picture.objects.all()
    paginator = Paginator(pictures, 16)
    page = request.GET.get('page')
    pictures = paginator.get_page(page)
    return render(request, 'core/base_gallery.html', {'pictures': pictures})


class PictureList(generic.ListView):
    queryset = Picture.objects.all().order_by('-created_date')
    template_name = 'base_gallery.html'


def contact(request):
    return render(request, 'core/contact.html', {'contact': contact})

"config/urls"

from django.shortcuts import render
from .models import Post, Subcategory, Picture
from django.core.paginator import Paginator
from django.views import generic


def base(request):
    return render(request, 'core/base.html')


def home(request):
    posts = Post.objects.all()
    paginator = Paginator(posts, 5)
    page = request.GET.get('page')
    posts = paginator.get_page(page)
    return render(request, 'core/home.html', {'posts': posts})


class PostList(generic.ListView):
    model = Post
    queryset = Post.objects.all().order_by('-created_date')
    template_name = 'home.html'


def post_detail(request, slug=None):
    post = Post.objects.get(slug=slug)
    return render(request, 'core/post_detail.html', {'post': post})


class PostDetail(generic.DetailView):
    model = Post
    template_name = 'post_detail.html'


def post_subcategory(request):
    subcategories = Subcategory.objects.all()
    paginator = Paginator(subcategories, 5)
    page = request.GET.get('page')
    subcategories = paginator.get_page(page)
    return render(request, 'core/post_subcategory.html', {'subcategories': subcategories})


class SubcategoryDetailView(generic.DetailView):
    model = Subcategory
    context_object_name = 'subcategory'
    template_name = 'core/post_subcategory.html'

    def get_queryset(self):
        return Post.objects.filter(subcategory_id=self.kwargs.get('slug'))


def base_gallery(request):
    pictures = Picture.objects.all()
    paginator = Paginator(pictures, 16)
    page = request.GET.get('page')
    pictures = paginator.get_page(page)
    return render(request, 'core/base_gallery.html', {'pictures': pictures})


class PictureList(generic.ListView):
    queryset = Picture.objects.all().order_by('-created_date')
    template_name = 'base_gallery.html'


def contact(request):
    return render(request, 'core/contact.html', {'contact': contact})

"app/urls":

from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
import core.views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('core/', include('core.urls')),
    path('ckeditor/', include('ckeditor_uploader.urls')),
    path('', core.views.home, name='home'),
    path('', core.views.base, name='base'),
    path('post_subcategory', core.views.post_subcategory, name='post_subcategory'),
    path('post_detail', core.views.post_detail, name='post_detail'),
    path('base_gallery', core.views.base_gallery, name='base_gallery'),
    path('contact', core.views.contact, name='contact')
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


"post_category.html":

{% extends "core/base.html" %}
{% block content %}
{% load static %}


  <div class="category-title">
<h1>HOW TO</h1>

  </div>
{% for subcategory in subcategories %}
    {% for post in subcategory.post_set.all %}
<h1>{{ post.title }}</h1>
<div class="category-content">
  <figure class="gallery-picture">
    <h1 class="picture-text"></h1>
    <img src="{{ post.image.url }}" alt="gallery image">
  </figure>

</div>
  {% endfor %}
  {% endfor %}
{% endblock %}

This is what I've tried so far and I can display all the subcategories on that template but I don't know how to filter them one by one, if that makes any sense.


Solution

  • First, I think the data structure and your model relationships are not properly placed.

    I think you should have a category model and a sub_category model too.

    class Category(models.Model):
        title = models.Charfield(...)
    
    class SubCategory(models.Model):
       category = models.ForiegnKey(on_delete=models.CASCADE)
       title = models.Charfield(...)
    

    By doing this, you wouldn't need the dictionary and you won't also need the category field on Post model so all have to query in your views.py is where post.sub_category.category == the requested category or where post.sub_cateogory == the requested sub_category.

    Editing to show how to implement in views.py

    class DIYCategoryList(generic.ListView):
        template_name = 'blog/diy_category.html'
        model = Post
        context_object_name = 'diy_posts'
        paginate_by = 16
    
        def get_queryset(self):
            return Post.objects.filter(sub_category.category.title='DIY')