From 11d07ede6b39888d7192145862c91de28a9acddc Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Mon, 24 Aug 2026 12:06:38 +0330 Subject: [PATCH] category filtering based on has hadith or has leaf for admin panel dropdowns --- apps/hadis/models/category.py | 8 ++++ apps/hadis/models/hadis.py | 9 ++++ apps/hadis/serializers/serializers_admin.py | 53 +++++++++++++++++++++ apps/hadis/views_admin.py | 17 +++++-- 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/apps/hadis/models/category.py b/apps/hadis/models/category.py index d151612..dc9cab9 100644 --- a/apps/hadis/models/category.py +++ b/apps/hadis/models/category.py @@ -99,6 +99,14 @@ class HadisCategory(LowercaseSlugMixin, MPTTModel): f'Parent source_type: {self.parent.source_type}, ' f'Your source_type: {self.source_type}') ) + if self.parent and self.parent.hadis_set.exists(): + raise ValidationError( + _('Cannot set parent to a category that already contains hadiths/content items.') + ) + if self.pk and self.hadis_set.exists() and self.children.exists(): + raise ValidationError( + _('A category cannot have both hadiths and subcategories.') + ) slug_source_field = 'title' diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index e2fbdb7..a8cbe10 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -2,6 +2,7 @@ from enum import unique from typing import Optional from django.db import models from django.db.models import F, ForeignKey +from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ from django.conf import settings from django.utils.text import slugify @@ -186,7 +187,15 @@ class Hadis(LowercaseSlugMixin, models.Model): title = self.title[0]['text'] if self.title else f"Hadis {self.number}" return f"{self.number} - {title}" + def clean(self): + super().clean() + if self.category and self.category.children.exists(): + raise ValidationError( + _('Cannot assign hadith to a category that has subcategories. Only leaf categories can contain hadiths.') + ) + def save(self, *args, **kwargs): + self.clean() # Generate slug if not already set or if slug is empty if not self.slug or (isinstance(self.slug, str) and self.slug.strip() == ''): title_text = None diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index 95b5a67..5f314ee 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/apps/hadis/serializers/serializers_admin.py @@ -1,5 +1,6 @@ from rest_framework import serializers from django.core.files.uploadedfile import SimpleUploadedFile +from django.utils.translation import gettext_lazy as _ from utils.image_compression import maybe_compress_uploaded_file from utils import absolute_https_url @@ -41,11 +42,30 @@ class AdminHadisCategorySerializer(serializers.ModelSerializer): sect_type = serializers.CharField(source='sect.sect_type', read_only=True) title = LocalizedField() description = LocalizedField() + has_children = serializers.SerializerMethodField() + is_leaf = serializers.SerializerMethodField() + has_hadith = serializers.SerializerMethodField() + + def get_has_children(self, obj): + if hasattr(obj, "annotated_has_children"): + return bool(obj.annotated_has_children) + return obj.children.exists() + + def get_is_leaf(self, obj): + if hasattr(obj, "annotated_has_children"): + return not bool(obj.annotated_has_children) + return not obj.children.exists() + + def get_has_hadith(self, obj): + if hasattr(obj, "annotated_has_hadith"): + return bool(obj.annotated_has_hadith) + return obj.hadis_set.exists() class Meta: model = HadisCategory fields = [ "id", + "parent", "title", "slug", "source_type", @@ -53,6 +73,9 @@ class AdminHadisCategorySerializer(serializers.ModelSerializer): "sect", "sect_type", "description", + "has_children", + "is_leaf", + "has_hadith", ] @@ -240,6 +263,13 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer): from apps.bookmark.models.bookmark import Bookmark return Bookmark.objects.filter(service=Bookmark.ServiceChoices.HADITH, content_id=obj.id, status=True).count() + def validate_category(self, value): + if value and value.children.exists(): + raise serializers.ValidationError( + _("Cannot assign a hadith/item to a category that has subcategories. Only leaf categories can contain hadiths.") + ) + return value + def get_collections_detail(self, obj): collections = [item.collection for item in obj.collection_items.all()] return AdminHadisCollectionSerializer(collections, many=True).data @@ -607,6 +637,8 @@ class AdminHadisCategoryArchiveSerializer(serializers.ModelSerializer): xmind_file = AbsoluteFileField(required=False, allow_null=True) remove_xmind_file = serializers.BooleanField(write_only=True, required=False, default=False) has_hadith = serializers.SerializerMethodField() + has_children = serializers.SerializerMethodField() + is_leaf = serializers.SerializerMethodField() parent = serializers.PrimaryKeyRelatedField( queryset=HadisCategory.objects.all(), @@ -619,8 +651,27 @@ class AdminHadisCategoryArchiveSerializer(serializers.ModelSerializer): ) def get_has_hadith(self, obj): + if hasattr(obj, "annotated_has_hadith"): + return bool(obj.annotated_has_hadith) return obj.hadis_set.exists() + def get_has_children(self, obj): + if hasattr(obj, "annotated_has_children"): + return bool(obj.annotated_has_children) + return obj.children.exists() + + def get_is_leaf(self, obj): + if hasattr(obj, "annotated_has_children"): + return not bool(obj.annotated_has_children) + return not obj.children.exists() + + def validate_parent(self, value): + if value and value.hadis_set.exists(): + raise serializers.ValidationError( + _("Cannot set parent to a category that already contains hadiths/content items.") + ) + return value + class Meta: model = HadisCategory fields = [ @@ -639,6 +690,8 @@ class AdminHadisCategoryArchiveSerializer(serializers.ModelSerializer): "share_link", "xmind_share_link", "has_hadith", + "has_children", + "is_leaf", ] read_only_fields = ["id", "share_link", "xmind_share_link"] diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index c48d314..49baf2d 100644 --- a/apps/hadis/views_admin.py +++ b/apps/hadis/views_admin.py @@ -1,5 +1,4 @@ -from django.db.models import Q -from django.db.models import Q +from django.db.models import Q, Exists, OuterRef from rest_framework.authentication import TokenAuthentication from rest_framework.decorators import action from rest_framework.parsers import FormParser, JSONParser, MultiPartParser @@ -55,7 +54,12 @@ class AdminHadisCategoryViewSet(ReadOnlyModelViewSet): pagination_class = None def get_queryset(self): - queryset = HadisCategory.objects.all() + hadis_subquery = Hadis.objects.filter(category=OuterRef("pk")) + children_subquery = HadisCategory.objects.filter(parent=OuterRef("pk")) + queryset = HadisCategory.objects.select_related("parent", "sect").annotate( + annotated_has_hadith=Exists(hadis_subquery), + annotated_has_children=Exists(children_subquery), + ) source_type = self.request.query_params.get("source_type") if source_type: queryset = queryset.filter(source_type=source_type) @@ -89,7 +93,12 @@ class DovodiHadisCategoryViewSet(ModelViewSet): parser_classes = (JSONParser, MultiPartParser, FormParser) def get_queryset(self): - queryset = HadisCategory.objects.select_related("parent", "sect") + hadis_subquery = Hadis.objects.filter(category=OuterRef("pk")) + children_subquery = HadisCategory.objects.filter(parent=OuterRef("pk")) + queryset = HadisCategory.objects.select_related("parent", "sect").annotate( + annotated_has_hadith=Exists(hadis_subquery), + annotated_has_children=Exists(children_subquery), + ) search_query = self.request.query_params.get("search") if search_query: queryset = queryset.filter(