Browse Source
feat(api): add optional pagination, category search, reference detail and relation management
master
feat(api): add optional pagination, category search, reference detail and relation management
master
17 changed files with 939 additions and 23 deletions
-
21apps/article/views.py
-
6apps/article/views_admin.py
-
28apps/hadis/serializers/serializers_admin.py
-
54apps/hadis/views_admin.py
-
16apps/library/serializers_admin.py
-
4apps/library/urls.py
-
35apps/library/views_admin.py
-
4apps/podcast/serializers_admin.py
-
18apps/podcast/views.py
-
25apps/podcast/views_admin.py
-
4apps/video/serializers_admin.py
-
18apps/video/views.py
-
25apps/video/views_admin.py
-
366scripts/generate_wise_sayings_hadiths.py
-
95scripts/resolve_category_hadis_conflicts.py
-
234scripts/update_readable_slugs.py
-
7utils/pagination.py
@ -0,0 +1,366 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
""" |
||||
|
Script to generate 50 realistic hadiths for the category 'wise-sayings'. |
||||
|
|
||||
|
Features: |
||||
|
- Connects to the 'wise-sayings' category (HadisCategory) |
||||
|
- Generates rich multilingual content (RU, EN, FA, AR) |
||||
|
- Attaches transmitters and book references |
||||
|
- Assigns readable unique slugs |
||||
|
- Supports --dry-run flag |
||||
|
""" |
||||
|
|
||||
|
import os |
||||
|
import sys |
||||
|
import random |
||||
|
import time |
||||
|
import argparse |
||||
|
from pathlib import Path |
||||
|
from django.utils.text import slugify |
||||
|
|
||||
|
# Force UTF-8 stdout for Windows consoles |
||||
|
if hasattr(sys.stdout, "reconfigure"): |
||||
|
sys.stdout.reconfigure(encoding="utf-8") |
||||
|
|
||||
|
BASE_DIR = Path(__file__).resolve().parent.parent |
||||
|
sys.path.insert(0, str(BASE_DIR)) |
||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.base') |
||||
|
|
||||
|
import django |
||||
|
django.setup() |
||||
|
|
||||
|
from django.db import transaction |
||||
|
from apps.hadis.models import ( |
||||
|
HadisCategory, Hadis, HadisStatus, HadisTag, |
||||
|
Transmitters, HadisTransmitter, HadisReference |
||||
|
) |
||||
|
from apps.hadis.models.reference import BookReference |
||||
|
|
||||
|
# 50 Wisdom-themed Hadith templates |
||||
|
WISDOM_TEMPLATES = [ |
||||
|
{ |
||||
|
"narrator_ru": "Повелитель верующих Али (мир ему)", |
||||
|
"narrator_en": "Amir al-Mu'minin Ali (peace be upon him)", |
||||
|
"narrator_fa": "امیرالمؤمنین علی (ع)", |
||||
|
"title_ru": "Ценность человека и его знания", |
||||
|
"title_en": "The Worth of a Person and Their Knowledge", |
||||
|
"title_fa": "ارزش هر انسان و دانش او", |
||||
|
"text": "قِيمَةُ كُلِّ امْرِئٍ مَا يُحْسِنُهُ.", |
||||
|
"trans_ru": "Ценность каждого человека заключается в том, что он делает наилучшим образом.", |
||||
|
"trans_en": "The value of every person lies in that which they excel at.", |
||||
|
"trans_fa": "ارزش هر انسانی به اندازه مهارتی است که آن را نیکو انجام میدهد.", |
||||
|
"exp_ru": "Этот афоризм подчеркивает значимость практических навыков, знаний и добродетелей.", |
||||
|
"exp_en": "This profound saying emphasizes that true human dignity comes from virtue and knowledge." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Посланник Аллаха (да благословит Аллах его и род его)", |
||||
|
"narrator_en": "The Messenger of Allah (peace be upon him and his family)", |
||||
|
"narrator_fa": "پیامبر اکرم (ص)", |
||||
|
"title_ru": "Мудрость как потерянное достояние верующего", |
||||
|
"title_en": "Wisdom as the Lost Treasure of the Believer", |
||||
|
"title_fa": "حکمت؛ گمشده مومن", |
||||
|
"text": "الْحِكْمَةُ ضَالَّةُ الْمُؤْمِنِ، فَخُذِ الْحِكْمَةَ وَلَوْ مِنْ أَهْلِ النِّفَاقِ.", |
||||
|
"trans_ru": "Мудрость — потерянное достояние верующего; берите же мудрость, даже если она исходит от лицемера.", |
||||
|
"trans_en": "Wisdom is the lost property of the believer; seek wisdom wherever it may be found.", |
||||
|
"trans_fa": "حکمت گمشده مؤمن است، پس حکمت را فرا گیرید حتی اگر از اهل نفاق باشد.", |
||||
|
"exp_ru": "Истина ценна сама по себе вне зависимости от того, кто ее произносит.", |
||||
|
"exp_en": "Truth and wisdom possess intrinsic value regardless of their source." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Имам Джафар ас-Садик (мир ему)", |
||||
|
"narrator_en": "Imam Ja'far al-Sadiq (peace be upon him)", |
||||
|
"narrator_fa": "امام جعفر صادق (ع)", |
||||
|
"title_ru": "Разум и опора человека", |
||||
|
"title_en": "Intellect as the Pillar of Humanity", |
||||
|
"title_fa": "عقل؛ ستون انسانیت", |
||||
|
"text": "دِعَامَةُ الإِنْسَانِ الْعَقْلُ، وَالْعَقْلُ مِنْهُ الْفِطْنَةُ وَالْفَهْمُ وَالْحِفْظُ وَالْعِلْمُ.", |
||||
|
"trans_ru": "Опора человека — это разум, а от разума происходят сообразительность, понимание, память и знание.", |
||||
|
"trans_en": "The pillar of a human being is the intellect, from which stem perception, understanding, memory, and knowledge.", |
||||
|
"trans_fa": "ستون و قوام انسان، عقل است؛ و از عقل زیرکی، فهم، حفظ و علم سرچشمه میگیرد.", |
||||
|
"exp_ru": "Разум является фундаментом для нравственного и интеллектуального развития личности.", |
||||
|
"exp_en": "Intellect serves as the essential bedrock for spiritual and practical enlightenment." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Повелитель верующих Али (мир ему)", |
||||
|
"narrator_en": "Amir al-Mu'minin Ali (peace be upon him)", |
||||
|
"narrator_fa": "امیرالمؤمنین علی (ع)", |
||||
|
"title_ru": "Молчание и спасение языка", |
||||
|
"title_en": "Silence and the Guarding of Speech", |
||||
|
"title_fa": "سکوت و پاسداری از زبان", |
||||
|
"text": "إِذَا تَمَّ الْعَقْلُ نَقَصَ الْكَلَامُ.", |
||||
|
"trans_ru": "Когда разум достигает совершенства, речь становится немногословной.", |
||||
|
"trans_en": "When the intellect reaches perfection, speech becomes concise.", |
||||
|
"trans_fa": "هنگامی که خرد به کمال رسد، سخن گفتن کاهش مییابد.", |
||||
|
"exp_ru": "Зрелый разум побуждает человека взвешивать каждое слово перед произнесением.", |
||||
|
"exp_en": "A mature mind naturally values purposeful reflection over excessive talking." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Посланник Аллаха (да благословит Аллах его и род его)", |
||||
|
"narrator_en": "The Messenger of Allah (peace be upon him and his family)", |
||||
|
"narrator_fa": "پیامبر اکرم (ص)", |
||||
|
"title_ru": "Доброе слово как милостыня", |
||||
|
"title_en": "A Good Word is Charity", |
||||
|
"title_fa": "کلام نیکو صدقه است", |
||||
|
"text": "الْكَلِمَةُ الطَّيِّبَةُ صَدَقَةٌ.", |
||||
|
"trans_ru": "Доброе, благое слово — это милостыня (садака).", |
||||
|
"trans_en": "A kind and pleasant word is an act of charity.", |
||||
|
"trans_fa": "سخن پاک و نیکو صدقه است.", |
||||
|
"exp_ru": "Даже доброе отношение и утешительное слово приравниваются к благодеяниям.", |
||||
|
"exp_en": "Kind words uplift hearts and constitute noble spiritual contributions." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Имам Мухаммад аль-Бакир (мир ему)", |
||||
|
"narrator_en": "Imam Muhammad al-Baqir (peace be upon him)", |
||||
|
"narrator_fa": "امام محمد باقر (ع)", |
||||
|
"title_ru": "Искренность в делах и намерениях", |
||||
|
"title_en": "Sincerity in Actions and Intentions", |
||||
|
"title_fa": "اخلاص در عمل و نیت", |
||||
|
"text": "الإِبْقَاءُ عَلَى الْعَمَلِ حَتَّى يَخْلُصَ أَشَدُّ مِنَ الْعَمَلِ.", |
||||
|
"trans_ru": "Сохранить дело в чистоте и искренности труднее, чем совершить само дело.", |
||||
|
"trans_en": "Preserving the purity of an action is more demanding than the act itself.", |
||||
|
"trans_fa": "پایداری بر اخلاص در عمل سختتر از خود عمل است.", |
||||
|
"exp_ru": "Искренность требует непрерывной внутренней работы и защиты от тщеславия.", |
||||
|
"exp_en": "Sustained purity of intention safeguards noble actions from vanity." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Повелитель верующих Али (мир ему)", |
||||
|
"narrator_en": "Amir al-Mu'minin Ali (peace be upon him)", |
||||
|
"narrator_fa": "امیرالمؤمنین علی (ع)", |
||||
|
"title_ru": "Терпение как вершина веры", |
||||
|
"title_en": "Patience as the Summit of Faith", |
||||
|
"title_fa": "صبر؛ راس ایمان", |
||||
|
"text": "الصَّبْرُ مِنَ الإِيمَانِ كَالرَّأْسِ مِنَ الْجَسَدِ.", |
||||
|
"trans_ru": "Терпение в вере подобно голове на теле: нет веры у того, у кого нет терпения.", |
||||
|
"trans_en": "Patience is to faith what the head is to the body.", |
||||
|
"trans_fa": "جایگاه صبر در ایمان همانند جایگاه سر نسبت به بدن است.", |
||||
|
"exp_ru": "Без терпения невозможно удержать духовные и нравственные ориентиры в жизни.", |
||||
|
"exp_en": "Patience stabilizes human resolve in the face of life's unpredictable trials." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Имам Али ар-Рида (мир ему)", |
||||
|
"narrator_en": "Imam Ali al-Rida (peace be upon him)", |
||||
|
"narrator_fa": "امام علی بن موسی الرضا (ع)", |
||||
|
"title_ru": "Дружба с разумом", |
||||
|
"title_en": "Friendship with Intellect", |
||||
|
"title_fa": "دوستی با خرد", |
||||
|
"text": "صَدِيقُ كُلِّ امْرِئٍ عَقْلُهُ، وَعَدُوُّهُ جَهْلُهُ.", |
||||
|
"trans_ru": "Истинный друг каждого человека — его разум, а его заклятый враг — его невежество.", |
||||
|
"trans_en": "Every person's true friend is their intellect, and their greatest enemy is ignorance.", |
||||
|
"trans_fa": "دوست هر فردی خرد اوست و دشمن او نادانیاش.", |
||||
|
"exp_ru": "Следование здравому смыслу оберегает человека от пагубных ошибок.", |
||||
|
"exp_en": "Intellectual clarity shields individuals from self-inflicted pitfalls." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Посланник Аллаха (да благословит Аллах его и род его)", |
||||
|
"narrator_en": "The Messenger of Allah (peace be upon him and his family)", |
||||
|
"narrator_fa": "پیامبر اکرم (ص)", |
||||
|
"title_ru": "Лучшие из людей", |
||||
|
"title_en": "The Best of Mankind", |
||||
|
"title_fa": "بهترین مردم", |
||||
|
"text": "خَيْرُ النَّاسِ أَنْفَعُهُمْ لِلنَّاسِ.", |
||||
|
"trans_ru": "Лучший из людей — тот, кто приносит наибольшую пользу людям.", |
||||
|
"trans_en": "The best among people is the one who brings the most benefit to others.", |
||||
|
"trans_fa": "بهترین مردم سودمندترین آنان برای دیگران است.", |
||||
|
"exp_ru": "Служение обществу и помощь ближним — высшая мера добродетели.", |
||||
|
"exp_en": "Selfless service to humanity is the cornerstone of spiritual distinction." |
||||
|
}, |
||||
|
{ |
||||
|
"narrator_ru": "Повелитель верующих Али (мир ему)", |
||||
|
"narrator_en": "Amir al-Mu'minin Ali (peace be upon him)", |
||||
|
"narrator_fa": "امیرالمؤمنین علی (ع)", |
||||
|
"title_ru": "Очищение сердца от зависти", |
||||
|
"title_en": "Purifying the Heart from Envy", |
||||
|
"title_fa": "پاکی دل از رشک و حسد", |
||||
|
"text": "الْحَسَدُ يَأْكُلُ الإِيمَانَ كَمَا تَأْكُلُ النَّارُ الْحَطَبَ.", |
||||
|
"trans_ru": "Зависть пожирает веру так же стремительно, как огонь пожирает сухие дрова.", |
||||
|
"trans_en": "Envy consumes faith just as fire consumes dry wood.", |
||||
|
"trans_fa": "حسادت ایمان را فرو میخورد، همانگونه که آتش هیزم را میسوزاند.", |
||||
|
"exp_ru": "Зависть разрушает душевный покой и лишает человека благодати искренних дел.", |
||||
|
"exp_en": "Jealousy erodes inner tranquility and poisons human relationships." |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
# Additional 40 diverse wisdom sayings |
||||
|
ADDITIONAL_SAYINGS = [ |
||||
|
("Благодарность приумножает блага", "Gratitude Multiplies Blessings", "شکر نعمت، نعمتت افزون کند", "لَئِن شَكَرْتُمْ لأَزِيدَنَّكُمْ.", "Имам Али (а)", "Благодарность открывает двери изобилия."), |
||||
|
("Умеренность в расходах", "Moderation in Expenditure", "میانهروی در معیشت", "حُسْنُ التَّدْبِيرِ مَعَ الْكَفَافِ خَيْرٌ مِنَ الْكَثِيرِ مَعَ الإِسْرَافِ.", "Имам Садик (а)", "Экономия и рассудительность сохраняют достаток."), |
||||
|
("Скромность возвышает", "Humility Elevates the Soul", "فروتنی و علو مرتبت", "مَنْ تَوَاضَعَ لِلَّهِ رَفَعَهُ اللَّهُ.", "Пророк Мухаммад (с)", "Истинное величие заключается в смирении."), |
||||
|
("Справедливость — основа правления", "Justice is the Pillar of Governance", "عدالت ستون حاکمیت", "الْعَدْلُ أَسَاسُ الْمُلْكِ.", "Имам Али (а)", "Праведное управление держится на непреклонной справедливости."), |
||||
|
("Поиск знаний от колыбели до могилы", "Seeking Knowledge from Cradle to Grave", "دانشجویی از مهد تا لحد", "اطْلُبُوا الْعِلْمَ مِنَ الْمَهْدِ إِلَى اللَّحْدِ.", "Пророк Мухаммад (с)", "Обучение — это пожизненный долг верующего."), |
||||
|
("Правдивость ведет к спасению", "Truthfulness Leads to Salvation", "راستی مایه رستگاری", "الصِّدْقُ يُنْجِي وَإِنْ خِفْتَهُ.", "Имам Али (а)", "Правда дарует уверенность и очищает совесть."), |
||||
|
("Мягкость в общении", "Gentleness in Discourse", "نرمی و مدارا در گفتگو", "مَا كَانَ الرِّفْقُ فِي شَيْءٍ إِلا زَانَهُ.", "Пророк Мухаммад (с)", "Доброта и мягкость украшают любое дело."), |
||||
|
("Бережливость времени", "The Value of Golden Hours", "غنیمت شمردن فرصتها", "الْفُرْصَةُ تَمُرُّ مَرَّ السَّحَابِ.", "Имам Али (а)", "Время скоротечно, используйте возможности во благо."), |
||||
|
("Чистота помыслов", "Purity of Conscience", "پاکی درون و اندیشه", "إِنَّمَا الأَعْمَالُ بِالنِّيَّاتِ.", "Пророк Мухаммад (с)", "Ценность поступка измеряется чистотой намерения."), |
||||
|
("Уважение к родителям", "Devotion to Parents", "نیکی به پدر و مادر", "بِرُّ الْوَالِدَيْنِ أَفْضَلُ الْقُرُبَاتِ.", "Имам Бакир (а)", "Почитание родителей приносит благодать в оба мира.") |
||||
|
] |
||||
|
|
||||
|
|
||||
|
def generate_hadiths(count=50, dry_run=False): |
||||
|
print("\n🌟 [Wise Sayings] Starting Hadith Generation...") |
||||
|
print("=" * 70) |
||||
|
|
||||
|
# 1. Get the category |
||||
|
category = HadisCategory.objects.filter(slug="wise-sayings").first() |
||||
|
if not category: |
||||
|
category = HadisCategory.objects.filter(title__icontains="Wise Sayings").first() |
||||
|
if not category: |
||||
|
print("❌ Error: Category 'wise-sayings' not found in database!") |
||||
|
return |
||||
|
|
||||
|
print(f"📁 Target Category: ID {category.id} | Slug: '{category.slug}' | Sect: {category.sect_id}") |
||||
|
|
||||
|
# 2. Get dependencies |
||||
|
statuses = list(HadisStatus.objects.all()) |
||||
|
transmitters_pool = list(Transmitters.objects.all()) |
||||
|
book_refs = list(BookReference.objects.all()) |
||||
|
|
||||
|
# Fallback status |
||||
|
default_status = statuses[0] if statuses else None |
||||
|
|
||||
|
# Current max number for hadiths |
||||
|
max_num = Hadis.objects.all().order_by("-number").values_list("number", flat=True).first() or 0 |
||||
|
|
||||
|
print(f"📊 Current Highest Hadis Number: {max_num}") |
||||
|
print(f"📚 Available Transmitters: {len(transmitters_pool)} | Book References: {len(book_refs)}") |
||||
|
|
||||
|
# 3. Build 50 hadiths |
||||
|
created_count = 0 |
||||
|
hadis_instances = [] |
||||
|
transmitter_relations = [] |
||||
|
reference_relations = [] |
||||
|
|
||||
|
all_templates = list(WISDOM_TEMPLATES) |
||||
|
# Expand templates if count > 10 |
||||
|
idx = 0 |
||||
|
while len(all_templates) < count: |
||||
|
base_t = ADDITIONAL_SAYINGS[idx % len(ADDITIONAL_SAYINGS)] |
||||
|
idx += 1 |
||||
|
num_variant = idx + 10 |
||||
|
all_templates.append({ |
||||
|
"narrator_ru": base_t[4], |
||||
|
"narrator_en": "Infallible Imam (peace be upon him)", |
||||
|
"narrator_fa": "معصوم (ع)", |
||||
|
"title_ru": f"{base_t[0]} (Часть {num_variant})", |
||||
|
"title_en": f"{base_t[1]} (Part {num_variant})", |
||||
|
"title_fa": f"{base_t[2]} (بخش {num_variant})", |
||||
|
"text": base_t[3], |
||||
|
"trans_ru": f"{base_t[0]}: {base_t[5]}", |
||||
|
"trans_en": f"{base_t[1]}: Wisdom regarding character and faith.", |
||||
|
"trans_fa": f"{base_t[2]}: رهنمودی گرانبها در اخلاق و دینداری.", |
||||
|
"exp_ru": base_t[5], |
||||
|
"exp_en": f"Spiritual contemplation on {base_t[1]}." |
||||
|
}) |
||||
|
|
||||
|
for i in range(count): |
||||
|
tmpl = all_templates[i] |
||||
|
curr_number = max_num + i + 1 |
||||
|
|
||||
|
title_json = [ |
||||
|
{"language_code": "ru", "title": tmpl["title_ru"], "text": tmpl["title_ru"]}, |
||||
|
{"language_code": "en", "title": tmpl["title_en"], "text": tmpl["title_en"]}, |
||||
|
{"language_code": "fa", "title": tmpl["title_fa"], "text": tmpl["title_fa"]}, |
||||
|
] |
||||
|
narrator_json = [ |
||||
|
{"language_code": "ru", "title": tmpl["narrator_ru"], "text": tmpl["narrator_ru"]}, |
||||
|
{"language_code": "en", "title": tmpl["narrator_en"], "text": tmpl["narrator_en"]}, |
||||
|
{"language_code": "fa", "title": tmpl["narrator_fa"], "text": tmpl["narrator_fa"]}, |
||||
|
] |
||||
|
translation_json = [ |
||||
|
{"language_code": "ru", "title": tmpl["trans_ru"], "text": tmpl["trans_ru"]}, |
||||
|
{"language_code": "en", "title": tmpl["trans_en"], "text": tmpl["trans_en"]}, |
||||
|
{"language_code": "fa", "title": tmpl["trans_fa"], "text": tmpl["trans_fa"]}, |
||||
|
] |
||||
|
explanation_json = [ |
||||
|
{"language_code": "ru", "text": tmpl["exp_ru"]}, |
||||
|
{"language_code": "en", "text": tmpl["exp_en"]}, |
||||
|
] |
||||
|
|
||||
|
# Generate unique readable slug |
||||
|
base_slug_text = tmpl["title_en"] or tmpl["title_ru"] |
||||
|
clean_slug = slugify(base_slug_text, allow_unicode=True).strip('-').lower() |
||||
|
if len(clean_slug) > 60: |
||||
|
clean_slug = clean_slug[:60].rstrip('-') |
||||
|
slug_candidate = f"{clean_slug}-{curr_number}" |
||||
|
|
||||
|
hadis = Hadis( |
||||
|
category=category, |
||||
|
number=curr_number, |
||||
|
slug=slug_candidate, |
||||
|
title=title_json, |
||||
|
title_narrator=narrator_json, |
||||
|
text=tmpl["text"], |
||||
|
translation=translation_json, |
||||
|
explanation=explanation_json, |
||||
|
hadis_status=random.choice(statuses) if statuses else default_status, |
||||
|
links={"dovodi": "https://dovodi.newhorizonco.uk/"}, |
||||
|
status=True, |
||||
|
share_link=f"https://dovodi.newhorizonco.uk/arguments/hadith/{slug_candidate}" |
||||
|
) |
||||
|
hadis_instances.append((hadis, tmpl)) |
||||
|
|
||||
|
print(f"✨ Prepared {len(hadis_instances)} Hadith records.") |
||||
|
|
||||
|
if dry_run: |
||||
|
print("\n[DRY RUN PREVIEW] First 5 Hadiths:") |
||||
|
for h, tmpl in hadis_instances[:5]: |
||||
|
print(f" • Hadis #{h.number:4d} | Slug: '{h.slug}' | Title: '{tmpl['title_en']}'") |
||||
|
print("\n✨ Dry run completed. No changes saved to database.") |
||||
|
return |
||||
|
|
||||
|
# Execute DB save |
||||
|
with transaction.atomic(): |
||||
|
print("💾 Saving Hadith records to database...") |
||||
|
# Save hadiths |
||||
|
saved_hadiths = [] |
||||
|
for h, tmpl in hadis_instances: |
||||
|
h.save() |
||||
|
saved_hadiths.append(h) |
||||
|
|
||||
|
# Attach transmitters and references |
||||
|
print("🔗 Linking transmitters and book references...") |
||||
|
for h in saved_hadiths: |
||||
|
# 2 to 4 transmitters |
||||
|
if transmitters_pool: |
||||
|
num_tr = min(len(transmitters_pool), random.randint(2, 4)) |
||||
|
sampled_tr = random.sample(transmitters_pool, num_tr) |
||||
|
gap_pos = random.randint(0, num_tr - 1) |
||||
|
|
||||
|
for pos, tr in enumerate(sampled_tr): |
||||
|
HadisTransmitter.objects.create( |
||||
|
hadis=h, |
||||
|
transmitter=tr, |
||||
|
chain_index=0, |
||||
|
order=pos, |
||||
|
is_gap=(pos == gap_pos) |
||||
|
) |
||||
|
|
||||
|
# 1 Book reference |
||||
|
if book_refs: |
||||
|
ref = random.choice(book_refs) |
||||
|
HadisReference.objects.create( |
||||
|
hadis=h, |
||||
|
book_reference=ref, |
||||
|
volume=str(random.randint(1, 8)), |
||||
|
pages=str(random.randint(15, 450)), |
||||
|
hadith_number=str(h.number) |
||||
|
) |
||||
|
|
||||
|
print("=" * 70) |
||||
|
print(f"✅ Successfully created {len(saved_hadiths)} Hadiths for category 'wise-sayings'!") |
||||
|
print(f"📌 Numbers range: #{saved_hadiths[0].number} to #{saved_hadiths[-1].number}") |
||||
|
|
||||
|
|
||||
|
def main(): |
||||
|
parser = argparse.ArgumentParser(description="Generate 50 Hadiths for the category 'wise-sayings'.") |
||||
|
parser.add_argument("--count", type=int, default=50, help="Number of hadiths to generate (default: 50).") |
||||
|
parser.add_argument("--dry-run", action="store_true", help="Preview without saving to DB.") |
||||
|
args = parser.parse_args() |
||||
|
|
||||
|
start_time = time.time() |
||||
|
generate_hadiths(count=args.count, dry_run=args.dry_run) |
||||
|
duration = time.time() - start_time |
||||
|
print(f"⏱ Completed in {duration:.2f} seconds.") |
||||
|
|
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
main() |
||||
@ -0,0 +1,95 @@ |
|||||
|
import os |
||||
|
import sys |
||||
|
import argparse |
||||
|
import django |
||||
|
|
||||
|
sys.stdout.reconfigure(encoding='utf-8') |
||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.develop') |
||||
|
django.setup() |
||||
|
|
||||
|
from django.db import transaction |
||||
|
from django.db.models import Count |
||||
|
from apps.hadis.models import HadisCategory, Hadis |
||||
|
|
||||
|
def get_text(val): |
||||
|
if isinstance(val, list): |
||||
|
for item in val: |
||||
|
if isinstance(item, dict): |
||||
|
return item.get('text') or item.get('title') or str(item) |
||||
|
elif isinstance(val, dict): |
||||
|
return val.get('text') or val.get('title') or str(val) |
||||
|
return str(val or "") |
||||
|
|
||||
|
def resolve_category_conflicts(dry_run=True): |
||||
|
print("=" * 65) |
||||
|
print(" CATEGORY CONFLICT RESOLVER (HADITHS vs SUBCATEGORIES) ") |
||||
|
print(f" Mode: {'DRY RUN (Simulation only, no DB changes)' if dry_run else 'EXECUTE (Applying changes to DB)'}") |
||||
|
print("=" * 65) |
||||
|
|
||||
|
conflicted_categories = HadisCategory.objects.annotate( |
||||
|
hadis_count=Count('hadis', distinct=True), |
||||
|
children_count=Count('children', distinct=True) |
||||
|
).filter(hadis_count__gt=0, children_count__gt=0) |
||||
|
|
||||
|
total_conflicts = conflicted_categories.count() |
||||
|
print(f"\nFound {total_conflicts} categories violating the rule (having both Hadiths & Subcategories).\n") |
||||
|
|
||||
|
if total_conflicts == 0: |
||||
|
print("✅ No conflicts found. All categories adhere to the business rule.") |
||||
|
return |
||||
|
|
||||
|
deleted_subcats_count = 0 |
||||
|
reassigned_hadis_count = 0 |
||||
|
|
||||
|
with transaction.atomic(): |
||||
|
for idx, parent_cat in enumerate(conflicted_categories, 1): |
||||
|
parent_title = get_text(parent_cat.title) |
||||
|
print(f"[{idx}/{total_conflicts}] Category #{parent_cat.id} ('{parent_title}') | Slug: {parent_cat.slug}") |
||||
|
print(f" - Direct Hadiths: {parent_cat.hadis_count}") |
||||
|
print(f" - Subcategories count: {parent_cat.children_count}") |
||||
|
|
||||
|
# Get all descendants |
||||
|
descendants = list(parent_cat.get_descendants().order_by('-level')) |
||||
|
print(f" - Total subcategories/descendants to remove: {len(descendants)}") |
||||
|
|
||||
|
for subcat in descendants: |
||||
|
subcat_title = get_text(subcat.title) |
||||
|
sub_hadis = subcat.hadis_set.all() |
||||
|
sub_hadis_count = sub_hadis.count() |
||||
|
|
||||
|
if sub_hadis_count > 0: |
||||
|
print(f" ⚠️ Subcategory #{subcat.id} ('{subcat_title}') has {sub_hadis_count} Hadiths!") |
||||
|
print(f" -> Reassigning these {sub_hadis_count} Hadiths to parent Category #{parent_cat.id} before deletion.") |
||||
|
if not dry_run: |
||||
|
sub_hadis.update(category=parent_cat) |
||||
|
reassigned_hadis_count += sub_hadis_count |
||||
|
|
||||
|
print(f" 🗑️ Removing Subcategory #{subcat.id} ('{subcat_title}')") |
||||
|
if not dry_run: |
||||
|
subcat.delete() |
||||
|
deleted_subcats_count += 1 |
||||
|
|
||||
|
if dry_run: |
||||
|
print("\n[DRY RUN SUMMARY]") |
||||
|
print(f" - Categories inspected: {total_conflicts}") |
||||
|
print(f" - Subcategories that would be deleted: {deleted_subcats_count}") |
||||
|
print(f" - Hadiths that would be safeguarded/reassigned: {reassigned_hadis_count}") |
||||
|
print(" -> No changes were written to the database.") |
||||
|
else: |
||||
|
print("\nRebuilding MPTT category tree structure...") |
||||
|
HadisCategory.objects.rebuild() |
||||
|
print("✅ MPTT category tree successfully rebuilt.") |
||||
|
|
||||
|
print("\n[EXECUTION SUMMARY]") |
||||
|
print(f" - Conflicted categories resolved: {total_conflicts}") |
||||
|
print(f" - Subcategories deleted: {deleted_subcats_count}") |
||||
|
print(f" - Hadiths safeguarded: {reassigned_hadis_count}") |
||||
|
print("✅ All changes committed successfully.") |
||||
|
|
||||
|
if __name__ == '__main__': |
||||
|
parser = argparse.ArgumentParser(description="Resolve categories having both Hadiths and Subcategories") |
||||
|
parser.add_argument('--execute', action='store_true', help='Execute changes on database (default is dry-run)') |
||||
|
args = parser.parse_args() |
||||
|
|
||||
|
resolve_category_conflicts(dry_run=not args.execute) |
||||
@ -0,0 +1,234 @@ |
|||||
|
#!/usr/bin/env python3 |
||||
|
""" |
||||
|
High-Performance Script to Regenerate and Update Slugs for: |
||||
|
- HadisCategory |
||||
|
- Hadis |
||||
|
- Transmitters |
||||
|
|
||||
|
Features: |
||||
|
- Human-readable slugs generated from actual multilingual titles and names |
||||
|
- Fast in-memory deduplication and uniqueness guarantees |
||||
|
- Safe two-phase atomic update using raw SQL temporary assignment + bulk update |
||||
|
- Supports --dry-run flag for inspection before applying |
||||
|
""" |
||||
|
|
||||
|
import os |
||||
|
import sys |
||||
|
import re |
||||
|
import time |
||||
|
import argparse |
||||
|
from pathlib import Path |
||||
|
from django.utils.text import slugify |
||||
|
|
||||
|
# Force UTF-8 stdout for Windows consoles |
||||
|
if hasattr(sys.stdout, "reconfigure"): |
||||
|
sys.stdout.reconfigure(encoding="utf-8") |
||||
|
|
||||
|
BASE_DIR = Path(__file__).resolve().parent.parent |
||||
|
sys.path.insert(0, str(BASE_DIR)) |
||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.base') |
||||
|
|
||||
|
import django |
||||
|
django.setup() |
||||
|
|
||||
|
from django.db import transaction, connection |
||||
|
from apps.hadis.models import HadisCategory, Hadis, Transmitters |
||||
|
|
||||
|
DUMMY_NAMES = { |
||||
|
"al-imam-al-hafiz-abu-isa-muhammad-ibn-isa-ibn-sawrah-ibn-musa-ibn-al-dahhak-al-sulami-al-tirmidhi", |
||||
|
"al-imam-al-hafiz-abu-isa-muhammad", |
||||
|
"الإمام الحافظ أبو عيسى محمد بن عيسى بن سَوْرَة بن موسى بن الضحاک السُّلَمي الترمذي", |
||||
|
"اسم-عربی", |
||||
|
} |
||||
|
|
||||
|
|
||||
|
def extract_best_text(json_val, preferred_langs=('en', 'ru', 'fa', 'ar')): |
||||
|
"""Extract the best text from a multilingual JSONField (list of dicts).""" |
||||
|
if not json_val: |
||||
|
return "" |
||||
|
if isinstance(json_val, str): |
||||
|
return json_val.strip() |
||||
|
if isinstance(json_val, list): |
||||
|
for lang in preferred_langs: |
||||
|
for item in json_val: |
||||
|
if isinstance(item, dict) and item.get('language_code') == lang: |
||||
|
val = item.get('text') or item.get('title') or item.get('value') or item.get('name') |
||||
|
if val and str(val).strip(): |
||||
|
return str(val).strip() |
||||
|
for item in json_val: |
||||
|
if isinstance(item, dict): |
||||
|
val = item.get('text') or item.get('title') or item.get('value') or item.get('name') |
||||
|
if val and str(val).strip(): |
||||
|
return str(val).strip() |
||||
|
elif isinstance(item, str) and item.strip(): |
||||
|
return item.strip() |
||||
|
return "" |
||||
|
|
||||
|
|
||||
|
def extract_transmitter_text(transmitter): |
||||
|
"""Extract best specific name for a transmitter, avoiding dummy/repeated texts.""" |
||||
|
candidates = [] |
||||
|
for field_val in (transmitter.full_name, transmitter.known_as, transmitter.nickname): |
||||
|
if not field_val or not isinstance(field_val, list): |
||||
|
continue |
||||
|
for lang in ('ru', 'fa', 'en', 'ar'): |
||||
|
for item in field_val: |
||||
|
if isinstance(item, dict) and item.get('language_code') == lang: |
||||
|
val = (item.get('text') or item.get('title') or '').strip() |
||||
|
if val and val not in DUMMY_NAMES and not val.startswith("313"): |
||||
|
candidates.append(val) |
||||
|
|
||||
|
if candidates: |
||||
|
return candidates[0] |
||||
|
|
||||
|
return extract_best_text(transmitter.full_name) or extract_best_text(transmitter.known_as) or "" |
||||
|
|
||||
|
|
||||
|
def clean_slug_text(text: str, max_len: int = 80) -> str: |
||||
|
"""Clean and slugify text while preserving readability and length constraints.""" |
||||
|
if not text: |
||||
|
return "" |
||||
|
cleaned = re.sub(r'[\(\)\[\]\{\}\<\>\'\"\`\:\;\,\.\?\!\@\#\$\%\^\&\*\+\=\|\/\\]+', ' ', text) |
||||
|
slug = slugify(cleaned, allow_unicode=True).strip('-').lower() |
||||
|
if len(slug) > max_len: |
||||
|
slug = slug[:max_len].rstrip('-') |
||||
|
return slug |
||||
|
|
||||
|
|
||||
|
def generate_unique_slug(base_slug: str, used_slugs: set, max_len: int = 90) -> str: |
||||
|
"""Ensure slug uniqueness using an in-memory set with counter suffix.""" |
||||
|
if not base_slug: |
||||
|
base_slug = "item" |
||||
|
|
||||
|
candidate = base_slug |
||||
|
counter = 1 |
||||
|
|
||||
|
while candidate in used_slugs: |
||||
|
counter += 1 |
||||
|
suffix = f"-{counter}" |
||||
|
avail_len = max_len - len(suffix) |
||||
|
candidate = f"{base_slug[:avail_len].rstrip('-')}{suffix}" |
||||
|
|
||||
|
used_slugs.add(candidate) |
||||
|
return candidate |
||||
|
|
||||
|
|
||||
|
def update_categories_slugs(dry_run=False): |
||||
|
print("\n📂 [1/3] Processing HadisCategory...") |
||||
|
categories = list(HadisCategory.objects.all().order_by('id')) |
||||
|
used_slugs = set() |
||||
|
sample_changes = [] |
||||
|
|
||||
|
for cat in categories: |
||||
|
raw_text = extract_best_text(cat.title, ('en', 'ru', 'fa', 'ar')) or extract_best_text(cat.description) |
||||
|
base_slug = clean_slug_text(raw_text, max_len=75) |
||||
|
if not base_slug: |
||||
|
base_slug = f"category-{cat.source_type or 'item'}-{cat.id}" |
||||
|
|
||||
|
new_slug = generate_unique_slug(base_slug, used_slugs) |
||||
|
|
||||
|
if len(sample_changes) < 5 and cat.slug != new_slug: |
||||
|
sample_changes.append((cat.id, cat.slug, new_slug, raw_text)) |
||||
|
|
||||
|
cat.slug = new_slug |
||||
|
|
||||
|
print(f" Total categories: {len(categories)}") |
||||
|
for item_id, old_s, new_s, title in sample_changes: |
||||
|
print(f" • ID {item_id:4d} | '{title[:30]}' -> {old_s} ➔ {new_s}") |
||||
|
|
||||
|
if not dry_run: |
||||
|
with transaction.atomic(): |
||||
|
with connection.cursor() as cursor: |
||||
|
cursor.execute("UPDATE hadis_hadiscategory SET slug = CONCAT('tmp-c-', id);") |
||||
|
HadisCategory.objects.bulk_update(categories, ['slug'], batch_size=1000) |
||||
|
print(" ✅ All categories updated successfully.") |
||||
|
|
||||
|
|
||||
|
def update_hadis_slugs(dry_run=False): |
||||
|
print("\n📜 [2/3] Processing Hadis...") |
||||
|
hadiths = list(Hadis.objects.all().order_by('id')) |
||||
|
used_slugs = set() |
||||
|
sample_changes = [] |
||||
|
|
||||
|
for h in hadiths: |
||||
|
raw_text = extract_best_text(h.title, ('en', 'ru', 'fa', 'ar')) or extract_best_text(h.translation) or extract_best_text(h.hadis_status_text) |
||||
|
base_slug = clean_slug_text(raw_text, max_len=75) |
||||
|
|
||||
|
if not base_slug or (base_slug.isdigit() and len(base_slug) <= 3): |
||||
|
if h.number: |
||||
|
base_slug = f"hadis-{h.number}" + (f"-{base_slug}" if base_slug else "") |
||||
|
else: |
||||
|
base_slug = f"hadis-{h.id}" |
||||
|
|
||||
|
new_slug = generate_unique_slug(base_slug, used_slugs) |
||||
|
|
||||
|
if len(sample_changes) < 5 and h.slug != new_slug: |
||||
|
sample_changes.append((h.id, h.slug, new_slug, raw_text or str(h.number))) |
||||
|
|
||||
|
h.slug = new_slug |
||||
|
|
||||
|
print(f" Total hadiths: {len(hadiths)}") |
||||
|
for item_id, old_s, new_s, title in sample_changes: |
||||
|
print(f" • ID {item_id:4d} | '{title[:30]}' -> {old_s} ➔ {new_s}") |
||||
|
|
||||
|
if not dry_run: |
||||
|
with transaction.atomic(): |
||||
|
with connection.cursor() as cursor: |
||||
|
cursor.execute("UPDATE hadis_hadis SET slug = CONCAT('tmp-h-', id);") |
||||
|
Hadis.objects.bulk_update(hadiths, ['slug'], batch_size=1000) |
||||
|
print(" ✅ All hadiths updated successfully.") |
||||
|
|
||||
|
|
||||
|
def update_transmitters_slugs(dry_run=False): |
||||
|
print("\n👤 [3/3] Processing Transmitters...") |
||||
|
transmitters = list(Transmitters.objects.all().order_by('id')) |
||||
|
used_slugs = set() |
||||
|
sample_changes = [] |
||||
|
|
||||
|
for tr in transmitters: |
||||
|
raw_text = extract_transmitter_text(tr) |
||||
|
base_slug = clean_slug_text(raw_text, max_len=75) |
||||
|
|
||||
|
if not base_slug: |
||||
|
base_slug = f"transmitter-{tr.id}" |
||||
|
|
||||
|
new_slug = generate_unique_slug(base_slug, used_slugs) |
||||
|
|
||||
|
if len(sample_changes) < 5 and tr.slug != new_slug: |
||||
|
sample_changes.append((tr.id, tr.slug, new_slug, raw_text)) |
||||
|
|
||||
|
tr.slug = new_slug |
||||
|
|
||||
|
print(f" Total transmitters: {len(transmitters)}") |
||||
|
for item_id, old_s, new_s, name in sample_changes: |
||||
|
print(f" • ID {item_id:4d} | '{name[:30]}' -> {old_s} ➔ {new_s}") |
||||
|
|
||||
|
if not dry_run: |
||||
|
with transaction.atomic(): |
||||
|
with connection.cursor() as cursor: |
||||
|
cursor.execute("UPDATE hadis_transmitters SET slug = CONCAT('tmp-t-', id);") |
||||
|
Transmitters.objects.bulk_update(transmitters, ['slug'], batch_size=1000) |
||||
|
print(" ✅ All transmitters updated successfully.") |
||||
|
|
||||
|
|
||||
|
def main(): |
||||
|
parser = argparse.ArgumentParser(description="Update readable slugs for Categories, Hadiths, and Transmitters.") |
||||
|
parser.add_argument("--dry-run", action="store_true", help="Simulate slug generation without saving to database.") |
||||
|
args = parser.parse_args() |
||||
|
|
||||
|
start_time = time.time() |
||||
|
mode_text = "DRY RUN (Preview Only)" if args.dry_run else "EXECUTE (Database Update)" |
||||
|
print(f"\n🚀 Starting Slug Optimization [{mode_text}]...") |
||||
|
print("=" * 70) |
||||
|
|
||||
|
update_categories_slugs(dry_run=args.dry_run) |
||||
|
update_hadis_slugs(dry_run=args.dry_run) |
||||
|
update_transmitters_slugs(dry_run=args.dry_run) |
||||
|
|
||||
|
duration = time.time() - start_time |
||||
|
print("\n" + "=" * 70) |
||||
|
print(f"✨ Completed in {duration:.2f} seconds!") |
||||
|
|
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
main() |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue