Browse Source

OriginalText share link and slug added.

master
Mohsen Taba 5 months ago
parent
commit
8c71598e5e
  1. 102
      apps/hadis/management/commands/slug_hadis.py
  2. 19
      apps/hadis/migrations/0062_transmitteroriginaltext_slug.py
  3. 19
      apps/hadis/migrations/0063_alter_transmitteroriginaltext_slug.py
  4. 36
      apps/hadis/models/transmitter.py

102
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]

19
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"
),
),
]

19
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"
),
),
]

36
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)
@ -357,6 +360,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):
"""
Get title for a specific language

Loading…
Cancel
Save