From 0f56ebb4aa4960bb8ce3f766b933600afb60df7f Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Wed, 21 Oct 2026 12:25:45 +0330 Subject: [PATCH] feat(hadis): auto-populate title_narrator for hadith chains and non-hadith book authors - Auto-populate title_narrator using 1st narrator known_as (or full_name) for hadith arguments - Auto-populate title_narrator using reference book author known_as (or name) for non-hadith arguments - Hook into Hadis.save, AdminHadisDetailSerializer create/update, and AdminHadisTransmitterViewSet relations - Preserve manual entries when title_narrator is explicitly modified --- apps/hadis/models/hadis.py | 40 +++++++++++++++++++++ apps/hadis/serializers/serializers_admin.py | 6 ++++ apps/hadis/views_admin.py | 14 ++++++++ 3 files changed, 60 insertions(+) diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index db4308c..14d7d1a 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -218,6 +218,42 @@ class Hadis(LowercaseSlugMixin, models.Model): raise ValidationError( _('Cannot assign hadith to a category that has subcategories. Only leaf categories can contain hadiths.') ) + def auto_populate_title_narrator(self): + """ + Auto-fills self.title_narrator if empty: + - For hadith (or items with transmitters): use the 'known_as' (fallback 'full_name') of the first narrator in the chain. + - For others (or non-hadith/quote/quran/fatwa/history): use the 'known_as' (fallback 'name') of the reference book's author. + """ + if self.title_narrator and isinstance(self.title_narrator, list): + has_val = any( + isinstance(item, dict) and item.get('text', '').strip() + for item in self.title_narrator + ) + if has_val: + return False + + # 1. Check first transmitter in chain + if self.pk: + first_trans_rel = self.transmitters.filter(transmitter__isnull=False).order_by('order', 'id').first() + if first_trans_rel and first_trans_rel.transmitter: + tr = first_trans_rel.transmitter + val = tr.known_as if (tr.known_as and isinstance(tr.known_as, list) and any(isinstance(x, dict) and x.get('text', '').strip() for x in tr.known_as)) else tr.full_name + if val and isinstance(val, list): + self.title_narrator = val + return True + + # 2. Check book reference author + if self.pk: + ref = self.references.filter(book_reference__isnull=False, book_reference__author__isnull=False).first() + if ref and ref.book_reference and ref.book_reference.author: + author = ref.book_reference.author + val = author.known_as if (author.known_as and isinstance(author.known_as, list) and any(isinstance(x, dict) and x.get('text', '').strip() for x in author.known_as)) else author.name + if val and isinstance(val, list): + self.title_narrator = val + return True + + return False + def save(self, *args, **kwargs): self.clean() @@ -249,6 +285,10 @@ class Hadis(LowercaseSlugMixin, models.Model): old_instance.translation != self.translation): self.embedded_in = [] # Reset! + # Auto-fill title_narrator if empty + if not self.title_narrator or (isinstance(self.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in self.title_narrator)): + self.auto_populate_title_narrator() + # Populate normalized_text for fast and accurate Arabic/Persian search from utils.text_normalizer import build_search_blob self.normalized_text = build_search_blob( diff --git a/apps/hadis/serializers/serializers_admin.py b/apps/hadis/serializers/serializers_admin.py index df68777..cca5afb 100644 --- a/apps/hadis/serializers/serializers_admin.py +++ b/apps/hadis/serializers/serializers_admin.py @@ -718,6 +718,9 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer): pages=pages or "", hadith_number=hadith_number or "" ) + if not hadis.title_narrator or (isinstance(hadis.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in hadis.title_narrator)): + if hadis.auto_populate_title_narrator(): + hadis.save(update_fields=['title_narrator', 'normalized_text']) return hadis def update(self, instance, validated_data): @@ -777,6 +780,9 @@ class AdminHadisDetailSerializer(serializers.ModelSerializer): if hadith_number_provided: ref.hadith_number = hadith_number or "" ref.save() + if not hadis.title_narrator or (isinstance(hadis.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in hadis.title_narrator)): + if hadis.auto_populate_title_narrator(): + hadis.save(update_fields=['title_narrator', 'normalized_text']) return hadis diff --git a/apps/hadis/views_admin.py b/apps/hadis/views_admin.py index ba645d7..ca5ac7b 100644 --- a/apps/hadis/views_admin.py +++ b/apps/hadis/views_admin.py @@ -2340,6 +2340,20 @@ class AdminCategoryQuranVerseViewSet(ModelViewSet): class AdminHadisTransmitterViewSet(ModelViewSet): + def perform_create(self, serializer): + instance = serializer.save() + hadis = instance.hadis + if hadis and (not hadis.title_narrator or (isinstance(hadis.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in hadis.title_narrator))): + if hadis.auto_populate_title_narrator(): + hadis.save(update_fields=['title_narrator', 'normalized_text']) + + def perform_update(self, serializer): + instance = serializer.save() + hadis = instance.hadis + if hadis and (not hadis.title_narrator or (isinstance(hadis.title_narrator, list) and not any(isinstance(item, dict) and item.get('text', '').strip() for item in hadis.title_narrator))): + if hadis.auto_populate_title_narrator(): + hadis.save(update_fields=['title_narrator', 'normalized_text']) + serializer_class = AdminHadisTransmitterSerializer permission_classes = [IsAuthenticated, IsSuperAdmin]