feat(blog): add production taxonomy seed commands
This commit is contained in:
125
apps/blog/management/commands/seed_blog_categories.py
Normal file
125
apps/blog/management/commands/seed_blog_categories.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from apps.blog.models import Category
|
||||||
|
|
||||||
|
|
||||||
|
CATEGORIES = [
|
||||||
|
{
|
||||||
|
"name": "اخبار و اطلاعیهها",
|
||||||
|
"description": "خبرها، اطلاعیهها و گزارشهای مرتبط با انجمن، دانشکده و جامعه دانشجویی.",
|
||||||
|
"children": [
|
||||||
|
("اخبار انجمن", "خبرها و گزارشهای رسمی انجمن علمی مهندسی کامپیوتر."),
|
||||||
|
("اطلاعیههای آموزشی", "اطلاعیههای مهم آموزشی، انتخاب واحد، امتحانات و امور دانشجویی."),
|
||||||
|
("رویدادها و کارگاهها", "معرفی، گزارش و پیگیری رویدادها، نشستها و کارگاهها."),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "آموزش و مسیر یادگیری",
|
||||||
|
"description": "مطالب آموزشی و مسیرهای یادگیری برای دانشجویان علوم و مهندسی کامپیوتر.",
|
||||||
|
"children": [
|
||||||
|
("راهنمای شروع", "مطالب مقدماتی برای شروع برنامهنویسی، دانشگاه و مهارتآموزی."),
|
||||||
|
("آموزشهای فنی", "آموزشهای عملی، گامبهگام و مسئلهمحور در حوزههای فنی."),
|
||||||
|
("منابع یادگیری", "معرفی کتاب، دوره، مستندات، مسیر مطالعه و منابع مفید."),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "فناوری و مهندسی نرمافزار",
|
||||||
|
"description": "مقالههای فنی درباره توسعه نرمافزار، ابزارها، معماری و فناوریهای روز.",
|
||||||
|
"children": [
|
||||||
|
("برنامهنویسی", "زبانها، الگوها، نکتههای کدنویسی و تجربههای عملی توسعه."),
|
||||||
|
("وب و اپلیکیشن", "فرانتاند، بکاند، موبایل، API و تجربه ساخت محصول."),
|
||||||
|
("دواپس و ابزارها", "لینوکس، گیت، CI/CD، استقرار، کانتینر و ابزارهای توسعه."),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "هوش مصنوعی و داده",
|
||||||
|
"description": "مطالب مرتبط با هوش مصنوعی، یادگیری ماشین، داده و کاربردهای آنها.",
|
||||||
|
"children": [
|
||||||
|
("یادگیری ماشین", "مفاهیم، تمرینها و تجربههای یادگیری ماشین و مدلسازی."),
|
||||||
|
("علم داده", "تحلیل داده، مصورسازی، آمار کاربردی و پروژههای دادهمحور."),
|
||||||
|
("هوش مصنوعی کاربردی", "ابزارها، کاربردها، ایدهها و تجربههای عملی با AI."),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "دانشگاه و پژوهش",
|
||||||
|
"description": "محتوای علمی، پژوهشی و دانشگاهی برای دانشجویان و اعضای انجمن.",
|
||||||
|
"children": [
|
||||||
|
("پژوهش دانشجویی", "تجربهها، معرفی مقاله، ایده پژوهشی و همکاریهای علمی."),
|
||||||
|
("درس و دانشگاه", "راهنمای درسها، پروژههای درسی، امتحان و تجربه دانشگاهی."),
|
||||||
|
("مسابقات علمی", "برنامهنویسی رقابتی، مسابقات، چالشها و آمادگی تیمی."),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "پروژهها و تجربهها",
|
||||||
|
"description": "تجربههای واقعی دانشجویان از پروژه، کار تیمی، کارآموزی و مسیر حرفهای.",
|
||||||
|
"children": [
|
||||||
|
("پروژههای دانشجویی", "معرفی، کالبدشکافی و گزارش پروژههای دانشجویی و تیمی."),
|
||||||
|
("کارآموزی و بازار کار", "رزومه، مصاحبه، کارآموزی، مسیر شغلی و تجربه ورود به کار."),
|
||||||
|
("تجربههای انجمنی", "روایتها و درسآموختههای فعالیت در انجمن و تیمهای دانشجویی."),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def console_safe(value: str) -> str:
|
||||||
|
encoding = sys.stdout.encoding or "utf-8"
|
||||||
|
return value.encode(encoding, errors="backslashreplace").decode(encoding)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Create or update production blog categories for the CS association blog."
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Print planned categories without writing changes.")
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
dry_run = options["dry_run"]
|
||||||
|
root_count = 0
|
||||||
|
child_count = 0
|
||||||
|
|
||||||
|
for root_spec in CATEGORIES:
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(console_safe(f"[root] {root_spec['name']}"))
|
||||||
|
for child_name, _ in root_spec["children"]:
|
||||||
|
self.stdout.write(console_safe(f" [child] {child_name}"))
|
||||||
|
continue
|
||||||
|
|
||||||
|
root = self._upsert_category(
|
||||||
|
name=root_spec["name"],
|
||||||
|
description=root_spec["description"],
|
||||||
|
parent=None,
|
||||||
|
)
|
||||||
|
root_count += 1
|
||||||
|
|
||||||
|
for child_name, child_description in root_spec["children"]:
|
||||||
|
self._upsert_category(
|
||||||
|
name=child_name,
|
||||||
|
description=child_description,
|
||||||
|
parent=root,
|
||||||
|
)
|
||||||
|
child_count += 1
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(self.style.WARNING("Dry run only. No categories were changed."))
|
||||||
|
return
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Blog categories synchronized: {root_count} roots, {child_count} children."))
|
||||||
|
|
||||||
|
def _upsert_category(self, *, name: str, description: str, parent: Category | None) -> Category:
|
||||||
|
if parent and parent.parent_id:
|
||||||
|
raise ValueError(f"Invalid category tree: parent '{parent.name}' is not a root category.")
|
||||||
|
|
||||||
|
category, _ = Category.all_objects.update_or_create(
|
||||||
|
name=name,
|
||||||
|
defaults={
|
||||||
|
"description": description,
|
||||||
|
"parent": parent,
|
||||||
|
"is_deleted": False,
|
||||||
|
"deleted_at": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return category
|
||||||
111
apps/blog/management/commands/seed_blog_tags.py
Normal file
111
apps/blog/management/commands/seed_blog_tags.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
from apps.blog.models import Tag
|
||||||
|
|
||||||
|
|
||||||
|
TAGS = [
|
||||||
|
"انجمن علمی",
|
||||||
|
"دانشکده",
|
||||||
|
"اطلاعیه",
|
||||||
|
"رویداد",
|
||||||
|
"کارگاه",
|
||||||
|
"گزارش رویداد",
|
||||||
|
"برنامهنویسی",
|
||||||
|
"پایتون",
|
||||||
|
"جاوااسکریپت",
|
||||||
|
"تایپاسکریپت",
|
||||||
|
"جاوا",
|
||||||
|
"سیپلاسپلاس",
|
||||||
|
"گولنگ",
|
||||||
|
"فرانتاند",
|
||||||
|
"بکاند",
|
||||||
|
"React",
|
||||||
|
"Next.js",
|
||||||
|
"Django",
|
||||||
|
"REST API",
|
||||||
|
"پایگاه داده",
|
||||||
|
"PostgreSQL",
|
||||||
|
"Redis",
|
||||||
|
"گیت",
|
||||||
|
"لینوکس",
|
||||||
|
"Docker",
|
||||||
|
"DevOps",
|
||||||
|
"استقرار",
|
||||||
|
"امنیت",
|
||||||
|
"شبکه",
|
||||||
|
"سیستمعامل",
|
||||||
|
"الگوریتم",
|
||||||
|
"ساختمان داده",
|
||||||
|
"برنامهنویسی رقابتی",
|
||||||
|
"حل مسئله",
|
||||||
|
"هوش مصنوعی",
|
||||||
|
"یادگیری ماشین",
|
||||||
|
"یادگیری عمیق",
|
||||||
|
"علم داده",
|
||||||
|
"تحلیل داده",
|
||||||
|
"دادهکاوی",
|
||||||
|
"پردازش زبان طبیعی",
|
||||||
|
"بینایی ماشین",
|
||||||
|
"پژوهش",
|
||||||
|
"مقالهخوانی",
|
||||||
|
"پروژه دانشجویی",
|
||||||
|
"پروژه درسی",
|
||||||
|
"تیمسازی",
|
||||||
|
"مدیریت پروژه",
|
||||||
|
"طراحی نرمافزار",
|
||||||
|
"معماری نرمافزار",
|
||||||
|
"تست نرمافزار",
|
||||||
|
"تجربه کاربری",
|
||||||
|
"طراحی رابط کاربری",
|
||||||
|
"اپنسورس",
|
||||||
|
"کارآموزی",
|
||||||
|
"رزومه",
|
||||||
|
"مصاحبه",
|
||||||
|
"مسیر شغلی",
|
||||||
|
"منابع یادگیری",
|
||||||
|
"کتاب",
|
||||||
|
"دوره آموزشی",
|
||||||
|
"تجربه دانشجویی",
|
||||||
|
"انتخاب واحد",
|
||||||
|
"امتحانات",
|
||||||
|
"آموزش",
|
||||||
|
"راهنمای شروع",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def console_safe(value: str) -> str:
|
||||||
|
encoding = sys.stdout.encoding or "utf-8"
|
||||||
|
return value.encode(encoding, errors="backslashreplace").decode(encoding)
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Create or update production blog tags for the CS association blog."
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Print planned tags without writing changes.")
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
dry_run = options["dry_run"]
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
for name in TAGS:
|
||||||
|
self.stdout.write(console_safe(f"[tag] {name}"))
|
||||||
|
self.stdout.write(self.style.WARNING("Dry run only. No tags were changed."))
|
||||||
|
return
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for name in TAGS:
|
||||||
|
Tag.all_objects.update_or_create(
|
||||||
|
name=name,
|
||||||
|
defaults={
|
||||||
|
"is_deleted": False,
|
||||||
|
"deleted_at": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Blog tags synchronized: {count} tags."))
|
||||||
Reference in New Issue
Block a user