diff --git a/apps/hadis/docs.py b/apps/hadis/docs.py index 8b8c89c..0135e91 100644 --- a/apps/hadis/docs.py +++ b/apps/hadis/docs.py @@ -779,6 +779,7 @@ transmitter_list_swagger = swagger_auto_schema( { "id": 56, "full_name": "Абу Дауд ас-Сиджистани", + "slug": "abdullah-ibn-abbas", "birth_year_hijri": 202, "death_year_hijri": 275, "known_as": "Imam Abu Daud", @@ -790,6 +791,7 @@ transmitter_list_swagger = swagger_auto_schema( { "id": 51, "full_name": "Мухаммад ибн Якуб Кулейни", + "slug": "abdullah-ibn-abbas", "birth_year_hijri": 250, "death_year_hijri": 329, "known_as": "Thiqat al-Islam", @@ -816,10 +818,10 @@ transmitter_detail_swagger = swagger_auto_schema( tags=['Hadis'], manual_parameters=[ openapi.Parameter( - 'transmitters_id', + 'narrator_slug', openapi.IN_PATH, - description="ID of the transmitter", - type=openapi.TYPE_INTEGER, + description="Slug of narrator", + type=openapi.TYPE_STRING, required=True ) ], @@ -1001,12 +1003,12 @@ transmitter_opinion_swagger = swagger_auto_schema( tags=['Hadis'], manual_parameters=[ openapi.Parameter( - 'transmitters_id', + 'narrator_slug', openapi.IN_PATH, - description="Unique identifier of the transmitter (narrator). Must be a valid transmitter ID that exists in the system.", - type=openapi.TYPE_INTEGER, + description="Narrator's slug", + type=openapi.TYPE_STRING, required=True, - example=56 + example="abdullah-ibn-abbas" ) ], responses={ @@ -1084,12 +1086,12 @@ transmitter_original_text_swagger = swagger_auto_schema( tags=['Hadis'], manual_parameters=[ openapi.Parameter( - 'transmitters_id', + 'narrator_slug', openapi.IN_PATH, - description="Unique identifier of the transmitter (narrator). Must be a valid transmitter ID that exists in the system.", - type=openapi.TYPE_INTEGER, + description="Narrator's slug", + type=openapi.TYPE_STRING, required=True, - example=56 + example="abdullah-ibn-abbas" ) ], responses={ diff --git a/apps/hadis/management/commands/generate_transmit_slug.py b/apps/hadis/management/commands/generate_transmit_slug.py new file mode 100644 index 0000000..dfbfbc5 --- /dev/null +++ b/apps/hadis/management/commands/generate_transmit_slug.py @@ -0,0 +1,71 @@ +# Create this file: yourapp/management/commands/generate_transmitter_slugs.py + +from django.core.management.base import BaseCommand +from django.utils.text import slugify +from apps.hadis.models import Transmitters # adjust import path as needed + + +class Command(BaseCommand): + help = 'Generate unique slugs for all transmitters' + + def add_arguments(self, parser): + parser.add_argument( + '--regenerate', + action='store_true', + help='Regenerate slugs even if they already exist', + ) + + def handle(self, *args, **options): + regenerate = options['regenerate'] + + transmitters = Transmitters.objects.all() + updated_count = 0 + skipped_count = 0 + + self.stdout.write( + self.style.SUCCESS(f'\n📝 Processing {transmitters.count()} transmitters...\n') + ) + + for transmitter in transmitters: + # Skip if slug exists and regenerate is False + if transmitter.slug and not regenerate: + self.stdout.write( + self.style.WARNING(f"⊘ Skipped: {transmitter.full_name} (slug exists)") + ) + skipped_count += 1 + continue + + # Generate base slug from full_name + base_slug = slugify(transmitter.full_name, allow_unicode=True) + + if not base_slug: + self.stdout.write( + self.style.ERROR(f"✗ Error: {transmitter.full_name} - Cannot generate slug from empty name") + ) + continue + + # Ensure uniqueness + slug = base_slug + counter = 1 + + while Transmitters.objects.filter(slug=slug).exclude(pk=transmitter.pk).exists(): + slug = f"{base_slug}-{counter}" + counter += 1 + + # Update the transmitter + transmitter.slug = slug + transmitter.save() + updated_count += 1 + + self.stdout.write( + self.style.SUCCESS(f"✓ Generated: {transmitter.full_name} → {slug}") + ) + + # Print summary + self.stdout.write("\n" + "="*70) + self.stdout.write(self.style.SUCCESS("SLUG GENERATION SUMMARY")) + self.stdout.write("="*70) + self.stdout.write(f"✓ Generated: {updated_count}") + self.stdout.write(f"⊘ Skipped: {skipped_count}") + self.stdout.write(f"📊 Total: {transmitters.count()}") + self.stdout.write("="*70 + "\n") diff --git a/apps/hadis/migrations/0046_transmitters_slug.py b/apps/hadis/migrations/0046_transmitters_slug.py new file mode 100644 index 0000000..6ae908a --- /dev/null +++ b/apps/hadis/migrations/0046_transmitters_slug.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.9 on 2025-12-17 13:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0045_bookreference_publisher"), + ] + + operations = [ + migrations.AddField( + model_name="transmitters", + name="slug", + field=models.SlugField( + blank=True, max_length=255, unique=True, verbose_name="slug" + ), + ), + ] diff --git a/apps/hadis/migrations/0047_remove_transmitters_slug.py b/apps/hadis/migrations/0047_remove_transmitters_slug.py new file mode 100644 index 0000000..e4fd14e --- /dev/null +++ b/apps/hadis/migrations/0047_remove_transmitters_slug.py @@ -0,0 +1,16 @@ +# Generated by Django 5.2.9 on 2025-12-17 13:26 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0046_transmitters_slug"), + ] + + operations = [ + migrations.RemoveField( + model_name="transmitters", + name="slug", + ), + ] diff --git a/apps/hadis/migrations/0048_transmitters_slug.py b/apps/hadis/migrations/0048_transmitters_slug.py new file mode 100644 index 0000000..0980607 --- /dev/null +++ b/apps/hadis/migrations/0048_transmitters_slug.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.9 on 2025-12-17 13:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0047_remove_transmitters_slug"), + ] + + operations = [ + migrations.AddField( + model_name="transmitters", + name="slug", + field=models.SlugField( + blank=True, max_length=255, null=True, verbose_name="slug" + ), + ), + ] diff --git a/apps/hadis/migrations/0049_alter_transmitters_slug.py b/apps/hadis/migrations/0049_alter_transmitters_slug.py new file mode 100644 index 0000000..d8c1263 --- /dev/null +++ b/apps/hadis/migrations/0049_alter_transmitters_slug.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.9 on 2025-12-17 13:34 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0048_transmitters_slug"), + ] + + operations = [ + migrations.AlterField( + model_name="transmitters", + name="slug", + field=models.SlugField( + blank=True, max_length=255, unique=True, verbose_name="slug" + ), + ), + ] diff --git a/apps/hadis/models/transmitter.py b/apps/hadis/models/transmitter.py index 0226e7e..e02ffc0 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -59,6 +59,7 @@ class Transmitters(models.Model): kunya = models.CharField(max_length=255, verbose_name=_('Kunya'), blank=True, null=True, help_text=_('e.g., Abu Abdullah')) known_as = models.CharField(max_length=255, verbose_name=_('Known As'), blank=True, null=True) nickname = models.CharField(max_length=255, verbose_name=_('Nickname/Laqab'), blank=True, null=True) + slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True) # Geographic Information origin = models.CharField(max_length=255, verbose_name=_('Origin'), blank=True, null=True, help_text=_('Place of origin')) @@ -114,6 +115,17 @@ class Transmitters(models.Model): verbose_name_plural = _('Transmitters') ordering = ('full_name',) + def save(self, *args, **kwargs): + if not self.slug: + base_slug = slugify(self.full_name, allow_unicode=True) + slug = base_slug + counter = 1 + while Transmitters.objects.filter(slug=slug).exclude(pk=self.pk).exists(): + slug = f"{base_slug}-{counter}" + counter += 1 + self.slug = slug + super().save(*args, **kwargs) + def __str__(self): return self.full_name diff --git a/apps/hadis/serializers/hadis.py b/apps/hadis/serializers/hadis.py index d32969d..1455fc5 100644 --- a/apps/hadis/serializers/hadis.py +++ b/apps/hadis/serializers/hadis.py @@ -196,7 +196,7 @@ class TransmitterSerializer(serializers.ModelSerializer): class Meta: model = Transmitters fields = [ - 'id', 'full_name', 'birth_year_hijri', 'death_year_hijri', + 'id', 'full_name', 'slug','birth_year_hijri', 'death_year_hijri', "known_as",'nickname','reliability','madhhab','generation' ] diff --git a/apps/hadis/urls.py b/apps/hadis/urls.py index 8a5735b..8810a90 100644 --- a/apps/hadis/urls.py +++ b/apps/hadis/urls.py @@ -25,9 +25,9 @@ urlpatterns = [ path('categories//', CategoriesBySectView.as_view(), name='categories-by-sect'), path('categories/', CategoriesView.as_view(), name='categories'), path('narrators/',TransmitterView.as_view(), name='narrators'), - path('narrators/',TransmitterDetailView.as_view(), name='narrator-detail'), - path('narrators//opinions',TransmitterOpinionView.as_view(), name='narrator-opinions'), - path('narrators//original_texts',TransmitterOriginalTextView.as_view(), name='narrator-original-texts'), + path('narrators/',TransmitterDetailView.as_view(), name='narrator-detail'), + path('narrators//opinions',TransmitterOpinionView.as_view(), name='narrator-opinions'), + path('narrators//original_texts',TransmitterOriginalTextView.as_view(), name='narrator-original-texts'), path('references/',BookReferencesView.as_view(), name='references'), path('references/',BookDetailView.as_view(), name='reference-detail'), path('references/attributes/',BookAttributeView.as_view(), name='book-attributes'), diff --git a/apps/hadis/views/transmitter.py b/apps/hadis/views/transmitter.py index 4b85ed0..114c217 100644 --- a/apps/hadis/views/transmitter.py +++ b/apps/hadis/views/transmitter.py @@ -39,16 +39,16 @@ class TransmitterView(ListAPIView): class TransmitterDetailView(RetrieveAPIView): serializer_class = TransmitterDetailSerializer - lookup_field = 'id' - lookup_url_kwarg = 'transmitters_id' + lookup_field = 'slug' + lookup_url_kwarg = 'narrator_slug' @transmitter_detail_swagger def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) def get_queryset(self): - input = self.kwargs['transmitters_id'] - return Transmitters.objects.filter(id=input) + input = self.kwargs.get('narrator_slug') + return Transmitters.objects.filter(slug=input) class TransmitterOpinionView(ListAPIView): serializer_class = TransmitterOpinionSerializer @@ -58,8 +58,16 @@ class TransmitterOpinionView(ListAPIView): return self.list(request, *args, **kwargs) def get_queryset(self): - transmitter_id = self.kwargs['transmitters_id'] - return TransmitterOpinion.objects.filter(transmitter_id=transmitter_id) + narrator_slug = self.kwargs.get('narrator_slug') + return TransmitterOpinion.objects.filter( + transmitter__slug=narrator_slug + ).select_related( + 'transmitter' # Essential if serializer includes transmitter data + ).prefetch_related( + # Add any nested relations from TransmitterOriginalTextSerializer + # 'translations', + # 'manuscript_references', + ) class TransmitterOriginalTextView(ListAPIView): @@ -70,8 +78,17 @@ class TransmitterOriginalTextView(ListAPIView): return self.list(request, *args, **kwargs) def get_queryset(self): - transmitter_id = self.kwargs['transmitters_id'] - return TransmitterOriginalText.objects.filter(transmitter_id=transmitter_id) + narrator_slug = self.kwargs.get('narrator_slug') + + return TransmitterOriginalText.objects.filter( + transmitter__slug=narrator_slug + ).select_related( + 'transmitter' # Essential if serializer includes transmitter data + ).prefetch_related( + # Add any nested relations from TransmitterOriginalTextSerializer + # 'translations', + # 'manuscript_references', + ) class TransmitterSyncView(ListAPIView):