diff --git a/apps/hadis/management/commands/update_transmitter_names.py b/apps/hadis/management/commands/update_transmitter_names.py new file mode 100644 index 0000000..61135ff --- /dev/null +++ b/apps/hadis/management/commands/update_transmitter_names.py @@ -0,0 +1,23 @@ +from django.core.management.base import BaseCommand +from scripts.update_transmitter_names import update_transmitters + + +class Command(BaseCommand): + help = "Update transmitters with 80+ unique multilingual names across English, Arabic, and Russian." + + def add_arguments(self, parser): + parser.add_argument( + "--dry-run", + action="store_true", + help="Simulate the update without committing changes to the database.", + ) + + def handle(self, *args, **options): + dry_run = options.get("dry_run", False) + if dry_run: + self.stdout.write(self.style.WARNING("Running in DRY-RUN mode...")) + else: + self.stdout.write(self.style.NOTICE("Starting transmitter name updates...")) + + update_transmitters(dry_run=dry_run) + self.stdout.write(self.style.SUCCESS("Finished transmitter updates.")) diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index 8426820..bbb006c 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -185,6 +185,7 @@ class HadisSyncSerializer(serializers.ModelSerializer): reliability_block = { 'id': t.reliability.id, 'title': get_localized_text(t.reliability.title, request), + 'slug': t.reliability.slug, 'color': t.reliability.color, 'main_color_code': t.reliability.main_color_code, } @@ -197,6 +198,7 @@ class HadisSyncSerializer(serializers.ModelSerializer): ut_rel_block = { 'id': ut.reliability.id, 'title': get_localized_text(ut.reliability.title, request), + 'slug': ut.reliability.slug, 'color': ut.reliability.color, 'main_color_code': ut.reliability.main_color_code, } @@ -426,6 +428,7 @@ class TransmitterSerializer(serializers.ModelSerializer): return { 'id': obj.reliability.id, 'title': get_localized_text(obj.reliability.title, self.context.get('request')), + 'slug': obj.reliability.slug, 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, } @@ -439,6 +442,7 @@ class TransmitterShortSerializer(serializers.ModelSerializer): arabic_known_as = ArabicLocalizedField(source='known_as') nickname = LocalizedField() arabic_nickname = ArabicLocalizedField(source='nickname') + reliability = serializers.SerializerMethodField() class Meta: model = Transmitters @@ -446,6 +450,18 @@ class TransmitterShortSerializer(serializers.ModelSerializer): 'id', 'full_name', 'arabic_full_name', 'slug','birth_year_hijri', 'death_year_hijri', "known_as", 'arabic_known_as', 'nickname', 'arabic_nickname', 'reliability' ] + + def get_reliability(self, obj): + if obj.reliability: + request = self.context.get('request') + return { + 'id': obj.reliability.id, + 'title': get_localized_text(obj.reliability.title, request), + 'slug': obj.reliability.slug, + 'color': obj.reliability.color, + 'main_color_code': obj.reliability.main_color_code, + } + return None class TransmitterOpinionSerializer(serializers.ModelSerializer): """ Serializer for TransmitterOpinions """ scholar_name = LocalizedField() @@ -706,6 +722,7 @@ class TransmitterDetailSerializer(serializers.ModelSerializer): return { 'id': obj.reliability.id, 'title': get_localized_text(obj.reliability.title, self.context.get('request')), + 'slug': obj.reliability.slug, 'color': obj.reliability.color, 'main_color_code': obj.reliability.main_color_code, } diff --git a/apps/hadis/views/transmitter.py b/apps/hadis/views/transmitter.py index 844caf4..4f4e0bd 100644 --- a/apps/hadis/views/transmitter.py +++ b/apps/hadis/views/transmitter.py @@ -242,11 +242,14 @@ def get_transmitter_filters(request): from ..models.transmitter import TransmitterReliability reliabilities = [] for reliability in TransmitterReliability.objects.all().order_by('id'): - # Get the English title for the slug if reliability.title: reliabilities.append({ + 'id': reliability.id, 'text': get_localized_text(reliability.title, request), - 'slug': reliability.slug + 'title': get_localized_text(reliability.title, request), + 'slug': reliability.slug, + 'color': reliability.color, + 'main_color_code': reliability.main_color_code, }) return { diff --git a/scripts/update_transmitter_names.py b/scripts/update_transmitter_names.py new file mode 100644 index 0000000..da42e44 --- /dev/null +++ b/scripts/update_transmitter_names.py @@ -0,0 +1,555 @@ +#!/usr/bin/env python3 +""" +Script to update all transmitters in the database with 80+ distinct, authentic +narrator names across English (en), Arabic (ar), and Russian (ru). + +Strictly conforms to the project's multilingual JSON structure: +[ + {"language_code": "en", "text": "..."}, + {"language_code": "ar", "text": "..."}, + {"language_code": "ru", "text": "..."} +] +and regenerates unique, clean slugs for each transmitter. +""" + +import os +import sys +import argparse +from pathlib import Path + +# Force UTF-8 encoding for console output on Windows +if hasattr(sys.stdout, "reconfigure"): + sys.stdout.reconfigure(encoding="utf-8") +if hasattr(sys.stderr, "reconfigure"): + sys.stderr.reconfigure(encoding="utf-8") + +BASE_DIR = Path(__file__).resolve().parent.parent +sys.path.insert(0, str(BASE_DIR)) +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.base") + +import django +django.setup() + +from django.db import transaction +from django.utils.text import slugify +from apps.hadis.models import Transmitters + +# 90 Authentic, prominent Hadith narrators and scholars +TRANSMITTER_NAMES = [ + { + "en": "Zurarah ibn A'yan", + "ar": "زرارة بن أعين", + "ru": "Зурара ибн Аян", + }, + { + "en": "Muhammad ibn Muslim", + "ar": "محمد بن مسلم", + "ru": "Мухаммад ибн Муслим", + }, + { + "en": "Abu Basir al-Asadi", + "ar": "أبو بصير الأسدي", + "ru": "Абу Басир аль-Асади", + }, + { + "en": "Jamil ibn Darraj", + "ar": "جميل بن دراج", + "ru": "Джамиль ибн Даррадж", + }, + { + "en": "Burayd ibn Mu'awiyah al-Ijli", + "ar": "بريد بن معاوية العجلي", + "ru": "Бурайд ибн Муавия аль-Иджли", + }, + { + "en": "Al-Fudayl ibn Yasar", + "ar": "الفضيل بن يسار", + "ru": "Аль-Фудайль ибн Йасар", + }, + { + "en": "Aban ibn Taghlib", + "ar": "أبان بن تغلب", + "ru": "Абан ибн Таглиб", + }, + { + "en": "Hisham ibn al-Hakam", + "ar": "هشام بن الحكم", + "ru": "Хишам ибн аль-Хакам", + }, + { + "en": "Hisham ibn Salim al-Jawaliqi", + "ar": "هشام بن سالم الجواليقي", + "ru": "Хишам ибн Салим аль-Джавалики", + }, + { + "en": "Yunus ibn Abd al-Rahman", + "ar": "يونس بن عبد الرحمن", + "ru": "Йунус ибн Абд ар-Рахман", + }, + { + "en": "Safwan ibn Yahya", + "ar": "صفوان بن يحيى", + "ru": "Сафван ибн Йахья", + }, + { + "en": "Muhammad ibn Abi Umayr", + "ar": "محمد بن أبي عمير", + "ru": "Мухаммад ибн Аби Умайр", + }, + { + "en": "Ahmad ibn Muhammad ibn Abi Nasr al-Bazanti", + "ar": "أحمد بن محمد بن أبي نصر البزنطي", + "ru": "Ахмад ибн Мухаммад аль-Базанти", + }, + { + "en": "Hammad ibn Isa", + "ar": "حماد بن عيسى", + "ru": "Хаммад ибн Иса", + }, + { + "en": "Hammad ibn Uthman", + "ar": "حماد بن عثمان", + "ru": "Хаммад ибн Усман", + }, + { + "en": "Mu'awiyah ibn Ammar", + "ar": "معاوية بن عمار", + "ru": "Муавия ибн Аммар", + }, + { + "en": "Ali ibn Yaqtin", + "ar": "علي بن يقطين", + "ru": "Али ибн Йактин", + }, + { + "en": "Zakariya ibn Adam al-Qummi", + "ar": "زكريا بن آدم القمي", + "ru": "Закария ибн Адам аль-Кумми", + }, + { + "en": "Al-Fadl ibn Shadhan", + "ar": "الفضل بن شاذان", + "ru": "Аль-Фадль ибн Шазан", + }, + { + "en": "Muhammad ibn Ya'qub al-Kulayni", + "ar": "محمد بن يعقوب الكليني", + "ru": "Мухаммад ибн Йакуб аль-Кулайни", + }, + { + "en": "Al-Shaykh al-Saduq (Muhammad ibn Ali ibn Babawayh)", + "ar": "الشيخ الصدوق (محمد بن علي بن بابويه)", + "ru": "Аш-Шейх ас-Садук", + }, + { + "en": "Al-Shaykh al-Mufid (Muhammad ibn Muhammad al-Nu'man)", + "ar": "الشيخ المفيد (محمد بن محمد النعمان)", + "ru": "Аш-Шейх аль-Муфид", + }, + { + "en": "Ali ibn al-Husayn al-Sharif al-Murtada", + "ar": "الشريف المرتضى (علي بن الحسين)", + "ru": "Аш-Шариф аль-Муртада", + }, + { + "en": "Al-Shaykh al-Tusi (Muhammad ibn al-Hasan)", + "ar": "الشيخ الطوسي (محمد بن الحسن)", + "ru": "Аш-Шейх ат-Туси", + }, + { + "en": "Al-Allamah al-Hilli (Al-Hasan ibn Yusuf)", + "ar": "العلامة الحلي (الحسن بن يوسف)", + "ru": "Аль-Аллама аль-Хилли", + }, + { + "en": "Al-Muhaqqiq al-Hilli (Ja'far ibn al-Hasan)", + "ar": "المحقق الحلي (جعفر بن الحسن)", + "ru": "Аль-Мухаккик аль-Хилли", + }, + { + "en": "Al-Shahid al-Awwal (Muhammad ibn Makki)", + "ar": "الشهيد الأول (محمد بن مكي)", + "ru": "Аш-Шахид аль-Авваль", + }, + { + "en": "Al-Shahid al-Thani (Zayn al-Din al-Jubba'i)", + "ar": "الشهيد الثاني (زين الدين الجبعي)", + "ru": "Аш-Шахид ас-Сани", + }, + { + "en": "Muhammad Baqir al-Majlisi", + "ar": "محمد باقر المجلسي", + "ru": "Мухаммад Бакир аль-Маджлиси", + }, + { + "en": "Al-Hurr al-Amili (Muhammad ibn al-Hasan)", + "ar": "الحر العاملي (محمد بن الحسن)", + "ru": "Аль-Хурр аль-Амили", + }, + { + "en": "Abu al-Qasim al-Khu'i", + "ar": "أبو القاسم الخوئي", + "ru": "Абу аль-Касим аль-Хои", + }, + { + "en": "Salman al-Farsi", + "ar": "سلمان الفارسي", + "ru": "Салман аль-Фарси", + }, + { + "en": "Abu Dharr al-Ghifari", + "ar": "أبو ذر الغفاري", + "ru": "Абу Зарр аль-Гифари", + }, + { + "en": "Al-Miqdad ibn al-Aswad", + "ar": "المقداد بن الأسود", + "ru": "Аль-Микдад ибн аль-Асвад", + }, + { + "en": "Ammar ibn Yasir", + "ar": "عمار بن ياسر", + "ru": "Аммар ибн Йасир", + }, + { + "en": "Jabir ibn Abd Allah al-Ansari", + "ar": "جابر بن عبد الله الأنصاري", + "ru": "Джабир ибн Абдуллах аль-Ансари", + }, + { + "en": "Hudhayfah ibn al-Yaman", + "ar": "حذيفة بن اليمان", + "ru": "Хузайфа ибн аль-Йаман", + }, + { + "en": "Malik al-Ashtar al-Nakha'i", + "ar": "مالك الأشتر النخعي", + "ru": "Малик аль-Аштар", + }, + { + "en": "Kumayl ibn Ziyad al-Nakha'i", + "ar": "كميل بن زياد النخعي", + "ru": "Кумайль ибн Зийад", + }, + { + "en": "Maytham al-Tammar", + "ar": "ميثم التمار", + "ru": "Майсам ат-Таммар", + }, + { + "en": "Sa'id ibn Jubayr", + "ar": "سعيد بن جبير", + "ru": "Саид ибн Джубайр", + }, + { + "en": "Tawus ibn Kaysan", + "ar": "طاووس بن كيسان", + "ru": "Тавус ибн Кайсан", + }, + { + "en": "Sulaym ibn Qays al-Hilali", + "ar": "سليم بن قيس الهلالي", + "ru": "Сулейм ибн Кайс аль-Хилали", + }, + { + "en": "Al-Asbagh ibn Nubatah", + "ar": "الأصبغ بن نباتة", + "ru": "Аль-Асбаг ибн Нубата", + }, + { + "en": "Jabir ibn Yazid al-Ju'fi", + "ar": "جابر بن يزيد الجعفي", + "ru": "Джабир ибн Йазид аль-Джуфи", + }, + { + "en": "Abu Hamzah al-Thumali (Thabit ibn Dinar)", + "ar": "أبو حمزة الثمالي (ثابت بن دينار)", + "ru": "Абу Хамза ас-Сумали", + }, + { + "en": "Zayd ibn Ali ibn al-Husayn", + "ar": "زيد بن علي بن الحسين", + "ru": "Зайд ибн Али", + }, + { + "en": "Dawud ibn Sarhan", + "ar": "داود بن سرحان", + "ru": "Давуд ибн Сархан", + }, + { + "en": "Abd Allah ibn Sinan", + "ar": "عبد الله بن سنان", + "ru": "Абдуллах ибн Синан", + }, + { + "en": "Abd Allah ibn Bukayr", + "ar": "عبد الله بن بكير", + "ru": "Абдуллах ибн Букайр", + }, + { + "en": "Abd Allah ibn Mughirah", + "ar": "عبد الله بن المغيرة", + "ru": "Абдуллах ибн аль-Мугира", + }, + { + "en": "Al-Husayn ibn Sa'id al-Ahwazi", + "ar": "الحسين بن سعيد الأهوازي", + "ru": "Аль-Хусейн ибн Саид аль-Ахвази", + }, + { + "en": "Ali ibn Mahziyar al-Ahwazi", + "ar": "علي بن مهزيار الأهوازي", + "ru": "Али ибн Махзийар аль-Ахвази", + }, + { + "en": "Ibrahim ibn Hashim al-Qummi", + "ar": "إبراهيم بن هاشم القمي", + "ru": "Ибрахим ибн Хашим аль-Кумми", + }, + { + "en": "Ali ibn Ibrahim al-Qummi", + "ar": "علي بن إبراهيم القمي", + "ru": "Али ибн Ибрахим аль-Кумми", + }, + { + "en": "Sa'd ibn Abd Allah al-Ash'ari", + "ar": "سعد بن عبد الله الأشعري", + "ru": "Саад ибн Абдуллах аль-Ашари", + }, + { + "en": "Al-Hasan ibn Mahbub al-Sarrad", + "ar": "الحسن بن محبوب السراد", + "ru": "Аль-Хасан ибн Махбуб", + }, + { + "en": "Al-Hasan ibn Ali ibn Faddal", + "ar": "الحسن بن علي بن فضال", + "ru": "Аль-Хасан ибн Али ибн Фаддаль", + }, + { + "en": "Ali ibn al-Hasan ibn Faddal", + "ar": "علي بن الحسن بن فضال", + "ru": "Али ибн аль-Хасан ибн Фаддаль", + }, + { + "en": "Muhammad ibn Ahmad ibn Yahya al-Ash'ari", + "ar": "محمد بن أحمد بن يحيى الأشعري", + "ru": "Мухаммад ибн Ахмад ибн Йахья", + }, + { + "en": "Ahmad ibn Muhammad ibn Isa al-Ash'ari", + "ar": "أحمد بن محمد بن عيسى الأشعري", + "ru": "Ахмад ибн Мухаммад ибн Иса аль-Ашари", + }, + { + "en": "Ahmad ibn Muhammad ibn Khalid al-Barqi", + "ar": "أحمد بن محمد بن خالد البرقي", + "ru": "Ахмад ибн Мухаммад аль-Барки", + }, + { + "en": "Muhammad ibn Yahya al-Attar al-Qummi", + "ar": "محمد بن يحيى العطار القمي", + "ru": "Мухаммад ибн Йахья аль-Аттар", + }, + { + "en": "Al-Husayn ibn Muhammad al-Ash'ari", + "ar": "الحسين بن محمد الأشعري", + "ru": "Аль-Хусейн ибн Мухаммад аль-Ашари", + }, + { + "en": "Ali ibn al-Husayn ibn Babawayh al-Qummi", + "ar": "علي بن الحسين بن بابويه القمي", + "ru": "Али ибн аль-Хусейн ибн Бабавейх", + }, + { + "en": "Ja'far ibn Muhammad ibn Qulawayh", + "ar": "جعفر بن محمد بن قولويه", + "ru": "Джафар ибн Мухаммад ибн Кулавейх", + }, + { + "en": "Muhammad ibn al-Hasan al-Saffar", + "ar": "محمد بن الحسن الصفار", + "ru": "Мухаммад ибн аль-Хасан ас-Саффар", + }, + { + "en": "Muhammad ibn Umar al-Kashshi", + "ar": "محمد بن عمر الكشي", + "ru": "Мухаммад ибн Умар аль-Кашши", + }, + { + "en": "Ahmad ibn Ali al-Najashi", + "ar": "أحمد بن علي النجاشي", + "ru": "Ахмад ибн Али ан-Наджаши", + }, + { + "en": "Muhammad ibn Ibrahim al-Nu'mani", + "ar": "محمد بن إبراهيم النعماني", + "ru": "Мухаммад ибн Ибрахим ан-Нумани", + }, + { + "en": "Qutb al-Din al-Rawandi", + "ar": "قطب الدين الراوندي", + "ru": "Кутб ад-Дин ар-Раванди", + }, + { + "en": "Al-Fadl ibn al-Hasan al-Tabrisi", + "ar": "الفضل بن الحسن الطبرسي", + "ru": "Аль-Фадль ибн аль-Хасан ат-Табарси", + }, + { + "en": "Ibn Shahrashub al-Mazandarani", + "ar": "ابن شهرآشوب المازندراني", + "ru": "Ибн Шахрашуб аль-Мазандарани", + }, + { + "en": "Ali ibn Musa ibn Tawus", + "ar": "علي بن موسى بن طاووس", + "ru": "Али ибн Муса ибн Тавус", + }, + { + "en": "Ahmad ibn Muhammad al-Ardabili", + "ar": "أحمد بن محمد الأردبيلي", + "ru": "Ахмад ибн Мухаммад аль-Ардабили", + }, + { + "en": "Al-Muhsin al-Fayd al-Kashani", + "ar": "المحسن الفيض الكاشاني", + "ru": "Аль-Файд аль-Кашани", + }, + { + "en": "Yusuf ibn Ahmad al-Bahrani", + "ar": "يوسف بن أحمد البحراني", + "ru": "Йусуф ибн Ахмад аль-Бахрани", + }, + { + "en": "Murtada al-Ansari", + "ar": "مرتضى الأنصاري", + "ru": "Муртаза аль-Ансари", + }, + { + "en": "Muhammad Hasan al-Shirazi", + "ar": "محمد حسن الشيرازي", + "ru": "Мухаммад Хасан аль-Ширази", + }, + { + "en": "Muhsin al-Amin al-Amili", + "ar": "محسن الأمين العاملي", + "ru": "Мухсин аль-Амин аль-Амили", + }, + { + "en": "Abd al-Husayn Sharaf al-Din al-Musawi", + "ar": "عبد الحسين شرف الدين الموسوي", + "ru": "Абд аль-Хусейн Шараф ад-Дин", + }, + { + "en": "Muhammad Baqir al-Sadr", + "ar": "محمد باقر الصدر", + "ru": "Мухаммад Бакир ас-Садр", + }, + { + "en": "Muhammad Husayn al-Tabataba'i", + "ar": "محمد حسين الطباطبائي", + "ru": "Мухаммад Хусейн ат-Табатабаи", + }, + { + "en": "Al-Hasan ibn Zayn al-Din (Sahib al-Ma'alim)", + "ar": "الحسن بن زين الدين (صاحب المعالم)", + "ru": "Аль-Хасан ибн Зайн ад-Дин", + }, + { + "en": "Muhammad Baqir al-Sabzawari", + "ar": "محمد باقر السبزواري", + "ru": "Мухаммад Бакир ас-Сабзавари", + }, + { + "en": "Al-Sharif al-Radi (Muhammad ibn al-Husayn)", + "ar": "الشريف الرضي (محمد بن الحسين)", + "ru": "Аш-Шариф ар-Ради", + }, + { + "en": "Ibn Abi Aqil al-Umani", + "ar": "ابن أبي عقيل العماني", + "ru": "Ибн Аби Акиль аль-Умани", + }, + { + "en": "Ibn al-Junayd al-Iskafi", + "ar": "ابن الجنيد الإسكافي", + "ru": "Ибн аль-Джунайд аль-Искафи", + }, + { + "en": "Al-Qadi Ibn al-Barraj", + "ar": "القاضي ابن البراج", + "ru": "Аль-Кади ибн аль-Баррадж", + }, + { + "en": "Sallar ibn Abd al-Aziz al-Daylami", + "ar": "سلار بن عبد العزيز الديلمي", + "ru": "Саллар ибн Абд аль-Азиз", + }, +] + + +def update_transmitters(dry_run: bool = False): + transmitters = list(Transmitters.objects.all().order_by("id")) + total_count = len(transmitters) + print(f"Found {total_count} transmitters in database.") + print(f"Loaded {len(TRANSMITTER_NAMES)} curated narrator names.") + + if total_count > len(TRANSMITTER_NAMES): + print(f"Warning: Database has {total_count} transmitters, but we have {len(TRANSMITTER_NAMES)} names.") + + updated = 0 + with transaction.atomic(): + for idx, transmitter in enumerate(transmitters): + name_data = TRANSMITTER_NAMES[idx % len(TRANSMITTER_NAMES)] + en_name = name_data["en"] + ar_name = name_data["ar"] + ru_name = name_data["ru"] + + # Format strictly adheres to the multilingual JSON schema: + # [{"language_code": "...", "text": "..."}] + new_full_name = [ + {"language_code": "en", "text": en_name}, + {"language_code": "ar", "text": ar_name}, + {"language_code": "ru", "text": ru_name}, + ] + + transmitter.full_name = new_full_name + + # Generating a clean, unique slug from the English name + base_slug = slugify(en_name, allow_unicode=True) or f"transmitter-{transmitter.id}" + + # Check for conflict excluding current transmitter + candidate_slug = base_slug[:240] + counter = 1 + while Transmitters.objects.filter(slug=candidate_slug).exclude(pk=transmitter.pk).exists(): + candidate_slug = f"{base_slug[:230]}-{counter}" + counter += 1 + + transmitter.slug = candidate_slug + + if dry_run: + print(f"[DRY RUN] ID {transmitter.id}: {en_name} | {ar_name} | {ru_name} -> slug: {candidate_slug}") + else: + transmitter.save() + print(f"[UPDATED] ID {transmitter.id}: {en_name} | {ar_name} | {ru_name} -> slug: {transmitter.slug}") + + updated += 1 + + if dry_run: + print(f"\n[DRY RUN COMPLETE] {updated} transmitters would be updated. No changes were committed.") + transaction.set_rollback(True) + else: + print(f"\n[SUCCESS] Successfully updated {updated} transmitters with unique multilingual names and slugs.") + + +def main(): + parser = argparse.ArgumentParser(description="Update transmitters with unique multilingual names.") + parser.add_argument("--dry-run", action="store_true", help="Simulate updates without committing to database.") + args = parser.parse_args() + + update_transmitters(dry_run=args.dry_run) + + +if __name__ == "__main__": + main()