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