From 8c71598e5e8234887931c3a1068f9adb6e1eace9 Mon Sep 17 00:00:00 2001 From: mohsentaba Date: Mon, 22 Dec 2025 12:31:39 +0330 Subject: [PATCH] OriginalText share link and slug added. --- apps/hadis/management/commands/slug_hadis.py | 102 +++++++++--------- .../0062_transmitteroriginaltext_slug.py | 19 ++++ ...0063_alter_transmitteroriginaltext_slug.py | 19 ++++ apps/hadis/models/transmitter.py | 36 +++++++ 4 files changed, 124 insertions(+), 52 deletions(-) create mode 100644 apps/hadis/migrations/0062_transmitteroriginaltext_slug.py create mode 100644 apps/hadis/migrations/0063_alter_transmitteroriginaltext_slug.py diff --git a/apps/hadis/management/commands/slug_hadis.py b/apps/hadis/management/commands/slug_hadis.py index c18a6e1..a72fca4 100644 --- a/apps/hadis/management/commands/slug_hadis.py +++ b/apps/hadis/management/commands/slug_hadis.py @@ -1,33 +1,34 @@ """ -Django Management Command: Regenerate HadisCorrection Slugs +Django Management Command: Regenerate TransmitterOriginalText Slugs This command: -1. Takes all existing HadisCorrection objects -2. Extracts their title (first 8 words max) +1. Takes all existing TransmitterOriginalText objects +2. Extracts their title (first N words, default 8) 3. Generates smart, short, meaningful slugs 4. Handles uniqueness with counters (-1, -2, -3, etc.) 5. REPLACES all old slug values with new ones 6. Detects and numbers duplicates automatically Usage: - python manage.py regenerate_hadis_corrections_slugs - python manage.py regenerate_hadis_corrections_slugs --max-length 75 - python manage.py regenerate_hadis_corrections_slugs --keep-words 6 - python manage.py regenerate_hadis_corrections_slugs --dry-run + python manage.py regenerate_transmitter_originaltext_slugs + python manage.py regenerate_transmitter_originaltext_slugs --max-length 75 + python manage.py regenerate_transmitter_originaltext_slugs --keep-words 6 + python manage.py regenerate_transmitter_originaltext_slugs --dry-run """ from collections import defaultdict from django.core.management.base import BaseCommand from django.db import transaction +from django.utils.text import slugify -from apps.hadis.models import HadisCorrection # ← adjust import path to your app -from utils.slug import generate_smart_slug # ← your existing helper +from apps.hadis.models import TransmitterOriginalText +from utils.slug import generate_smart_slug # your existing helper class Command(BaseCommand): help = ( - "Regenerate smart slugs for all HadisCorrection objects. " + "Regenerate smart slugs for all TransmitterOriginalText objects. " "Replaces existing slug values with optimized, short, meaningful ones. " "Automatically adds counters for duplicates." ) @@ -59,64 +60,61 @@ class Command(BaseCommand): dry_run = options["dry_run"] self.stdout.write(self.style.HTTP_INFO("=" * 80)) - self.stdout.write("🔄 HADISCORRECTION SLUG REGENERATION WITH DUPLICATE HANDLING\n") + self.stdout.write("🔄 TRANSMITTER ORIGINAL TEXT SLUG REGENERATION\n") self.stdout.write("Configuration:") self.stdout.write(f" • Max length: {max_length} chars") self.stdout.write(f" • Keep words: {keep_words} words") self.stdout.write(f" • Dry run: {'Yes (no changes)' if dry_run else 'No (will save)'}") self.stdout.write(self.style.HTTP_INFO("=" * 80) + "\n") - # Get all corrections - qs = HadisCorrection.objects.all().order_by("id") + # Get all objects + qs = TransmitterOriginalText.objects.all().order_by("id") total = qs.count() - self.stdout.write(f"Step 1: Analyzing {total} HadisCorrection objects...\n") + self.stdout.write(f"Step 1: Analyzing {total} TransmitterOriginalText objects...\n") - # Dictionary to track duplicate slugs: {base_slug: [correction_ids]} + # Dictionary to track duplicate slugs: {base_slug: [ids]} slug_map = defaultdict(list) - correction_slug_map = {} # {correction_id: data} + obj_slug_map = {} # {id: data} # First pass: Generate base slugs and identify duplicates - for correction in qs: + for obj in qs: try: - # 1) Decide which field to use as base text for slug - # Adjust this depending on your HadisCorrection model: - # - if you have `title` JSONField like Hadis: - # correction.title[0]['text'] - # - or maybe `text`, `summary`, etc. + # Extract title text from JSONField title_text = None - - # Example: using a JSONField called `title` - if getattr(correction, "title", None) and isinstance(correction.title, list): - first_item = correction.title[0] + if obj.title and isinstance(obj.title, list) and obj.title: + first_item = obj.title[0] if isinstance(first_item, dict): title_text = first_item.get("text") - # Fallback: use plain text field or id + # Fallback if no title if not title_text: - if hasattr(correction, "text") and isinstance(correction.text, str): - title_text = correction.text - else: - title_text = f"hadis-correction-{correction.id}" + # use transmitter name if available, otherwise id + base = None + if obj.transmitter and isinstance(obj.transmitter.full_name, list): + first_name = obj.transmitter.full_name[0] + if isinstance(first_name, dict): + base = first_name.get("text") + title_text = base or f"transmitter-originaltext-{obj.id}" # Generate base slug (without counter) base_slug = self._generate_base_slug( title_text, - max_length - 5, # reserve for counter + max_length - 5, # Reserve space for counter keep_words, ) - correction_slug_map[correction.id] = { + obj_slug_map[obj.id] = { "base_slug": base_slug, "title_text": title_text, - "old_slug": correction.slug or "(empty)", + "old_slug": obj.slug or "(empty)", } - slug_map[base_slug].append(correction.id) + slug_map[base_slug].append(obj.id) except Exception as e: self.stdout.write( - self.style.ERROR(f" ❌ Error analyzing HadisCorrection {correction.id}: {str(e)}") + self.style.ERROR(f" ❌ Error analyzing TransmitterOriginalText {obj.id}: {str(e)}") ) self.stdout.write("✅ Analysis complete!\n") @@ -134,11 +132,11 @@ class Command(BaseCommand): ) ) for base_slug, ids in sorted(duplicate_groups.items()): - self.stdout.write(f" • '{base_slug}' → {len(ids)} corrections") - for idx, corr_id in enumerate(ids): + self.stdout.write(f" • '{base_slug}' → {len(ids)} objects") + for idx, obj_id in enumerate(ids): counter = "" if idx == 0 else f"-{idx}" self.stdout.write( - f" - HadisCorrection {corr_id}: {base_slug}{counter}" + f" - TransmitterOriginalText {obj_id}: {base_slug}{counter}" ) else: self.stdout.write("✅ No duplicates found! All slugs are unique.\n") @@ -150,9 +148,9 @@ class Command(BaseCommand): unchanged = 0 errors = [] - for correction in qs: + for obj in qs: try: - slug_info = correction_slug_map.get(correction.id) + slug_info = obj_slug_map.get(obj.id) if not slug_info: continue @@ -162,8 +160,9 @@ class Command(BaseCommand): # If this slug has duplicates, add counter if len(slug_map[base_slug]) > 1: duplicate_ids = slug_map[base_slug] - position = duplicate_ids.index(correction.id) + position = duplicate_ids.index(obj.id) + # First one gets no counter, rest get -1, -2, etc. if position == 0: new_slug = base_slug else: @@ -176,21 +175,22 @@ class Command(BaseCommand): else: new_slug = base_slug - changed = correction.slug != new_slug + changed = obj.slug != new_slug if changed: self.stdout.write( - f" [{correction.id:5d}] {old_slug:45s} → {new_slug}" + f" [{obj.id:5d}] {old_slug:45s} → {new_slug}" ) + if not dry_run: - correction.slug = new_slug - correction.save(update_fields=["slug"]) + obj.slug = new_slug + obj.save(update_fields=["slug"]) updated += 1 else: unchanged += 1 except Exception as e: - error_msg = f"HadisCorrection {correction.id}: {str(e)}" + error_msg = f"TransmitterOriginalText {obj.id}: {str(e)}" self.stdout.write(self.style.ERROR(f" ❌ {error_msg}")) errors.append(error_msg) @@ -226,7 +226,7 @@ class Command(BaseCommand): if not dry_run and updated > 0: self.stdout.write( self.style.SUCCESS( - f"✅ Successfully regenerated {updated} HadisCorrection slug(s)!" + f"✅ Successfully regenerated {updated} slug(s) for TransmitterOriginalText!" ) ) elif dry_run: @@ -239,12 +239,10 @@ class Command(BaseCommand): def _generate_base_slug(self, text: str, max_length: int, keep_words: int) -> str: """ Generate a base slug without counter. - Returns the base slug that might be used for multiple corrections. + Returns the base slug that might be used for multiple objects. """ - from django.utils.text import slugify - if not text or not isinstance(text, str): - return "hadis-correction-unknown" + return "transmitter-originaltext-unknown" # Extract first N words words = text.strip().split()[:keep_words] diff --git a/apps/hadis/migrations/0062_transmitteroriginaltext_slug.py b/apps/hadis/migrations/0062_transmitteroriginaltext_slug.py new file mode 100644 index 0000000..a24f475 --- /dev/null +++ b/apps/hadis/migrations/0062_transmitteroriginaltext_slug.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.27 on 2025-12-22 12:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0061_alter_hadiscorrection_slug"), + ] + + operations = [ + migrations.AddField( + model_name="transmitteroriginaltext", + name="slug", + field=models.SlugField( + blank=True, max_length=255, null=True, verbose_name="slug" + ), + ), + ] diff --git a/apps/hadis/migrations/0063_alter_transmitteroriginaltext_slug.py b/apps/hadis/migrations/0063_alter_transmitteroriginaltext_slug.py new file mode 100644 index 0000000..7dc9e2b --- /dev/null +++ b/apps/hadis/migrations/0063_alter_transmitteroriginaltext_slug.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.27 on 2025-12-22 12:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("hadis", "0062_transmitteroriginaltext_slug"), + ] + + operations = [ + migrations.AlterField( + model_name="transmitteroriginaltext", + 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 1f6bb51..00c2276 100644 --- a/apps/hadis/models/transmitter.py +++ b/apps/hadis/models/transmitter.py @@ -5,6 +5,8 @@ from django.utils.translation import gettext_lazy as _ from filer.fields.image import FilerImageField from django.utils.text import slugify from typing import Optional +from utils.slug import generate_smart_slug +from django.conf import settings @@ -349,6 +351,7 @@ class TransmitterOriginalText(models.Model): verbose_name=_('transmitter'), related_name='originaltexts' ) + slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True) title = models.JSONField(default = list , verbose_name=_('Title')) text = models.JSONField(default = list , verbose_name=_('Text')) translation = models.JSONField(verbose_name=_('translation'), default=list) @@ -356,6 +359,39 @@ class TransmitterOriginalText(models.Model): def __str__(self): return f"{self.title[0]['text']} by {self.transmitter.full_name[0]['text']}" + + def save(self, *args, **kwargs): + """ + Override save to automatically generate smart slugs. + """ + + # Generate slug if not already set + if not self.slug and self.title: + # Extract title text + title_text = None + if isinstance(self.title, list) and self.title: + first_item = self.title[0] + if isinstance(first_item, dict): + title_text = first_item.get("text") + + # Generate smart slug + if title_text: + self.slug = generate_smart_slug( + text=title_text, + model_class=TransmitterOriginalText, + max_length=100, # ← Adjust max length here + keep_words=8, # ← Limit to 8 words (your requirement) + instance=self, + ) + else: + # Fallback if title is empty + self.slug = f"original-text-{self.transmitter.slug}-{self.id or 'unknown'}" + + # Call parent save + if not self.share_link: + self.share_link = f"{settings.SITE_DOMAIN}/hadis/narrators/{self.transmitter.slug}/original-texts/{self.slug}" + super().save(*args, **kwargs) + def get_title(self,lang): """