Browse Source

HadisCorrection slug and link fixed.

master
Mohsen Taba 5 months ago
parent
commit
c007db05cd
  1. 109
      apps/hadis/management/commands/slug_hadis.py
  2. 19
      apps/hadis/migrations/0060_hadiscorrection_slug.py
  3. 19
      apps/hadis/migrations/0061_alter_hadiscorrection_slug.py
  4. 35
      apps/hadis/models/hadis.py

109
apps/hadis/management/commands/slug_hadis.py

@ -1,38 +1,33 @@
"""
Django Management Command: Regenerate Hadis Slugs
Django Management Command: Regenerate HadisCorrection Slugs
This command:
1. Takes all existing Hadis objects
1. Takes all existing HadisCorrection objects
2. Extracts their title (first 8 words max)
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
Key feature:
- If multiple hadis have the same shortened title, they get numbered:
- hadis-1830: "достоинство-молитвы-и-ее-место" "достоинство-молитвы-и-ее-место"
- hadis-1831: same title "достоинство-молитвы-и-ее-место-1"
- hadis-1832: same title "достоинство-молитвы-и-ее-место-2"
Usage:
python manage.py regenerate_hadis_slugs
python manage.py regenerate_hadis_slugs --max-length 75
python manage.py regenerate_hadis_slugs --keep-words 6
python manage.py regenerate_hadis_slugs --dry-run
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
"""
from collections import defaultdict
from django.core.management.base import BaseCommand
from django.db import transaction
from collections import defaultdict
from apps.hadis.models import Hadis
from utils.slug import generate_smart_slug
from apps.hadis.models import HadisCorrection # ← adjust import path to your app
from utils.slug import generate_smart_slug # ← your existing helper
class Command(BaseCommand):
help = (
"Regenerate smart slugs for all Hadis objects. "
"Regenerate smart slugs for all HadisCorrection objects. "
"Replaces existing slug values with optimized, short, meaningful ones. "
"Automatically adds counters for duplicates."
)
@ -64,59 +59,68 @@ class Command(BaseCommand):
dry_run = options["dry_run"]
self.stdout.write(self.style.HTTP_INFO("=" * 80))
self.stdout.write("🔄 HADIS SLUG REGENERATION WITH DUPLICATE HANDLING\n")
self.stdout.write(f"Configuration:")
self.stdout.write("🔄 HADISCORRECTION SLUG REGENERATION WITH DUPLICATE HANDLING\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 hadis
qs = Hadis.objects.all().order_by("id")
# Get all corrections
qs = HadisCorrection.objects.all().order_by("id")
total = qs.count()
self.stdout.write(f"Step 1: Analyzing {total} hadis objects...\n")
self.stdout.write(f"Step 1: Analyzing {total} HadisCorrection objects...\n")
# Dictionary to track duplicate slugs: {base_slug: [hadis_ids]}
# Dictionary to track duplicate slugs: {base_slug: [correction_ids]}
slug_map = defaultdict(list)
hadis_slug_map = {} # {hadis_id: generated_slug}
correction_slug_map = {} # {correction_id: data}
# First pass: Generate slugs and identify duplicates
for hadis in qs:
# First pass: Generate base slugs and identify duplicates
for correction in qs:
try:
# Extract title text
# 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.
title_text = None
if hadis.title and isinstance(hadis.title, list) and hadis.title:
first_item = hadis.title[0]
# Example: using a JSONField called `title`
if getattr(correction, "title", None) and isinstance(correction.title, list):
first_item = correction.title[0]
if isinstance(first_item, dict):
title_text = first_item.get("text")
# Fallback if no title
# Fallback: use plain text field or id
if not title_text:
title_text = f"hadis-{hadis.number or hadis.id}"
if hasattr(correction, "text") and isinstance(correction.text, str):
title_text = correction.text
else:
title_text = f"hadis-correction-{correction.id}"
# Generate base slug (without counter)
base_slug = self._generate_base_slug(
title_text,
max_length - 5, # Reserve space for counter
max_length - 5, # reserve for counter
keep_words,
)
hadis_slug_map[hadis.id] = {
correction_slug_map[correction.id] = {
"base_slug": base_slug,
"title_text": title_text,
"old_slug": hadis.slug or "(empty)",
"old_slug": correction.slug or "(empty)",
}
slug_map[base_slug].append(hadis.id)
slug_map[base_slug].append(correction.id)
except Exception as e:
self.stdout.write(
self.style.ERROR(f" ❌ Error analyzing Hadis {hadis.id}: {str(e)}")
self.style.ERROR(f" ❌ Error analyzing HadisCorrection {correction.id}: {str(e)}")
)
self.stdout.write(f"✅ Analysis complete!\n")
self.stdout.write(f"Step 2: Detecting duplicates...\n")
self.stdout.write("✅ Analysis complete!\n")
self.stdout.write("Step 2: Detecting duplicates...\n")
# Identify groups with duplicates
duplicate_groups = {
@ -130,11 +134,11 @@ class Command(BaseCommand):
)
)
for base_slug, ids in sorted(duplicate_groups.items()):
self.stdout.write(f"'{base_slug}' → {len(ids)} hadis")
for idx, hadis_id in enumerate(ids):
self.stdout.write(f"'{base_slug}' → {len(ids)} corrections")
for idx, corr_id in enumerate(ids):
counter = "" if idx == 0 else f"-{idx}"
self.stdout.write(
f" - Hadis {hadis_id}: {base_slug}{counter}"
f" - HadisCorrection {corr_id}: {base_slug}{counter}"
)
else:
self.stdout.write("✅ No duplicates found! All slugs are unique.\n")
@ -146,9 +150,9 @@ class Command(BaseCommand):
unchanged = 0
errors = []
for hadis in qs:
for correction in qs:
try:
slug_info = hadis_slug_map.get(hadis.id)
slug_info = correction_slug_map.get(correction.id)
if not slug_info:
continue
@ -157,11 +161,9 @@ class Command(BaseCommand):
# If this slug has duplicates, add counter
if len(slug_map[base_slug]) > 1:
# Find position of this hadis in the duplicate group
duplicate_ids = slug_map[base_slug]
position = duplicate_ids.index(hadis.id)
position = duplicate_ids.index(correction.id)
# First one gets no counter, rest get -1, -2, etc.
if position == 0:
new_slug = base_slug
else:
@ -174,22 +176,21 @@ class Command(BaseCommand):
else:
new_slug = base_slug
changed = hadis.slug != new_slug
changed = correction.slug != new_slug
if changed:
self.stdout.write(
f" [{hadis.id:5d}] {old_slug:45s} → {new_slug}"
f" [{correction.id:5d}] {old_slug:45s} → {new_slug}"
)
if not dry_run:
hadis.slug = new_slug
hadis.save(update_fields=["slug"])
correction.slug = new_slug
correction.save(update_fields=["slug"])
updated += 1
else:
unchanged += 1
except Exception as e:
error_msg = f"Hadis {hadis.id}: {str(e)}"
error_msg = f"HadisCorrection {correction.id}: {str(e)}"
self.stdout.write(self.style.ERROR(f" ❌ {error_msg}"))
errors.append(error_msg)
@ -225,7 +226,7 @@ class Command(BaseCommand):
if not dry_run and updated > 0:
self.stdout.write(
self.style.SUCCESS(
f"✅ Successfully regenerated {updated} slug(s)!"
f"✅ Successfully regenerated {updated} HadisCorrection slug(s)!"
)
)
elif dry_run:
@ -238,12 +239,12 @@ 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 hadis.
Returns the base slug that might be used for multiple corrections.
"""
from django.utils.text import slugify
if not text or not isinstance(text, str):
return "hadis-unknown"
return "hadis-correction-unknown"
# Extract first N words
words = text.strip().split()[:keep_words]

19
apps/hadis/migrations/0060_hadiscorrection_slug.py

@ -0,0 +1,19 @@
# Generated by Django 4.2.27 on 2025-12-22 11:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("hadis", "0059_alter_hadis_slug"),
]
operations = [
migrations.AddField(
model_name="hadiscorrection",
name="slug",
field=models.SlugField(
blank=True, max_length=255, null=True, verbose_name="slug"
),
),
]

19
apps/hadis/migrations/0061_alter_hadiscorrection_slug.py

@ -0,0 +1,19 @@
# Generated by Django 4.2.27 on 2025-12-22 12:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("hadis", "0060_hadiscorrection_slug"),
]
operations = [
migrations.AlterField(
model_name="hadiscorrection",
name="slug",
field=models.SlugField(
blank=True, max_length=255, unique=True, verbose_name="slug"
),
),
]

35
apps/hadis/models/hadis.py

@ -289,7 +289,7 @@ class Hadis(models.Model):
def save(self, *args, **kwargs):
# ساخت share_link قبل از ذخیره
if not self.share_link:
self.share_link = f"{settings.SITE_DOMAIN}/hadis/{self.id}"
self.share_link = f"{settings.SITE_DOMAIN}/hadis/{self.slug}"
super().save(*args, **kwargs)
class Meta:
@ -372,6 +372,7 @@ class ReferenceImage(models.Model):
class HadisCorrection(models.Model):
hadis = models.ForeignKey(Hadis, verbose_name=_("hadis correction"), on_delete=models.CASCADE)
title = models.JSONField(default = list , verbose_name=_('Title'))
slug = models.SlugField(max_length=255, verbose_name=_('slug'), blank=True,unique=True)
description =models.JSONField(default = list , verbose_name=_('Description'))
translation = models.JSONField(verbose_name=_("translation"), default=list)
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("created at"))
@ -386,6 +387,38 @@ class HadisCorrection(models.Model):
def __str__(self):
return f"{self.hadis.number} - {self.title[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=HadisCorrection,
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"correction-{self.hadis.slug}-{self.id}"
# Call parent save
if not self.share_link:
self.share_link = f"{settings.SITE_DOMAIN}/hadis/{self.hadis.slug}/corrections/{self.slug}"
super().save(*args, **kwargs)
def get_title(self,lang):
"""
Get title for a specific language

Loading…
Cancel
Save