diff --git a/apps/blog/management/commands/seed_blog_categories.py b/apps/blog/management/commands/seed_blog_categories.py new file mode 100644 index 0000000..8ad650d --- /dev/null +++ b/apps/blog/management/commands/seed_blog_categories.py @@ -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 diff --git a/apps/blog/management/commands/seed_blog_tags.py b/apps/blog/management/commands/seed_blog_tags.py new file mode 100644 index 0000000..7b66218 --- /dev/null +++ b/apps/blog/management/commands/seed_blog_tags.py @@ -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."))