You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

367 lines
22 KiB

import os
import random
import uuid
from typing import List, Dict
from django.conf import settings
from django.core.management.base import BaseCommand
from django.core.files import File
from apps.blog.models import Blog, BlogContent
def build_multilang_list(values: Dict[str, str], value_key: str = "title") -> List[Dict[str, str]]:
"""
Convert a dict like {'en': '...', 'fa': '...', 'ru': '...'} into the project's
JSONField list schema: [{'language_code': 'en', 'title': '...'}, ...]
value_key controls whether we store under 'title' (for titles) or 'text' (for content).
"""
return [{"language_code": code, value_key: text} for code, text in values.items()]
def get_seed_images() -> List[str]:
"""
Load available image file paths from BASE_DIR/seeds/images/
"""
base = os.path.join(settings.BASE_DIR, "seeds", "images")
if not os.path.isdir(base):
return []
files = []
for name in os.listdir(base):
lower = name.lower()
if lower.endswith((".jpg", ".jpeg", ".png", ".webp")):
files.append(os.path.join(base, name))
return files
def pick_image_path(images: List[str]) -> str:
"""
Randomly pick an image path from the provided list.
"""
if not images:
return ""
return random.choice(images)
def generate_topics() -> List[Dict[str, Dict[str, str]]]:
"""
Build 20 topics based on prophets and imams to satisfy the requested domains.
Each topic is a mapping for three languages: en, fa, ru.
"""
prophets = [
{"en": "Prophet Muhammad", "fa": "حضرت محمد (ص)", "ru": "Пророк Мухаммад"},
{"en": "Prophet Musa", "fa": "حضرت موسی (ع)", "ru": "Пророк Муса"},
{"en": "Prophet Isa", "fa": "حضرت عیسی (ع)", "ru": "Пророк Иса"},
{"en": "Prophet Ibrahim", "fa": "حضرت ابراهیم (ع)", "ru": "Пророк Ибрахим"},
{"en": "Prophet Nuh", "fa": "حضرت نوح (ع)", "ru": "Пророк Нух"},
{"en": "Prophet Yusuf", "fa": "حضرت یوسف (ع)", "ru": "Пророк Юсуф"},
{"en": "Prophet Yaqub", "fa": "حضرت یعقوب (ع)", "ru": "Пророк Якуб"},
{"en": "Prophet Dawud", "fa": "حضرت داوود (ع)", "ru": "Пророк Давуд"},
]
imams = [
{"en": "Imam Ali", "fa": "امام علی (ع)", "ru": "Имам Али"},
{"en": "Imam Hasan", "fa": "امام حسن (ع)", "ru": "Имам Хасан"},
{"en": "Imam Husayn", "fa": "امام حسین (ع)", "ru": "Имам Хусейн"},
{"en": "Imam Sajjad", "fa": "امام سجاد (ع)", "ru": "Имам Саджад"},
{"en": "Imam Baqir", "fa": "امام باقر (ع)", "ru": "Имам Бакир"},
{"en": "Imam Sadiq", "fa": "امام صادق (ع)", "ru": "Имам Садык"},
{"en": "Imam Kadhim", "fa": "امام کاظم (ع)", "ru": "Имам Казим"},
{"en": "Imam Reza", "fa": "امام رضا (ع)", "ru": "Имам Реза"},
{"en": "Imam Jawad", "fa": "امام جواد (ع)", "ru": "Имам Джавад"},
{"en": "Imam Hadi", "fa": "امام هادی (ع)", "ru": "Имам Хади"},
{"en": "Imam Askari", "fa": "امام عسکری (ع)", "ru": "Имам Аскари"},
{"en": "Imam Mahdi", "fa": "امام مهدی (عج)", "ru": "Имам Махди"},
]
topics = prophets + imams
return topics[:20]
def content_sections(name_en: str, name_fa: str, name_ru: str) -> List[Dict[str, Dict[str, str]]]:
"""
Build 10 narrative anecdotal content sections per blog, tailored to the blog's subject (prophet/imam),
with rich multilingual texts (fa, en, ru). Each section is a self-contained story (حکایت/История).
"""
sections = []
sections.append({
"title": {
"en": f"Anecdote: Early Life Kindness of {name_en}",
"fa": f"حکایت: مهربانی در کودکی {name_fa}",
"ru": f"История: Доброе сердце в детстве {name_ru}",
},
"text": {
"en": f"As a child, {name_en} was noted for uncommon kindness. One cold morning a neighbor had no bread, "
f"so {name_en} shared the family portion and said, 'Provision grows when shared.' "
f"The town remembered this as a lesson that compassion is the seed of community.",
"fa": f"{name_fa} از همان کودکی به مهربانی شناخته می‌شد. صبحی سرد، همسایه‌ای نان نداشت؛ "
f"{name_fa} سهم خانواده را بخشید و گفت: «روزی وقتی تقسیم شود، افزون می‌گردد.» "
f"آن رفتار درسی شد برای شهر که شفقت، بذر اجتماع است.",
"ru": f"С детства {name_ru} отличался редкой добротой. В холодное утро у соседа не было хлеба, "
f"и {name_ru} поделился семейной долей, сказав: «Истинный удел умножается, когда им делятся». "
f"Так люди усвоили урок о сострадании как основе общины.",
},
})
sections.append({
"title": {
"en": f"Anecdote: First Signs of Wisdom of {name_en}",
"fa": f"حکایت: نشانه‌های نخستین حکمت {name_fa}",
"ru": f"История: Первые признаки мудрости {name_ru}",
},
"text": {
"en": f"In youth, a dispute arose over a simple matter. While others raised their voices, "
f"{name_en} asked both sides to repeat their words slowly. "
f"By listening with fairness, {name_en} settled the matter gently and taught that calm clarity reveals truth.",
"fa": f"در جوانی، نزاعی بر سر مسئله‌ای ساده درگرفت. هنگامی که دیگران صدا بلند کرده بودند، "
f"{name_fa} از هر دو طرف خواست آرام و دقیق سخن بگویند. "
f"با گوش سپردن منصفانه، نزاع به نرمی پایان یافت و روشن شد که آرامش، حقیقت را آشکار می‌کند.",
"ru": f"В юности возник спор по пустяку. Пока голоса накалялись, "
f"{name_ru} попросил обе стороны говорить медленно и ясно. "
f"Выслушав справедливо, {name_ru} примирил спорящих и показал, что спокойная ясность открывает истину.",
},
})
sections.append({
"title": {
"en": f"Anecdote: Compassion for the Poor by {name_en}",
"fa": f"حکایت: شفقت بر نیازمندان از سوی {name_fa}",
"ru": f"История: Сострадание к нуждающимся от {name_ru}",
},
"text": {
"en": f"A traveler arrived hungry and ashamed. {name_en} prepared food with their own hands and invited the traveler "
f"to sit as an honored guest. People learned that dignity grows where compassion leads.",
"fa": f"مسافری گرسنه و شرمسار فرا رسید. {name_fa} خود دست به کار شد، طعامی مهیا کرد و مسافر را "
f"چون مهمانی گرامی نشاند. مردم آموختند که کرامت، در سایهٔ پیشگامیِ شفقت می‌روید.",
"ru": f"Пришел путник голодный и смущенный. {name_ru} собственноручно приготовил еду и усадил его как почётного гостя. "
f"Люди поняли, что достоинство расцветает там, где впереди идет сострадание.",
},
})
sections.append({
"title": {
"en": f"Anecdote: Patience Under Trial of {name_en}",
"fa": f"حکایت: صبر در امتحان {name_fa}",
"ru": f"История: Терпение в испытании {name_ru}",
},
"text": {
"en": f"Hard days came with whispers and blame. {name_en} answered with patience, refusing to return harshness with harshness. "
f"In time, those who criticized felt softened and sought forgiveness.",
"fa": f"روزهای دشوار با زمزمه‌ها و سرزنش‌ها همراه شد. {name_fa} با صبر پاسخ گفت و به تندی، تندی نکرد. "
f"با گذر زمان، دلِ ملامت‌گران نرم شد و پوزش خواستند.",
"ru": f"Настали трудные дни с шепотом упреков. {name_ru} отвечал терпением и не платил жесткостью за жесткость. "
f"Со временем сердца порицавших смягчились, и они попросили прощения.",
},
})
sections.append({
"title": {
"en": f"Anecdote: Justice in a Dispute by {name_en}",
"fa": f"حکایت: عدالت در یک نزاع به روایت {name_fa}",
"ru": f"История: Справедливость в споре у {name_ru}",
},
"text": {
"en": f"Two neighbors quarreled over a wall. {name_en} measured the ground, heard each claim, and decided "
f"with equity—neither fully winning nor losing. They accepted, seeing justice as balance, not bias.",
"fa": f"دو همسایه بر سر دیواری به نزاع افتادند. {name_fa} زمین را اندازه گرفت، سخن هر دو را شنید "
f"و به گونه‌ای حکم کرد که نه این پیروزِ مطلق باشد و نه آن؛ عدالت را توازن دیدند نه جانبداری.",
"ru": f"Двое соседей спорили из‑за стены. {name_ru} измерил участок, выслушал обе стороны и вынес решение, "
f"где ни один не выиграл полностью и не проиграл. Так они увидели справедливость как равновесие, а не пристрастие.",
},
})
sections.append({
"title": {
"en": f"Anecdote: A Miraculous Sign with {name_en}",
"fa": f"حکایت: نشانه‌ای شگفت با {name_fa}",
"ru": f"История: Чудесный знак с {name_ru}",
},
"text": {
"en": f"In a moment of fear, a small sign appeared—unexpected help arrived at the right time. "
f"People said, 'It was a mercy,' and {name_en} reminded them that signs awaken gratitude and responsibility.",
"fa": f"در لحظه‌ای هراس‌انگیز، نشانه‌ای پدیدار شد؛ یاریِ ناگهانی در زمانِ درست. "
f"مردم گفتند: «رحمتی بود»، و {name_fa} یادآور شد که نشانه‌ها سپاس و مسئولیت می‌آموزند.",
"ru": f"В миг страха явился маленький знак — помощь пришла вовремя. "
f"Люди сказали: «Это была милость», а {name_ru} напомнил, что знамения пробуждают благодарность и ответственность.",
},
})
sections.append({
"title": {
"en": f"Anecdote: Teaching with Gentle Words of {name_en}",
"fa": f"حکایت: تعلیم با سخن نرم از {name_fa}",
"ru": f"История: Наставление мягким словом от {name_ru}",
},
"text": {
"en": f"A young student erred while reading. {name_en} corrected without humiliation, "
f"explaining with care until understanding bloomed. Knowledge, they said, enters where hearts feel safe.",
"fa": f"شاگردی در خواندن خطا کرد. {name_fa} بی‌آنکه او را خوار کند، با دلسوزی توضیح داد تا فهم شکوفا شد. "
f"گفت: دانش، جایی وارد می‌شود که دل‌ها امن باشند.",
"ru": f"Юный ученик ошибся в чтении. {name_ru} исправил без унижения и терпеливо объяснил, пока не пришло понимание. "
f"Знание входит туда, где сердце в безопасности.",
},
})
sections.append({
"title": {
"en": f"Anecdote: Night Prayer and Humility of {name_en}",
"fa": f"حکایت: نماز شب و فروتنی {name_fa}",
"ru": f"История: Ночная молитва и смирение {name_ru}",
},
"text": {
"en": f"In the stillness of the night, {name_en} stood in prayer, whispering gratitude and seeking guidance. "
f"Those who saw learned that inner strength is born from humble devotion.",
"fa": f"در سکوت شب، {name_fa} به نماز ایستاد؛ شکر می‌گفت و راه می‌جست. "
f"بینندگان آموختند که قوت درون از بندگی فروتنانه زاده می‌شود.",
"ru": f"В тишине ночи {name_ru} стоял в молитве, шепча благодарность и прося наставления. "
f"Те, кто видел, поняли: внутренняя сила рождается из смиренного поклонения.",
},
})
sections.append({
"title": {
"en": f"Anecdote: Generosity Without Expectation by {name_en}",
"fa": f"حکایت: بخشش بی‌منت از {name_fa}",
"ru": f"История: Щедрость без ожиданий от {name_ru}",
},
"text": {
"en": f"A poor family hid their need out of modesty. {name_en} discreetly sent provisions for days, "
f"asking no thanks. True giving, they taught, seeks no witness but the All‑Seeing.",
"fa": f"خانواده‌ای نیاز خود را از شرم پنهان می‌کردند. {name_fa} بی‌صدا آذوقهٔ چند روزشان را رساند "
f"و هیچ سپاسی نخواست؛ آموخت که بخششِ راستین، جز دیدهٔ حق گواهی نمی‌طلبد.",
"ru": f"Бедная семья скрывала нужду из скромности. {name_ru} тайно прислал им припасы на несколько дней "
f"и не просил благодарности. Истинная щедрость не ищет свидетелей, кроме Всевидящего.",
},
})
sections.append({
"title": {
"en": f"Anecdote: Legacy That Inspires of {name_en}",
"fa": f"حکایت: میراث الهام‌بخشِ {name_fa}",
"ru": f"История: Наследие, которое вдохновляет {name_ru}",
},
"text": {
"en": f"Years later, children repeated the sayings of {name_en} and neighbors kept the customs of mercy, justice, and truth. "
f"The legacy was not stone or gold, but transformed hearts.",
"fa": f"سال‌ها بعد، کودکان سخنانِ {name_fa} را بازمی‌گفتند و همسایگان آیینِ رحمت، عدالت و راستی را نگه می‌داشتند. "
f"میراث، سنگ و زر نبود؛ دل‌های دگرگون‌شده بود.",
"ru": f"Спустя годы дети повторяли изречения {name_ru}, а соседи хранили обычаи милости, справедливости и истины. "
f"Их наследие было не в камне и золоте, а в преображенных сердцах.",
},
})
return sections
class Command(BaseCommand):
help = "Seed 20 blogs with 10 related contents each in fa, en, ru languages. Images are randomly assigned from seeds/images."
def add_arguments(self, parser):
parser.add_argument("--blogs", type=int, default=20, help="Number of blogs to create")
parser.add_argument("--contents", type=int, default=10, help="Number of contents per blog")
parser.add_argument("--commit", action="store_true", help="Persist changes to the database. If omitted, runs in dry-run mode.")
parser.add_argument("--images-dir", type=str, default="", help="Override images directory (defaults to BASE_DIR/seeds/images)")
def handle(self, *args, **options):
blogs_count = int(options.get("blogs") or 20)
contents_count = int(options.get("contents") or 10)
commit = bool(options.get("commit"))
images_dir_opt = options.get("images_dir")
# Load image candidates
images = []
if images_dir_opt:
base = images_dir_opt
if os.path.isdir(base):
for name in os.listdir(base):
lower = name.lower()
if lower.endswith((".jpg", ".jpeg", ".png", ".webp")):
images.append(os.path.join(base, name))
else:
images = get_seed_images()
if not images:
self.stdout.write(self.style.WARNING("No seed images found under seeds/images/. Thumbnails and content images will be empty."))
topics = generate_topics()
if blogs_count > len(topics):
blogs_count = len(topics)
created_blogs = 0
created_contents = 0
for idx in range(blogs_count):
topic = topics[idx]
name_en = topic["en"]
name_fa = topic["fa"]
name_ru = topic["ru"]
title_values = {"en": f"Biography: {name_en}", "fa": f"زندگی‌نامه: {name_fa}", "ru": f"Биография: {name_ru}"}
slogan_values = {"en": f"Stories and lessons from {name_en}", "fa": f"حکایت‌ها و درس‌ها از {name_fa}", "ru": f"Истории и уроки о {name_ru}"}
summary_values = {
"en": f"A curated collection of chapters about {name_en}, covering life, teachings, and legacy.",
"fa": f"مجموعه‌ای منتخب از فصل‌ها درباره {name_fa} شامل زندگی، تعالیم و میراث.",
"ru": f"Подборка глав о {name_ru}, охватывающих жизнь, учение и наследие.",
}
blog = Blog(
title=build_multilang_list(title_values, "title"),
slogan=build_multilang_list(slogan_values, "title"),
summary=build_multilang_list(summary_values, "text"),
)
# Assign a random thumbnail image if available
thumb_path = pick_image_path(images)
if thumb_path:
ext = os.path.splitext(thumb_path)[1].lower()
fname = f"seed_thumb_{uuid.uuid4().hex}{ext}"
if commit:
with open(thumb_path, "rb") as f:
blog.thumbnail.save(fname, File(f), save=False)
else:
# Dry-run: simulate
blog.thumbnail.name = os.path.join("blog/thumbnails", fname)
self.stdout.write(f"[{'COMMIT' if commit else 'DRY'}] Preparing blog {idx+1}: {name_en}")
contents_payload = content_sections(name_en, name_fa, name_ru)
# Limit to requested count
contents_payload = contents_payload[:contents_count]
if commit:
blog.save()
created_blogs += 1
# Create related contents
order = 1
for section in contents_payload:
title_list = build_multilang_list(section["title"], "title")
text_list = build_multilang_list(section["text"], "text")
content_image_path = pick_image_path(images)
bc = BlogContent(
blog=blog,
title=title_list,
content=text_list,
slug=title_list, # allow slug generation from multilingual titles
order=order,
)
order += 1
if content_image_path:
ext = os.path.splitext(content_image_path)[1].lower()
fname = f"seed_content_{uuid.uuid4().hex}{ext}"
if commit:
with open(content_image_path, "rb") as f:
bc.image.save(fname, File(f), save=False)
else:
bc.image = None # do not assign filesystem in dry-run
if commit:
bc.save()
created_contents += 1
self.stdout.write(self.style.SUCCESS(f"Prepared {len(contents_payload)} contents for blog '{name_en}'"))
mode = "COMMIT" if commit else "DRY-RUN"
self.stdout.write(self.style.SUCCESS(f"{mode} finished. Blogs prepared: {created_blogs}, Contents prepared: {created_contents}"))
if not commit:
self.stdout.write(self.style.WARNING("Run again with --commit to persist the changes."))