diff --git a/apps/chat/migrations/0002_auto_20241125_1219.py b/apps/chat/migrations/0002_auto_20241125_1219.py index 9b27b10..07e7546 100644 --- a/apps/chat/migrations/0002_auto_20241125_1219.py +++ b/apps/chat/migrations/0002_auto_20241125_1219.py @@ -8,7 +8,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('course', '0004_auto_20241122_1913'), + ('course', '0003_alter_course_is_online_alter_course_timing_and_more'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ('chat', '0001_initial'), ] diff --git a/apps/course/migrations/0004_alter_attachment_options_alter_glossary_options_and_more.py b/apps/course/migrations/0004_alter_attachment_options_alter_glossary_options_and_more.py deleted file mode 100644 index 8d7a29d..0000000 --- a/apps/course/migrations/0004_alter_attachment_options_alter_glossary_options_and_more.py +++ /dev/null @@ -1,132 +0,0 @@ -# Generated by Django 5.1.8 on 2025-04-13 01:35 - -import django.db.models.deletion -import django.utils.timezone -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0002_alter_user_phone_number'), - ('course', '0003_alter_course_is_online_alter_course_timing_and_more'), - ] - - operations = [ - migrations.AlterModelOptions( - name='attachment', - options={'verbose_name': 'Attachment', 'verbose_name_plural': 'Attachments'}, - ), - migrations.AlterModelOptions( - name='glossary', - options={'verbose_name': 'Glossary', 'verbose_name_plural': 'Glossaries'}, - ), - migrations.RemoveField( - model_name='attachment', - name='course', - ), - migrations.RemoveField( - model_name='glossary', - name='course', - ), - migrations.RemoveField( - model_name='lesson', - name='course', - ), - migrations.RemoveField( - model_name='lesson', - name='is_active', - ), - migrations.RemoveField( - model_name='lesson', - name='priority', - ), - migrations.AddField( - model_name='attachment', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created at'), - preserve_default=False, - ), - migrations.AddField( - model_name='attachment', - name='updated_at', - field=models.DateTimeField(auto_now=True, verbose_name='Updated At'), - ), - migrations.AddField( - model_name='glossary', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created at'), - preserve_default=False, - ), - migrations.AddField( - model_name='glossary', - name='updated_at', - field=models.DateTimeField(auto_now=True, verbose_name='Updated At'), - ), - migrations.AlterField( - model_name='lesson', - name='content_type', - field=models.CharField(choices=[('youtube_link', 'Youtube Link'), ('video_file', 'Video File')], max_length=50, verbose_name='Content Type'), - ), - migrations.CreateModel( - name='CourseAttachment', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('attachment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_attachments', to='course.attachment', verbose_name='Attachment')), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='attachments', to='course.course', verbose_name='Course')), - ], - options={ - 'verbose_name': 'Course Attachment', - 'verbose_name_plural': 'Course Attachments', - 'ordering': ('-id',), - }, - ), - migrations.CreateModel( - name='CourseGlossary', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='glossaries', to='course.course', verbose_name='Course')), - ('glossary', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_glossaries', to='course.glossary', verbose_name='Glossary')), - ], - options={ - 'verbose_name': 'Course Glossary', - 'verbose_name_plural': 'Course Glossaries', - 'ordering': ('-id',), - }, - ), - migrations.CreateModel( - name='CourseLesson', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(blank=True, max_length=255, null=True, verbose_name='Course Lesson Title')), - ('priority', models.IntegerField(blank=True, null=True, verbose_name='Priority')), - ('is_active', models.BooleanField(default=True, verbose_name='Is Active')), - ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created at')), - ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Updated At')), - ('course', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='course.course', verbose_name='Course')), - ('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='course_lessons', to='course.lesson', verbose_name='Lesson')), - ], - ), - migrations.AlterUniqueTogether( - name='lessoncompletion', - unique_together=set(), - ), - migrations.AddField( - model_name='lessoncompletion', - name='course_lesson', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='completions', to='course.courselesson'), - preserve_default=False, - ), - migrations.AlterUniqueTogether( - name='lessoncompletion', - unique_together={('student', 'course_lesson')}, - ), - migrations.RemoveField( - model_name='lessoncompletion', - name='lesson', - ), - ] diff --git a/apps/course/migrations/0004_auto_20241122_1913.py b/apps/course/migrations/0004_auto_20241122_1913.py deleted file mode 100644 index bdd56db..0000000 --- a/apps/course/migrations/0004_auto_20241122_1913.py +++ /dev/null @@ -1,42 +0,0 @@ -# Generated by Django 3.2.4 on 2024-11-22 19:13 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('course', '0003_participant'), - ] - - operations = [ - migrations.AddField( - model_name='course', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created at'), - preserve_default=False, - ), - migrations.AddField( - model_name='course', - name='updated_at', - field=models.DateTimeField(auto_now=True, verbose_name='Updated At'), - ), - migrations.AddField( - model_name='lesson', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created at'), - preserve_default=False, - ), - migrations.AddField( - model_name='lesson', - name='updated_at', - field=models.DateTimeField(auto_now=True, verbose_name='Updated At'), - ), - migrations.AddField( - model_name='lessoncompletion', - name='created_at', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, verbose_name='Created at'), - preserve_default=False, - ), - ] diff --git a/apps/course/migrations/0005_add_database_indexes.py b/apps/course/migrations/0005_add_database_indexes.py index 9723b80..838c640 100644 --- a/apps/course/migrations/0005_add_database_indexes.py +++ b/apps/course/migrations/0005_add_database_indexes.py @@ -7,7 +7,7 @@ class Migration(migrations.Migration): dependencies = [ ('account', '0003_locationhistory'), - ('course', '0004_alter_attachment_options_alter_glossary_options_and_more'), + ('course', '0003_alter_course_is_online_alter_course_timing_and_more'), ] operations = [ diff --git a/apps/course/migrations/0005_participant_unread_messages_count.py b/apps/course/migrations/0005_participant_unread_messages_count.py index acf281f..cad41db 100644 --- a/apps/course/migrations/0005_participant_unread_messages_count.py +++ b/apps/course/migrations/0005_participant_unread_messages_count.py @@ -6,7 +6,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ('course', '0004_auto_20241122_1913'), + ('course', '0003_participant'), ] operations = [ diff --git a/apps/hadis/management/commands/seed_hadis_transmitter.py b/apps/hadis/management/commands/seed_hadis_transmitter.py index ec20105..bc5b12e 100644 --- a/apps/hadis/management/commands/seed_hadis_transmitter.py +++ b/apps/hadis/management/commands/seed_hadis_transmitter.py @@ -3,37 +3,34 @@ import time from django.core.management.base import BaseCommand from django.db import transaction from django.db.models import Q -# REPLACE 'your_app' with your actual app name +# REPLACE 'your_app' with your actual app name (e.g., apps.hadis.models) from apps.hadis.models import Hadis, Transmitters, NarratorLayer, HadisTransmitter class Command(BaseCommand): - help = 'Seeds HadisTransmitter instances with specific constraints' + help = 'Seeds HadisTransmitter instances for ALL Hadis with 5+ transmitters spanning 2-3 layers' def handle(self, *args, **options): - self.stdout.write("Starting HadisTransmitter seeding...") + self.stdout.write("Starting Global HadisTransmitter seeding...") # --------------------------------------------------------- - # 1. Fetch the specific Objects + # 1. Fetch ALL Objects # --------------------------------------------------------- - layers = list(NarratorLayer.objects.filter(id__range=(11, 15))) - transmitters = list(Transmitters.objects.filter(id__range=(84, 91))) - - # Hadis query - hadis_qs = Hadis.objects.filter( - Q(id__range=(1800, 1852)) | Q(id__range=(1877, 1889)) - ) + layers = list(NarratorLayer.objects.all()) + transmitters = list(Transmitters.objects.all()) + # Fetch all Hadis records + hadis_qs = Hadis.objects.all() # --------------------------------------------------------- # 2. Validation # --------------------------------------------------------- - if len(layers) < 2: - self.stdout.write(self.style.ERROR(f"Need at least 2 NarratorLayers to satisfy constraints, but found {len(layers)}.")) + if len(layers) < 3: + self.stdout.write(self.style.ERROR(f"Need at least 3 NarratorLayers to satisfy constraints, but found {len(layers)}.")) return - if not transmitters: - self.stdout.write(self.style.ERROR("No Transmitters found in range 84-91.")) + if len(transmitters) < 5: + self.stdout.write(self.style.ERROR(f"Need at least 5 Transmitters to satisfy chain length, but found {len(transmitters)}.")) return if not hadis_qs.exists(): - self.stdout.write(self.style.ERROR("No Hadis found in the specified ranges.")) + self.stdout.write(self.style.ERROR("No Hadis found in the database.")) return total_hadis = hadis_qs.count() @@ -42,50 +39,55 @@ class Command(BaseCommand): # --------------------------------------------------------- # 3. Creation Loop # --------------------------------------------------------- - created_count = 0 + created_links_count = 0 self.stdout.write("Beginning processing...") for i, hadis in enumerate(hadis_qs, 1): - # Print progress every 5 items so you know it's not frozen - if i % 5 == 0: - self.stdout.write(f"Processing {i}/{total_hadis} (Hadis ID: {hadis.id})...") + # Progress indicator + if i % 100 == 0: + self.stdout.write(f"Processing {i}/{total_hadis}...") - # Wrap PER ITEM in transaction to avoid long db locks with transaction.atomic(): - # CONSTRAINT 1: Each hadis must have 3-4 transmitters - chain_length = random.randint(3, 4) + # Clean existing transmitters for this hadis to avoid duplicates/conflicts if re-running + # Remove this line if you want to APPEND to existing data instead of resetting + HadisTransmitter.objects.filter(hadis=hadis).delete() + + # CONSTRAINT 1: Chain length must be at least 5 (let's do 5 to 7) + chain_length = random.randint(5, 7) + + # CONSTRAINT 2: Must have at least 2-3 narrator layers + target_unique_layers = random.randint(2, 3) - if len(transmitters) < chain_length: - self.stdout.write(self.style.WARNING(f"Not enough transmitters. Skipping Hadis {hadis.id}.")) - continue + # --- Logic to Select Transmitters and Layers --- - # Pick unique transmitters + # A. Select Transmitters selected_transmitters = random.sample(transmitters, chain_length) - # CONSTRAINT 2: Transmitters must be separated to at least 2 narrator layers - # LOGIC FIX: Instead of a while loop, we force the condition explicitly. + # B. Select Layers + # First, pick the 'mandatory' unique layers to satisfy the constraint + mandatory_layers = random.sample(layers, target_unique_layers) - # Step A: Pick 2 DISTINCT layers guaranteed - guaranteed_layers = random.sample(layers, 2) + # Second, fill the remaining slots in the chain + remaining_slots = chain_length - target_unique_layers - # Step B: Fill the remaining slots (1 or 2 slots) with random layers - remaining_slots = chain_length - 2 + # We can pick from ANY layer for the remaining slots (including the mandatory ones) other_layers = [random.choice(layers) for _ in range(remaining_slots)] - # Step C: Combine and Shuffle so the distinct ones aren't always first - final_layers = guaranteed_layers + other_layers - random.shuffle(final_layers) + # Combine and Shuffle + final_layers_pool = mandatory_layers + other_layers + random.shuffle(final_layers_pool) - # Create the connections + # --- Create Connections --- for index, transmitter in enumerate(selected_transmitters): HadisTransmitter.objects.create( hadis=hadis, transmitter=transmitter, - narrator_layer=final_layers[index], + narrator_layer=final_layers_pool[index], order=index, + # Check if transmitter has 'reliability' (ForeignKey) or if it's a direct field status=transmitter.reliability if hasattr(transmitter, 'reliability') else None ) - created_count += 1 + created_links_count += 1 - self.stdout.write(self.style.SUCCESS(f"Done! Successfully created {created_count} HadisTransmitter instances.")) \ No newline at end of file + self.stdout.write(self.style.SUCCESS(f"Done! Created {created_links_count} HadisTransmitter connections for {total_hadis} Hadiths.")) \ No newline at end of file diff --git a/apps/hadis/management/commands/seed_transmitters.py b/apps/hadis/management/commands/seed_transmitters.py index 9e06710..c7c33e8 100644 --- a/apps/hadis/management/commands/seed_transmitters.py +++ b/apps/hadis/management/commands/seed_transmitters.py @@ -1,109 +1,133 @@ import random from django.core.management.base import BaseCommand from django.db import transaction -# REPLACE 'apps.hadis.models' WITH YOUR ACTUAL APP PATH -from apps.hadis.models import Transmitters, TransmitterOpinion, TransmitterOriginalText, OpinionStatus +# REPLACE 'apps.hadis.models' with your actual app path if different +from apps.hadis.models import Transmitters, TransmitterReliability class Command(BaseCommand): - help = 'Seeds TransmitterOpinion and TransmitterOriginalText for transmitters 84-91' + help = 'Seeds database with 50 famous Islamic transmitters and scholars' + + def create_json_field(self, en_text, fa_text, ru_text): + """Helper to create the standard JSON structure""" + return [ + {"text": en_text, "language_code": "en"}, + {"text": fa_text, "language_code": "fa"}, + {"text": ru_text, "language_code": "ru"} + ] def handle(self, *args, **options): - self.stdout.write("Starting Transmitter Details seeding...") - - # --------------------------------------------------------- - # 1. Fetch Target Objects - # --------------------------------------------------------- - transmitters = Transmitters.objects.filter(id__range=(84, 91)) - - if not transmitters.exists(): - self.stdout.write(self.style.ERROR("No Transmitters found in range 84-91.")) - return + self.stdout.write("Starting to seed transmitters...") - # Fetch OpinionStatus objects - # We need these to populate the 'status' ForeignKey in TransmitterOpinion - statuses = list(OpinionStatus.objects.all()) + # 1. Fetch available reliability IDs (7-17) + # We assume these exist based on your prompt. + reliability_ids = list(range(7, 18)) - # Fallback: If no statuses exist, we can't create opinions safely without knowing your Status model fields. - if not statuses: - self.stdout.write(self.style.ERROR("No 'OpinionStatus' objects found! Please create some via Admin first.")) - return - - self.stdout.write(f"Found {transmitters.count()} Transmitters and {len(statuses)} Opinion Statuses.") - - # --------------------------------------------------------- - # 2. Data Generators (Helpers) - # --------------------------------------------------------- - scholars = [ - {"en": "Al-Dhahabi", "ar": "الذهبي", "fa": "ذهبی"}, - {"en": "Ibn Hajar", "ar": "ابن حجر", "fa": "ابن حجر"}, - {"en": "Al-Tusi", "ar": "الطوسي", "fa": "طوسی"}, - {"en": "Al-Najashi", "ar": "النجاشي", "fa": "نجاشی"}, - ] - - opinion_texts = [ - {"en": "He is trustworthy and reliable.", "ar": "هو ثقة ثبت.", "fa": "او ثقه و مورد اعتماد است."}, - {"en": "His memory was weak in later years.", "ar": "كان سيء الحفظ في آخره.", "fa": "حافظه او در اواخر عمر ضعیف بود."}, - {"en": "Unknown status.", "ar": "مجهول الحال.", "fa": "مجهول است."}, - {"en": "Highly praised by scholars.", "ar": "ممدوح عند العلماء.", "fa": "مورد ستایش علما است."}, - ] - - book_titles = [ - {"en": "Book of Traditions", "ar": "كتاب الحديث", "fa": "کتاب حدیث"}, - {"en": "Treatise on Rights", "ar": "رسالة الحقوق", "fa": "رساله حقوق"}, - {"en": "The Clarification", "ar": "التبين", "fa": "تبیین"}, - {"en": "Collection of Virtues", "ar": "مجموع الفضائل", "fa": "مجموعه فضائل"}, + # Verify reliabilities exist, or create a fallback + existing_reliabilities = list(TransmitterReliability.objects.filter(id__in=reliability_ids)) + if not existing_reliabilities: + self.stdout.write(self.style.WARNING("No TransmitterReliability found in range 7-17. Creating a default one...")) + default_rel = TransmitterReliability.objects.create( + id=7, + title=self.create_json_field("Trustworthy", "ثقه", "Надежный"), + color='green' + ) + existing_reliabilities = [default_rel] + + # 2. Data Definition (List of 50) + # Structure: (EN Name, FA Name, RU Name, Death Year, Origin EN, Origin FA, Origin RU) + data_source = [ + ("Abu Hurairah", "ابو هریره", "Абу Хурайра", 59, "Yemen", "یمن", "Йемен"), + ("Anas ibn Malik", "انس بن مالک", "Анас ибн Малик", 93, "Medina", "مدینه", "Медина"), + ("Aisha bint Abi Bakr", "عایشه بنت ابی‌بکر", "Аиша бинт Абу Бакр", 58, "Mecca", "مکه", "Мекка"), + ("Abdullah ibn Umar", "عبدالله بن عمر", "Абдуллах ибн Умар", 73, "Mecca", "مکه", "Мекка"), + ("Abdullah ibn Abbas", "عبدالله بن عباس", "Абдуллах ибн Аббас", 68, "Mecca", "مکه", "Мекка"), + ("Jabir ibn Abdullah", "جابر بن عبدالله", "Джабир ибн Абдуллах", 78, "Medina", "مدینه", "Медина"), + ("Abu Sa'id al-Khudri", "ابو سعید خدری", "Абу Саид аль-Худри", 74, "Medina", "مدینه", "Медина"), + ("Abdullah ibn Mas'ud", "عبدالله بن مسعود", "Абдуллах ибн Масуд", 32, "Mecca", "مکه", "Мекка"), + ("Ali ibn Abi Talib", "علی بن ابی‌طالب", "Али ибн Абу Талиб", 40, "Mecca", "مکه", "Мекка"), + ("Umar ibn al-Khattab", "عمر بن خطاب", "Умар ибн аль-Хаттаб", 23, "Mecca", "مکه", "Мекка"), + ("Abu Bakr as-Siddiq", "ابوبکر صدیق", "Абу Бакр ас-Сиддик", 13, "Mecca", "مکه", "Мекка"), + ("Uthman ibn Affan", "عثمان بن عفان", "Усман ибн Аффан", 35, "Mecca", "مکه", "Мекка"), + ("Salman al-Farsi", "سلمان فارسی", "Салман аль-Фариси", 36, "Isfahan", "اصفهان", "Исфахан"), + ("Bilal ibn Rabah", "بلال حبشی", "Билал ибн Рабах", 20, "Mecca", "مکه", "Мекка"), + ("Ammar ibn Yasir", "عمار یاسر", "Аммар ибн Ясир", 37, "Mecca", "مکه", "Мекка"), + ("Abu Dharr al-Ghifari", "ابوذر غفاری", "Абу Зарр аль-Гифари", 32, "Ghifar", "غفار", "Гифар"), + ("Miqdad ibn Aswad", "مقداد بن اسود", "Микдад ибн Асвад", 33, "Mecca", "مکه", "Мекка"), + ("Talhah ibn Ubaydullah", "طلحه بن عبیدالله", "Талха ибн Убайдуллах", 36, "Mecca", "مکه", "Мекка"), + ("Zubayr ibn al-Awam", "زبیر بن عوام", "Зубайр ибн аль-Аввам", 36, "Mecca", "مکه", "Мекка"), + ("Sa'd ibn Abi Waqqas", "سعد بن ابی‌وقاص", "Саад ибн Абу Ваккас", 55, "Mecca", "مکه", "Мекка"), + ("Saeed ibn al-Musayyib", "سعید بن مسیب", "Саид ибн аль-Мусаййиб", 94, "Medina", "مدینه", "Медина"), + ("Urwah ibn al-Zubayr", "عروه بن زبیر", "Урва ибн аз-Зубайр", 94, "Medina", "مدینه", "Медина"), + ("Al-Hasan al-Basri", "حسن بصری", "Аль-Хасан аль-Басри", 110, "Medina", "مدینه", "Медина"), + ("Muhammad ibn Sirin", "محمد بن سیرین", "Мухаммад ибн Сирин", 110, "Basra", "بصره", "Басра"), + ("Ibn Shihab al-Zuhri", "ابن شهاب زهری", "Ибн Шихаб аз-Зухри", 124, "Medina", "مدینه", "Медина"), + ("Nafi Mawla Ibn Umar", "نافع مولی ابن عمر", "Нафи мавля Ибн Умар", 117, "Medina", "مدینه", "Медина"), + ("Malik ibn Anas", "مالک بن انس", "Малик ибн Анас", 179, "Medina", "مدینه", "Медина"), + ("Al-Shafi'i", "محمد بن ادریس شافعی", "Аш-Шафии", 204, "Gaza", "غزه", "Газа"), + ("Ahmad ibn Hanbal", "احمد بن حنبل", "Ахмад ибн Ханбаль", 241, "Baghdad", "بغداد", "Багдад"), + ("Sufyan al-Thawri", "سفیان ثوری", "Суфьян ас-Саури", 161, "Kufa", "کوفه", "Куфа"), + ("Sufyan ibn Uyaynah", "سفیان بن عیینه", "Суфьян ибн Уйайна", 198, "Kufa", "کوفه", "Куфа"), + ("Abdullah ibn al-Mubarak", "عبدالله بن مبارک", "Абдуллах ибн аль-Мубарак", 181, "Merv", "مرو", "Мерв"), + ("Waki ibn al-Jarrah", "وکیع بن جراح", "Ваки ибн аль-Джаррах", 197, "Kufa", "کوفه", "Куфа"), + ("Shu'bah ibn al-Hajjaj", "شعبه بن حجاج", "Шуба ибн аль-Хаджадж", 160, "Wasit", "واسط", "Васит"), + ("Yahya ibn Ma'in", "یحیی بن معین", "Яхья ибн Маин", 233, "Baghdad", "بغداد", "Багдад"), + ("Ali ibn al-Madini", "علی بن مدینی", "Али ибн аль-Мадини", 234, "Basra", "بصره", "Басра"), + ("Muhammad al-Bukhari", "محمد بخاری", "Мухаммад аль-Бухари", 256, "Bukhara", "بخارا", "Бухара"), + ("Muslim ibn al-Hajjaj", "مسلم نیشابوری", "Муслим ибн аль-Хаджадж", 261, "Nishapur", "نیشابور", "Нишапур"), + ("Abu Dawood", "ابوداوود سجستانی", "Абу Дауд", 275, "Sistan", "سیستان", "Систан"), + ("Al-Tirmidhi", "ترمذی", "Ат-Тирмизи", 279, "Termez", "ترمز", "Термез"), + ("Al-Nasa'i", "نسائی", "Ан-Насаи", 303, "Nasa", "نسا", "Наса"), + ("Ibn Majah", "ابن ماجه", "Ибн Маджа", 273, "Qazvin", "قزوین", "Казвин"), + ("Al-Daraqutni", "دارقطنی", "Ад-Даракутни", 385, "Baghdad", "بغداد", "Багдад"), + ("Al-Bayhaqi", "بیهقی", "Аль-Байхаки", 458, "Bayhaq", "بیهق", "Байхак"), + ("Al-Hakim al-Nishapuri", "حاکم نیشابوری", "Аль-Хаким ан-Нишапури", 405, "Nishapur", "نیشابور", "Нишапур"), + ("Al-Nawawi", "نووی", "Ан-Навави", 676, "Nawa", "نوا", "Нава"), + ("Al-Dhahabi", "ذهبی", "Аз-Захаби", 748, "Damascus", "دمشق", "Дамаск"), + ("Ibn Hajar al-Asqalani", "ابن حجر عسقلانی", "Ибн Хаджар аль-Аскаляни", 852, "Cairo", "قاهره", "Каир"), + ("Ibn Kathir", "ابن کثیر", "Ибн Касир", 774, "Damascus", "دمشق", "Дамаск"), + ("Al-Tabari", "طبری", "Ат-Табари", 310, "Amol", "آمل", "Амоль"), ] - def make_json_field(data_dict): - """Converts simple dict {'en': 'X', 'fa': 'Y'} to your model's JSON structure""" - return [ - {"language_code": "en", "text": data_dict.get("en", "")}, - {"language_code": "ar", "text": data_dict.get("ar", "")}, - {"language_code": "fa", "text": data_dict.get("fa", "")}, - ] - - # --------------------------------------------------------- - # 3. Creation Loop - # --------------------------------------------------------- - counts = {'opinions': 0, 'texts': 0} - + # 3. Execution Loop + created_count = 0 with transaction.atomic(): - for t in transmitters: - self.stdout.write(f"Processing {t.full_name[0]['text'] if t.full_name else t.id}...") - - # --- A. Create TransmitterOpinion (1 to 2 per person) --- - for _ in range(random.randint(1, 2)): - scholar = random.choice(scholars) - text = random.choice(opinion_texts) - status = random.choice(statuses) - - TransmitterOpinion.objects.create( - transmitter=t, - scholar_name=make_json_field(scholar), - opinion_text=make_json_field(text), - status=status - ) - counts['opinions'] += 1 - - # --- B. Create TransmitterOriginalText (1 to 2 per person) --- - for _ in range(random.randint(1, 2)): - title = random.choice(book_titles) - # Simple dummy text - body = { - "en": f"This is an excerpt from {title['en']}...", - "ar": f"هذا مقتطف من {title['ar']}...", - "fa": f"این بخشی از {title['fa']} است..." - } - - TransmitterOriginalText.objects.create( - transmitter=t, - title=make_json_field(title), - text=make_json_field(body), - translation=make_json_field(body), # Using same text for translation as placeholder - # Note: 'slug' and 'share_link' are handled by your model's save() method automatically - ) - counts['texts'] += 1 - - self.stdout.write(self.style.SUCCESS( - f"Done! Created {counts['opinions']} Opinions and {counts['texts']} Original Texts." - )) \ No newline at end of file + for entry in data_source: + en_name, fa_name, ru_name, death_year, en_origin, fa_origin, ru_origin = entry + + # Check if exists (by English Name) to allow re-running script + if Transmitters.objects.filter(full_name__contains=[{"text": en_name, "language_code": "en"}]).exists(): + self.stdout.write(f"Skipping {en_name} (already exists)") + continue + + # Prepare JSON fields + full_name_json = self.create_json_field(en_name, fa_name, ru_name) + origin_json = self.create_json_field(en_origin, fa_origin, ru_origin) + + # Simple defaults for other text fields + desc_json = self.create_json_field( + f"A renowned scholar/narrator from {en_origin}", + f"از راویان/علمای مشهور اهل {fa_origin}", + f"Известный передатчик/ученый из {ru_origin}" + ) + + # Randomly pick reliability + reliability = random.choice(existing_reliabilities) + + # Create Object + Transmitters.objects.create( + full_name=full_name_json, + origin=origin_json, + lived_in=origin_json, # Assume lived where born for simplicity + died_in=self.create_json_field("Unknown", "نامشخص", "Неизвестно"), + death_year_hijri=death_year, + description=desc_json, + reliability=reliability, + # Setting defaults for required JSON fields + kunya=self.create_json_field("", "", ""), + known_as=self.create_json_field("", "", ""), + nickname=self.create_json_field("", "", ""), + madhhab='unknown', # Default as per your choices + ) + created_count += 1 + + self.stdout.write(self.style.SUCCESS(f"Successfully created {created_count} new Transmitters!")) \ No newline at end of file diff --git a/apps/hadis/models/hadis.py b/apps/hadis/models/hadis.py index f0bba40..92c9ec2 100644 --- a/apps/hadis/models/hadis.py +++ b/apps/hadis/models/hadis.py @@ -192,8 +192,8 @@ class Hadis(models.Model): share_link = models.CharField(max_length=255, verbose_name=_('share link'), null=True, blank=True) explanation = models.JSONField(default = list , verbose_name=_('Explanation')) - explanations = models.JSONField(default = list , verbose_name=_('Explanations')) - address_details = models.JSONField(default = list , verbose_name=_('Address Details')) + # explanations = models.JSONField(default = list , verbose_name=_('Explanations')) + # address_details = models.JSONField(default = list , verbose_name=_('Address Details')) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_('created at')) updated_at = models.DateTimeField(auto_now=True, verbose_name=_('updated at')) diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index 323c9ff..095f2f6 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -162,10 +162,10 @@ class Transmitters(models.Model): class MadhhabChoices(models.TextChoices): SHIA = 'shia', _('Shia') SUNNI = 'sunni', _('Sunni') - HANAFI = 'hanafi', _('Hanafi') - MALIKI = 'maliki', _('Maliki') - SHAFII = 'shafii', _('Shafi\'i') - HANBALI = 'hanbali', _('Hanbali') + # HANAFI = 'hanafi', _('Hanafi') + # MALIKI = 'maliki', _('Maliki') + # SHAFII = 'shafii', _('Shafi\'i') + # HANBALI = 'hanbali', _('Hanbali') OTHER = 'other', _('Other') UNKNOWN = 'unknown', _('Unknown') diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index ee45fc1..0e9f9b5 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -618,30 +618,30 @@ class HadisBasicSerializer(serializers.ModelSerializer): title = LocalizedField() title_narrator = LocalizedField() explanation = LocalizedField() - explanations = serializers.SerializerMethodField() + # explanations = serializers.SerializerMethodField() class Meta: model = Hadis fields = [ 'id', 'slug', 'title','bookmark', 'title_narrator', 'text', - 'translation', 'share_link','explanation', 'explanations','category' + 'translation', 'share_link','explanation','category' ] - def get_explanations(self, obj): - """Get structured explanations for the requested language""" - request = self.context.get('request') - request_lang = request.query_params.get('lang', 'en') if request else 'en' + # def get_explanations(self, obj): + # """Get structured explanations for the requested language""" + # request = self.context.get('request') + # request_lang = request.query_params.get('lang', 'en') if request else 'en' - explanations_list = [] - if obj.explanations and isinstance(obj.explanations, list): - for item in obj.explanations: - if isinstance(item, dict) and item.get('language_code') == request_lang: - explanations_list.append({ - 'title': item.get('title', ''), - 'description': item.get('description', '') - }) + # explanations_list = [] + # if obj.explanations and isinstance(obj.explanations, list): + # for item in obj.explanations: + # if isinstance(item, dict) and item.get('language_code') == request_lang: + # explanations_list.append({ + # 'title': item.get('title', ''), + # 'description': item.get('description', '') + # }) - return explanations_list if explanations_list else None + # return explanations_list if explanations_list else None def get_category(self, obj): @@ -694,20 +694,20 @@ class HadisDetailSerializer(serializers.ModelSerializer): ) reference_images = SerializerMethodField() address = LocalizedField() - address_details = serializers.SerializerMethodField() + # address_details = serializers.SerializerMethodField() class Meta: model = Hadis fields = [ 'id', 'number', 'slug','hadis_status', 'links','share_link', - 'tags','references','reference_images','address', 'address_details' + 'tags','references','reference_images','address' ] - def get_address_details(self, obj): - """Get address details sorted by priority""" - if obj.address_details and isinstance(obj.address_details, list): - return sorted(obj.address_details, key=lambda x: x.get('priority', 0)) - return [] + # def get_address_details(self, obj): + # """Get address details sorted by priority""" + # if obj.address_details and isinstance(obj.address_details, list): + # return sorted(obj.address_details, key=lambda x: x.get('priority', 0)) + # return [] def get_reference_images(self, obj): """Get all reference images from all references""" diff --git a/entrypoint.sh b/entrypoint.sh index 4f52a22..36faa43 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -6,9 +6,10 @@ python manage.py migrate python manage.py collectstatic --noinput # Seed Russian data (only runs once, skips if data exists) +python manage.py seed_hadis_transmitter # python manage.py seed_russian_data # python manage.py hadisreferences -python manage.py seed_complete_hadis_data +# python manage.py seed_complete_hadis_data # python manage.py populate_books # python manage.py populate_book_reference # python manage.py populate_refrence_images