diff --git a/apps/hadis/admin/transmitter.py b/apps/hadis/admin/transmitter.py index f0fc184..5f6775f 100644 --- a/apps/hadis/admin/transmitter.py +++ b/apps/hadis/admin/transmitter.py @@ -11,7 +11,7 @@ class HadisTransmitterInline(TabularInline): """Inline for HadisTransmitter in Transmitters admin""" model = HadisTransmitter extra = 0 - fields = ('hadis', 'order', 'status') + fields = ('hadis', 'order') readonly_fields = ('created_at',) @@ -51,8 +51,8 @@ class TransmittersAdmin(ModelAdmin): class HadisTransmitterAdmin(ModelAdmin): """Admin for HadisTransmitter model""" - list_display = ('hadis', 'transmitter', 'order', 'status', 'created_at') - list_filter = ('status', 'created_at') + list_display = ('hadis', 'transmitter', 'order', 'created_at') + list_filter = ( 'created_at',) search_fields = ('hadis__title', 'transmitter__full_name') readonly_fields = ('created_at',) ordering = ('hadis', 'order') @@ -61,9 +61,6 @@ class HadisTransmitterAdmin(ModelAdmin): (None, { 'fields': ('hadis', 'transmitter', 'order') }), - (_('Status Information'), { - 'fields': ('status',) - }), (_('Timestamps'), { 'fields': ('created_at',), 'classes': ('collapse',) diff --git a/apps/hadis/management/commands/seed_hadis_transmitter.py b/apps/hadis/management/commands/seed_hadis_transmitter.py new file mode 100644 index 0000000..ec20105 --- /dev/null +++ b/apps/hadis/management/commands/seed_hadis_transmitter.py @@ -0,0 +1,91 @@ +import random +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 +from apps.hadis.models import Hadis, Transmitters, NarratorLayer, HadisTransmitter + +class Command(BaseCommand): + help = 'Seeds HadisTransmitter instances with specific constraints' + + def handle(self, *args, **options): + self.stdout.write("Starting HadisTransmitter seeding...") + + # --------------------------------------------------------- + # 1. Fetch the specific 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)) + ) + + # --------------------------------------------------------- + # 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)}.")) + return + if not transmitters: + self.stdout.write(self.style.ERROR("No Transmitters found in range 84-91.")) + return + if not hadis_qs.exists(): + self.stdout.write(self.style.ERROR("No Hadis found in the specified ranges.")) + return + + total_hadis = hadis_qs.count() + self.stdout.write(f"Found {len(layers)} Layers, {len(transmitters)} Transmitters, and {total_hadis} Hadis.") + + # --------------------------------------------------------- + # 3. Creation Loop + # --------------------------------------------------------- + created_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})...") + + # 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) + + if len(transmitters) < chain_length: + self.stdout.write(self.style.WARNING(f"Not enough transmitters. Skipping Hadis {hadis.id}.")) + continue + + # Pick unique 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. + + # Step A: Pick 2 DISTINCT layers guaranteed + guaranteed_layers = random.sample(layers, 2) + + # Step B: Fill the remaining slots (1 or 2 slots) with random layers + remaining_slots = chain_length - 2 + 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) + + # Create the connections + for index, transmitter in enumerate(selected_transmitters): + HadisTransmitter.objects.create( + hadis=hadis, + transmitter=transmitter, + narrator_layer=final_layers[index], + order=index, + status=transmitter.reliability if hasattr(transmitter, 'reliability') else None + ) + created_count += 1 + + self.stdout.write(self.style.SUCCESS(f"Done! Successfully created {created_count} HadisTransmitter instances.")) \ 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 bd3c266..364f51f 100644 --- a/apps/hadis/management/commands/seed_transmitters.py +++ b/apps/hadis/management/commands/seed_transmitters.py @@ -3,12 +3,13 @@ from django.core.management.base import BaseCommand from django.core.exceptions import ObjectDoesNotExist # Ensure these imports match your actual app structure from apps.hadis.models import ( - NarratorLayer, - Transmitters, - HadisTransmitter, - TransmitterOpinion, - TransmitterOriginalText, - Hadis + NarratorLayer, + Transmitters, + HadisTransmitter, + TransmitterOpinion, + TransmitterOriginalText, + Hadis, + TransmitterReliability ) class Command(BaseCommand): @@ -246,12 +247,21 @@ class Command(BaseCommand): for index, narrator in enumerate(chain_narrators): layer_obj = NarratorLayer.objects.get(number=5-index) if index < 5 else None + # Get or create reliable status + reliable_status, created = TransmitterReliability.objects.get_or_create( + slug='reliable', + defaults={ + 'title': self.create_json_field('Reliable', 'موثوق', 'Надежный'), + 'color': 'green' + } + ) + HadisTransmitter.objects.create( hadis=hadis_obj, transmitter=narrator, narrator_layer=layer_obj, order=index + 1, - status=HadisTransmitter.ReliabilityStatus.RELIABLE, + status=reliable_status, is_gap=False ) diff --git a/apps/hadis/migrations/0081_alter_hadistransmitter_status.py b/apps/hadis/migrations/0081_alter_hadistransmitter_status.py new file mode 100644 index 0000000..b36a679 --- /dev/null +++ b/apps/hadis/migrations/0081_alter_hadistransmitter_status.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.27 on 2025-12-24 11:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0080_transmitteropinion_status"), + ] + + operations = [ + migrations.AlterField( + model_name="hadistransmitter", + name="status", + field=models.ForeignKey( + blank=True, + help_text="Reliability status of the narrator", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="hadis_transmitters", + to="hadis.transmitterreliability", + verbose_name="reliability status", + ), + ), + ] diff --git a/apps/hadis/migrations/0082_remove_hadistransmitter_status.py b/apps/hadis/migrations/0082_remove_hadistransmitter_status.py new file mode 100644 index 0000000..effe156 --- /dev/null +++ b/apps/hadis/migrations/0082_remove_hadistransmitter_status.py @@ -0,0 +1,16 @@ +# Generated by Django 4.2.27 on 2025-12-24 12:11 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0081_alter_hadistransmitter_status"), + ] + + operations = [ + migrations.RemoveField( + model_name="hadistransmitter", + name="status", + ), + ] diff --git a/apps/hadis/migrations/0083_auto_20251224_1214.py b/apps/hadis/migrations/0083_auto_20251224_1214.py new file mode 100644 index 0000000..1daea60 --- /dev/null +++ b/apps/hadis/migrations/0083_auto_20251224_1214.py @@ -0,0 +1,15 @@ +from django.db import migrations + +class Migration(migrations.Migration): + + dependencies = [ + ('hadis', '0082_remove_hadistransmitter_status'), # Keep your actual dependency here + ] + + operations = [ + # This tells Django: "Run this SQL command, but don't try to update your internal model state" + migrations.RunSQL( + sql="ALTER TABLE hadis_hadistransmitter DROP COLUMN status;", + reverse_sql="ALTER TABLE hadis_hadistransmitter ADD COLUMN status varchar(255);" # Optional fallback + ), + ] \ No newline at end of file diff --git a/apps/hadis/migrations/0084_hadistransmitter_status.py b/apps/hadis/migrations/0084_hadistransmitter_status.py new file mode 100644 index 0000000..4f7755b --- /dev/null +++ b/apps/hadis/migrations/0084_hadistransmitter_status.py @@ -0,0 +1,26 @@ +# Generated by Django 4.2.27 on 2025-12-24 12:32 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0083_auto_20251224_1214"), + ] + + operations = [ + migrations.AddField( + model_name="hadistransmitter", + name="status", + field=models.ForeignKey( + blank=True, + help_text="Reliability status of the narrator", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="hadis_transmitters", + to="hadis.transmitterreliability", + verbose_name="reliability status", + ), + ), + ] diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index d27a8ae..bc28ce1 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -273,11 +273,6 @@ class Transmitters(models.Model): class HadisTransmitter(models.Model): - class ReliabilityStatus(models.TextChoices): - RELIABLE = 'reliable', _('Reliable') - WEAK = 'weak', _('Weak') - UNKNOWN = 'unknown', _('Unknown') - hadis = models.ForeignKey( "hadis.Hadis", on_delete=models.CASCADE, @@ -299,11 +294,13 @@ class HadisTransmitter(models.Model): blank=True, help_text=_('The layer/class (Tabaqah) this narrator belongs to') ) - status = models.CharField( - max_length=20, - choices=ReliabilityStatus.choices, - default=ReliabilityStatus.UNKNOWN, + status = models.ForeignKey( + TransmitterReliability, + on_delete=models.SET_NULL, verbose_name=_('reliability status'), + related_name='hadis_transmitters', + null=True, + blank=True, help_text=_('Reliability status of the narrator') ) order = models.PositiveIntegerField( diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index d436427..f5c8c9a 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -447,10 +447,11 @@ class HadisTransmitterSerializer(serializers.ModelSerializer): transmitter = TransmitterShortSerializer(read_only=True) narrator_layer_description = serializers.SerializerMethodField() layer = serializers.SerializerMethodField() + status = serializers.SerializerMethodField() class Meta: model = HadisTransmitter fields = [ - 'id', 'order', 'is_gap','narrator_layer_description','layer', 'transmitter' + 'id', 'order', 'is_gap','narrator_layer_description','layer', 'transmitter', 'status' ] def get_narrator_layer_description(self, obj): @@ -462,7 +463,19 @@ class HadisTransmitterSerializer(serializers.ModelSerializer): def get_layer(self, obj): """Get narrator layer slug""" - return obj.narrator_layer.slug + return obj.narrator_layer.slug if obj.narrator_layer else None + + def get_status(self, obj): + """Serialize the status foreign key""" + if obj.status: + request = self.context.get('request') + return { + 'id': obj.status.id, + 'title': get_localized_text(obj.status.title, request), + 'slug': obj.status.slug, + 'color': obj.status.color + } + return None # serializers.py