From 5ae61a2b10fac6c142eb287278d278405ffeb3d7 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Tue, 20 Oct 2026 15:48:54 +0330 Subject: [PATCH] feat(transmitters): add Madhhab model, relational field, and admin endpoints - Introduce Madhhab model with multilingual title, description, and slug - Link madhhabs relation (ManyToMany) to Transmitters - Add migration 0062 to create table and populate from existing madhab JSON data - Register DovodiMadhhabViewSet and admin routes in urls.py - Update RELATIONAL_MIGRATION_ROADMAP.md for madhhab migration --- RELATIONAL_MIGRATION_ROADMAP.md | 49 ++++---- .../migrations/0062_madhhab_and_relations.py | 112 ++++++++++++++++++ apps/hadis/models/transmitter.py | 47 ++++++++ apps/hadis/serializers/serializers_admin.py | 58 ++++++++- apps/hadis/urls.py | 5 +- apps/hadis/views_admin.py | 20 ++++ 6 files changed, 264 insertions(+), 27 deletions(-) create mode 100644 apps/hadis/migrations/0062_madhhab_and_relations.py diff --git a/RELATIONAL_MIGRATION_ROADMAP.md b/RELATIONAL_MIGRATION_ROADMAP.md index 6219a9d..825d359 100644 --- a/RELATIONAL_MIGRATION_ROADMAP.md +++ b/RELATIONAL_MIGRATION_ROADMAP.md @@ -9,8 +9,8 @@ - [x] **فاز ۱: کنیه‌ها (`Kunya` / `kunyas`)** — رابطه چندبه‌چند (M2M) *(تکمیل شده)* - [x] **فاز ۲: ویژگی‌ها (`TransmitterFeature` / `features`)** — رابطه چندبه‌چند (M2M) *(تکمیل شده)* - [x] **فاز ۳: وضعیت صحابی بودن (`SahabiStatus` / `sahabi_status`)** — رابطه تک‌به‌چند (ForeignKey / M2O) *(تکمیل شده)* -- [ ] **فاز ۴: تفکیک و اتصال رتبه‌بندی و وثاقت راوی و مؤلف (`NarratorRating` و `reliability_levels`)** — رابطه ForeignKey / M2O -- [ ] **فاز ۵: مذاهب راویان (`Madhhab` / `madhabs`)** — رابطه چندبه‌چند (M2M) +- [—] **فاز ۴: تفکیک و اتصال رتبه‌بندی و وثاقت راوی و مؤلف (`NarratorRating` و `reliability_levels`)** — *(صرف‌نظر شد / اسکیپ طبق تصمیم: سیستم فعلی نظرات رجالی پاسخگو است)* +- [x] **فاز ۵: مذاهب راویان (`Madhhab` / `madhabs`)** — رابطه چندبه‌چند (M2M) *(تکمیل شده)* - [ ] **فاز ۶: داینامیک‌سازی انواع نسبت‌های خویشاوندی (`RelationType` / `relatives`)** — رابطه ForeignKey در `TransmitterRelative` - [ ] **فاز ۷: مکان‌های چندگانه راوی (`narrators_locations`)** — اتصال چندبه‌چند با ترتیب (`sort_order`) و نوع (`residence` / `death`) @@ -104,33 +104,34 @@ --- -### فاز ۴: تفکیک و اتصال رتبه‌بندی و وثاقت راوی و مؤلف (`NarratorRating` و `reliability_levels`) 🔲 -* **وضعیت:** `در انتظار اجرا` -* **نوع رابطه طبق داکیومنت کارفرما:** **ForeignKey (M2O) برای هر دو فیلد** - > در معماری کارفرما دو مفهوم متمایز وجود دارد: - > ۱. `reliability_levels` (۱۱ سطح کلی اعتبار: ثقه، صدوق، ضعیف، کذاب و...) - > ۲. `narrator_ratings` (عبارت دقیق ارزیابی رجالی: ثقة ثبت، صدوق له أوهام و...) -* **مراحل پیاده‌سازی بک‌اند:** - 1. بررسی و یکپارچه‌سازی مدل `TransmitterReliability` فعلی با ۱۱ سطح استاندارد کارفرما. - 2. استفاده از مدل `BookAuthorRutba` یا ایجاد مدل عمومی `NarratorRating` (با ترجمه چندزبانه) و اتصال آن به عنوان `rating` هم به راوی (`Transmitters.narrator_rating`) و هم به مؤلف (`BookAuthor.rutba_status`). - 3. تبدیل فیلد `BookAuthor.scholarity` به کلید خارجی به `TransmitterReliability`. - 4. سریالایزرها و مایگریشن داده‌ها. -* **مراحل پیاده‌سازی داشبورد:** - 1. نمایش هر دو دراپ‌داون «سطح وثاقت کلی» و «عبارت ارزیابی نهایی» در فرم راوی و مؤلف. +### فاز ۴: تفکیک و اتصال رتبه‌بندی و وثاقت راوی و مؤلف (`NarratorRating` و `reliability_levels`) ⏭️ +* **وضعیت:** `صرف‌نظر شد (Skipped)` +* **علت و تصمیم اتخاذشده:** + - مفهوم ارزیابی‌های تفصیلی رجالی (`narrator_ratings`) در حال حاضر به‌صورت کامل، غنی و پیشرفته در سیستم نظرات رجالی (`TransmitterOpinion` و `ScholarlyOpinion`) پیاده‌سازی شده و نیازی به تغییر یا مدل تکراری ندارد. + - برای فیلدهای وثاقت و رتبه راوی و مؤلف (`TransmitterReliability` و `BookAuthorRutba`) دست ادمین باز گذاشته شده تا بر اساس نیاز خود رتبه‌ها را مدیریت و انتخاب کنند و نیازی به ادغام جبری آنها نیست. + - بنابراین این فاز طبق تصمیم اسکیپ شده و منطق فعلی بدون تغییر حفظ گردید. --- -### فاز ۵: مذاهب راویان (`Madhhab` / `madhabs`) 🔲 -* **وضعیت:** `در انتظار اجرا` +### فاز ۵: مذاهب راویان (`Madhhab` / `madhabs`) ✅ +* **وضعیت:** `تکمیل شده` * **نوع رابطه طبق داکیومنت کارفرما:** **Many-to-Many (M2M)** > کارفرما مذهب را به عنوان یک کالکشن چندزبانه با رابطه M2M در نظر گرفته است (تا بتوان مذاهب گوناگون، انشعابات، یا نظرات متعدد درباره مذهب راوی را ثبت کرد). -* **مراحل پیاده‌سازی بک‌اند:** - 1. ساخت مدل `Madhhab` با فیلد چندزبانه `name`/`title` و `slug`. - 2. ایجاد رابطه M2M روی `Transmitters.madhhabs = models.ManyToManyField('Madhhab', blank=True, related_name='transmitters')`. - 3. حفظ فیلد `madhhab` قبلی (`shia`/`sunni`) و سینک مقدار پیش‌فرض با آن. - 4. ساخت Seed داده‌های مذاهب و ثبت API ادمین. -* **مراحل پیاده‌سازی داشبورد:** - 1. تبدیل سلکتور دو گزینه‌ای به دراپ‌داون داینامیک چندزبانه. +* **کارهای انجام‌شده:** + 1. **مدل `Madhhab`:** ایجاد در `transmitter.py` با فیلدهای `title` (چندزبانه به ۴ زبان: fa, ar, en, ru)، `slug` (یکتا و خودکار)، متد هلپر `get_title()` و تایم‌استمپ‌ها. + 2. **اتصال رابطه چندبه‌چند به `Transmitters`:** افزودن فیلد `madhhabs = models.ManyToManyField('hadis.Madhhab', blank=True, related_name='transmitters')` به همراه حفظ کامل فیلد متنی قدیمی `madhhab` جهت سازگاری به عقب (backward compatibility). + 3. **مایگریشن و Seeding دیتابیس (۰۶۲):** + - ساخت جدول و Seed کردن ۵ مذهب استاندارد داکیومنت کارفرما (شیعه، اهل سنت، زیدی، واقفی، فتحی). + - انتقال خودکار دیتای تمام ۷۹ راوی موجود در دیتابیس به رکوردهای متناظر M2M (۳۴ راوی شیعه و ۴۵ راوی سنی). + 4. **سریالایزرها و APIها:** + - ساخت `AdminMadhhabSerializer` و `DovodiMadhhabViewSet`. + - ثبت روت‌های `/api/hadis/admin/dovodi-madhhabs/` و `/api/hadis/admin/madhhabs/`. + - پشتیبانی کامل در `AdminTransmitterListSerializer` و `AdminTransmitterDetailSerializer` با ارسال فیلدهای `madhhabs` (لیست شناسه‌ها) و `madhhabs_detail` و همگام‌سازی دوطرفه خودکار با فیلد استرینگ قدیمی `madhhab` در توابع `create` و `update`. + 5. **داشبورد (فرانت‌اند):** + - ساخت کامپوننت ماژولار چندانتخابی `MadhhabSelect.tsx` (با کش ماژولار در سطح حافظه، قابلیت افزودن سریع و ریموو بج‌های مذهب، سرچ و فیلتر Popover و مودال ایجاد مذهب جدید با هدر چندزبانه، نمایش کامل توضیحات بدون برش و فوتر چسبان). + - جایگزینی سلکتور استاتیک قبلی با `MadhhabSelect` در سایدبار فرم راوی (`TransmitterSidebar.tsx` و `TransmitterFormPage.tsx`) با همگام‌سازی داینامیک. + - افزودن تب مدیریت «مذاهب»، ستون‌های جدول و دیالوگ مدیریت کامل (`MadhhabDialog`) در صفحه آرشیو راویان (`TransmittersArchivePage.tsx`). + - بیلد نهایی پروژه فرانت‌اند با موفقیت انجام شد (بدون هیچ خطای TypeScript یا کامپایل). --- diff --git a/apps/hadis/migrations/0062_madhhab_and_relations.py b/apps/hadis/migrations/0062_madhhab_and_relations.py new file mode 100644 index 0000000..9c099a2 --- /dev/null +++ b/apps/hadis/migrations/0062_madhhab_and_relations.py @@ -0,0 +1,112 @@ +# Generated by Django 4.2.30 on 2026-10-20 15:23 + +from django.db import migrations, models + + +MADHHAB_DATA = [ + { + "slug": "shia", + "title": [ + {"language_code": "ar", "text": "شيعي", "title": "شيعي"}, + {"language_code": "fa", "text": "شیعه", "title": "شیعه"}, + {"language_code": "en", "text": "Shia", "title": "Shia"}, + {"language_code": "ru", "text": "Шиит", "title": "Шиит"}, + ], + }, + { + "slug": "sunni", + "title": [ + {"language_code": "ar", "text": "سني", "title": "سني"}, + {"language_code": "fa", "text": "سنی", "title": "سنی"}, + {"language_code": "en", "text": "Sunni", "title": "Sunni"}, + {"language_code": "ru", "text": "Суннит", "title": "Суннит"}, + ], + }, + { + "slug": "zaydi", + "title": [ + {"language_code": "ar", "text": "زيدي", "title": "زيدي"}, + {"language_code": "fa", "text": "زیدی", "title": "زیدی"}, + {"language_code": "en", "text": "Zaydi", "title": "Zaydi"}, + {"language_code": "ru", "text": "Зейдит", "title": "Зейдит"}, + ], + }, + { + "slug": "waqifi", + "title": [ + {"language_code": "ar", "text": "واقفي", "title": "واقفي"}, + {"language_code": "fa", "text": "واقفی", "title": "واقفی"}, + {"language_code": "en", "text": "Waqifi", "title": "Waqifi"}, + {"language_code": "ru", "text": "Вакифит", "title": "Вакифит"}, + ], + }, + { + "slug": "fathi", + "title": [ + {"language_code": "ar", "text": "فطحي", "title": "فطحي"}, + {"language_code": "fa", "text": "فطحی", "title": "فطحی"}, + {"language_code": "en", "text": "Fathi", "title": "Fathi"}, + {"language_code": "ru", "text": "Фатхит", "title": "Фатхиت"}, + ], + }, +] + + +def seed_and_migrate_madhhabs(apps, schema_editor): + Madhhab = apps.get_model('hadis', 'Madhhab') + Transmitters = apps.get_model('hadis', 'Transmitters') + + madhhab_map = {} + for item in MADHHAB_DATA: + obj, _ = Madhhab.objects.get_or_create( + slug=item["slug"], + defaults={"title": item["title"]}, + ) + madhhab_map[item["slug"]] = obj + + shia_obj = madhhab_map.get("shia") + sunni_obj = madhhab_map.get("sunni") + + if shia_obj: + for t in Transmitters.objects.filter(madhhab='shia'): + t.madhhabs.add(shia_obj) + + if sunni_obj: + for t in Transmitters.objects.filter(madhhab='sunni'): + t.madhhabs.add(sunni_obj) + + +def reverse_seed(apps, schema_editor): + pass + + +class Migration(migrations.Migration): + + dependencies = [ + ('hadis', '0061_sahabi_status_and_relation'), + ] + + operations = [ + migrations.CreateModel( + name='Madhhab', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.JSONField(default=list, verbose_name='Title')), + ('slug', models.SlugField(allow_unicode=True, blank=True, max_length=255, unique=True, verbose_name='slug')), + ('description', models.JSONField(blank=True, default=list, verbose_name='Description')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), + ], + options={ + 'verbose_name': 'Madhhab', + 'verbose_name_plural': 'Madhhabs', + 'ordering': ['slug', 'id'], + }, + ), + migrations.AddField( + model_name='transmitters', + name='madhhabs', + field=models.ManyToManyField(blank=True, related_name='transmitters', to='hadis.madhhab', verbose_name='Madhhabs (M2M)'), + ), + migrations.RunPython(seed_and_migrate_madhhabs, reverse_seed), + ] diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index 952eb2c..61271ee 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -289,6 +289,47 @@ class SahabiStatus(LowercaseSlugMixin, models.Model): return None + + +class Madhhab(LowercaseSlugMixin, models.Model): + slug_source_field = 'title' + + title = models.JSONField(default=list, verbose_name=_('Title')) + slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True, unique=True, allow_unicode=True) + description = models.JSONField(default=list, blank=True, verbose_name=_('Description')) + created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('Created At')) + updated_at = models.DateTimeField(auto_now=True, verbose_name=_('Updated At')) + + class Meta: + verbose_name = _('Madhhab') + verbose_name_plural = _('Madhhabs') + ordering = ['slug', 'id'] + + def __str__(self): + if not self.title or not isinstance(self.title, list): + return str(self.id) + for tr in self.title: + if isinstance(tr, dict) and tr.get('language_code') == 'en': + return tr.get('text', '') or tr.get('title', '') + first = self.title[0] + if isinstance(first, dict): + return first.get('text', '') or first.get('title', '') + return str(self.id) + + def get_title(self, lang: str = 'en') -> Optional[str]: + if not self.title or not isinstance(self.title, list): + return None + for tr in self.title: + if isinstance(tr, dict) and tr.get('language_code') == lang: + return tr.get('text', '') or tr.get('title', '') + for tr in self.title: + if isinstance(tr, dict) and tr.get('language_code') == 'en': + return tr.get('text', '') or tr.get('title', '') + if isinstance(self.title[0], dict): + return self.title[0].get('text', '') or self.title[0].get('title', '') + return None + + class Transmitters(LowercaseSlugMixin, models.Model): slug_source_field = 'full_name' # class ReliabilityLevel(models.TextChoices): @@ -406,6 +447,12 @@ class Transmitters(LowercaseSlugMixin, models.Model): default=MadhhabChoices.SHIA, verbose_name=_('Madhhab/School of Thought') ) + madhhabs = models.ManyToManyField( + 'hadis.Madhhab', + blank=True, + related_name='transmitters', + verbose_name=_('Madhhabs (M2M)') + ) # Presence in Famous Collections in_sahih_muslim = models.BooleanField( diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index 2aeabb3..1fbbabc 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/apps/hadis/serializers/serializers_admin.py @@ -10,7 +10,7 @@ from ..models import ( Hadis, HadisCategory, HadisStatus, HadisTag, HadisCorrection, HadisInterpretation, HadisCollection, HadisSect, HadisInCollection, HadisTransmitter, HadisReference, ReferenceImage, BookReference, BookReferenceImage, BookReferenceDocument, BookAuthor, BookAuthorRutba, BookAttribute, BookSubjectArea, BookType, BookTag, BookEdition, BookVolume, BookResearcher, BookEditor, Transmitters, TransmitterReliability, NarratorLayer, OpinionStatus, ScholarlyOpinion, TransmitterOpinion, TransmitterOriginalText, - City, Kunya, TransmitterFeature, SahabiStatus, YearValue, NarratorDate, + City, Kunya, TransmitterFeature, SahabiStatus, Madhhab, YearValue, NarratorDate, CorrectionReference, CorrectionReferenceImage, InterpretationReference, InterpretationReferenceImage, OriginalTextReference, OriginalTextReferenceImage, ContentRelease @@ -2058,6 +2058,18 @@ class AdminSahabiStatusSerializer(serializers.ModelSerializer): read_only_fields = ["id", "slug", "created_at", "updated_at"] + + +class AdminMadhhabSerializer(serializers.ModelSerializer): + title = serializers.JSONField() + description = serializers.JSONField(required=False, default=list) + + class Meta: + model = Madhhab + fields = ["id", "title", "slug", "description", "created_at", "updated_at"] + read_only_fields = ["id", "slug", "created_at", "updated_at"] + + def _build_admin_date_preview(obj, date_type: str, raw_val: str): if not raw_val or not str(raw_val).strip(): return None @@ -2073,6 +2085,7 @@ class AdminTransmitterListSerializer(serializers.ModelSerializer): kunyas_detail = AdminKunyaSerializer(source="kunyas", many=True, read_only=True) features_detail = AdminTransmitterFeatureSerializer(source="features_list", many=True, read_only=True) sahabi_status_detail = AdminSahabiStatusSerializer(source="sahabi_status", read_only=True) + madhhabs_detail = AdminMadhhabSerializer(source="madhhabs", many=True, read_only=True) full_name = serializers.JSONField() known_as = serializers.JSONField() nickname = serializers.JSONField() @@ -2104,6 +2117,7 @@ class AdminTransmitterListSerializer(serializers.ModelSerializer): "reliability", "reliability_detail", "madhhab", + "madhhabs_detail", "generation", "thumbnail", "share_link", @@ -2145,6 +2159,12 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer): allow_null=True, ) sahabi_status_detail = AdminSahabiStatusSerializer(source="sahabi_status", read_only=True) + madhhabs = serializers.PrimaryKeyRelatedField( + queryset=Madhhab.objects.all(), + many=True, + required=False, + ) + madhhabs_detail = AdminMadhhabSerializer(source="madhhabs", many=True, read_only=True) companion_type = serializers.JSONField(required=False, default=list) known_as = serializers.JSONField() nickname = serializers.JSONField() @@ -2203,7 +2223,7 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer): "kunyas_detail", "features", "features_list", - "features_detail", "sahabi_status_detail", + "features_detail", "sahabi_status_detail", "madhhabs_detail", "sahabi_status", "sahabi_status_detail", "companion_type", @@ -2224,6 +2244,8 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer): "reliability", "reliability_detail", "madhhab", + "madhhabs", + "madhhabs_detail", "in_sahih_muslim", "in_sahih_bukhari", "origin", @@ -2358,6 +2380,14 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer): except ValueError: pass + if "madhhabs" in data: + val = data["madhhabs"] + if isinstance(val, str): + try: + data["madhhabs"] = json.loads(val) + except ValueError: + pass + return super().to_internal_value(data) def validate(self, attrs): @@ -2386,6 +2416,7 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer): sons_ids = validated_data.pop("sons_ids", None) kunyas_objs = validated_data.pop("kunyas", None) features_objs = validated_data.pop("features_list", None) + madhhabs_objs = validated_data.pop("madhhabs", None) instance = super().create(validated_data) if sons_ids is not None: Transmitters.objects.filter(id__in=sons_ids).update(father=instance) @@ -2411,6 +2442,17 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer): st = validated_data.get("sahabi_status") instance.companion_type = st.title if st else [] instance.save(update_fields=['companion_type']) + if madhhabs_objs is not None: + instance.madhhabs.set(madhhabs_objs) + for m in madhhabs_objs: + if m.slug == "sunni": + instance.madhhab = "sunni" + instance.save(update_fields=["madhhab"]) + break + elif m.slug == "shia": + instance.madhhab = "shia" + instance.save(update_fields=["madhhab"]) + break return instance def update(self, instance, validated_data): @@ -2418,6 +2460,7 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer): sons_ids = validated_data.pop("sons_ids", None) kunyas_objs = validated_data.pop("kunyas", None) features_objs = validated_data.pop("features_list", None) + madhhabs_objs = validated_data.pop("madhhabs", None) if remove_thumbnail and instance.thumbnail: instance.thumbnail.delete(save=False) instance.thumbnail = None @@ -2447,6 +2490,17 @@ class AdminTransmitterDetailSerializer(serializers.ModelSerializer): st = validated_data.get("sahabi_status") instance.companion_type = st.title if st else [] instance.save(update_fields=['companion_type']) + if madhhabs_objs is not None: + instance.madhhabs.set(madhhabs_objs) + for m in madhhabs_objs: + if m.slug == "sunni": + instance.madhhab = "sunni" + instance.save(update_fields=["madhhab"]) + break + elif m.slug == "shia": + instance.madhhab = "shia" + instance.save(update_fields=["madhhab"]) + break return instance diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index 3c321c0..6c3e97a 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -47,7 +47,8 @@ from .views_admin import ( DovodiCityViewSet, DovodiKunyaViewSet, DovodiTransmitterFeatureViewSet, - DovodiSahabiStatusViewSet + DovodiSahabiStatusViewSet, + DovodiMadhhabViewSet ) @@ -152,6 +153,8 @@ admin_router.register(r'dovodi-transmitter-features', DovodiTransmitterFeatureVi admin_router.register(r'transmitter-features', DovodiTransmitterFeatureViewSet, basename='admin-transmitter-features') admin_router.register(r'dovodi-sahabi-statuses', DovodiSahabiStatusViewSet, basename='dovodi-sahabi-statuses') admin_router.register(r'sahabi-statuses', DovodiSahabiStatusViewSet, basename='admin-sahabi-statuses') +admin_router.register(r'dovodi-madhhabs', DovodiMadhhabViewSet, basename='dovodi-madhhabs') +admin_router.register(r'madhhabs', DovodiMadhhabViewSet, basename='admin-madhhabs') diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index 122d96c..0f2cd4d 100644 --- a/apps/hadis/views_admin.py +++ b/apps/hadis/views_admin.py @@ -41,6 +41,7 @@ from .models import ( Kunya, TransmitterFeature, SahabiStatus, + Madhhab, ) @@ -131,6 +132,7 @@ from .serializers.serializers_admin import ( AdminKunyaSerializer, AdminTransmitterFeatureSerializer, AdminSahabiStatusSerializer, + AdminMadhhabSerializer, ) @@ -2957,3 +2959,21 @@ class DovodiSahabiStatusViewSet(ModelViewSet): | Q(slug__icontains=search_query) ) return queryset + + +class DovodiMadhhabViewSet(ModelViewSet): + serializer_class = AdminMadhhabSerializer + permission_classes = [IsAuthenticated, IsSuperAdmin] + authentication_classes = [TokenAuthentication] + pagination_class = OptionalPagination + parser_classes = (JSONParser, MultiPartParser, FormParser) + + def get_queryset(self): + queryset = Madhhab.objects.all().order_by("slug", "id") + search_query = self.request.query_params.get("search") + if search_query: + queryset = queryset.filter( + Q(title__icontains=search_query) + | Q(slug__icontains=search_query) + ) + return queryset