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."))