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.
117 lines
4.9 KiB
117 lines
4.9 KiB
import random
|
|
import sys
|
|
from django.core.management.base import BaseCommand
|
|
from django.db import transaction
|
|
from apps.course.models import Course, CourseChapter, CourseLesson
|
|
|
|
class Command(BaseCommand):
|
|
help = 'Redistributes course lessons into multiple chapters randomly (maintaining order) for courses with >= 2 lessons.'
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
'--dry-run',
|
|
action='store_true',
|
|
help='Simulates the redistribution without modifying the database.',
|
|
)
|
|
parser.add_argument(
|
|
'--max-chapters',
|
|
type=int,
|
|
default=5,
|
|
help='Maximum number of chapters to create for a course (default: 5).',
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
# Reconfigure stdout to UTF-8 to prevent UnicodeEncodeError on Windows terminals
|
|
try:
|
|
sys.stdout.reconfigure(encoding='utf-8')
|
|
except AttributeError:
|
|
pass
|
|
|
|
dry_run = options['dry_run']
|
|
max_chapters = options['max_chapters']
|
|
|
|
if dry_run:
|
|
self.stdout.write(self.style.WARNING("⚠️ DRY RUN MODE: No database changes will be saved."))
|
|
|
|
self.stdout.write(self.style.WARNING("Starting lesson redistribution..."))
|
|
|
|
courses = Course.objects.all()
|
|
courses_updated = 0
|
|
total_chapters_created = 0
|
|
total_lessons_redistributed = 0
|
|
|
|
# Wrap everything in an atomic transaction to ensure safety
|
|
with transaction.atomic():
|
|
for course in courses:
|
|
lessons = list(course.lessons.all().order_by('priority'))
|
|
n = len(lessons)
|
|
|
|
if n < 2:
|
|
self.stdout.write(self.style.NOTICE(
|
|
f"Skipping course: '{course.title}' (ID: {course.id}) - Only has {n} lesson(s)."
|
|
))
|
|
continue
|
|
|
|
# Number of chapters C must be between 2 and min(N, max_chapters)
|
|
c = random.randint(2, min(n, max_chapters))
|
|
|
|
self.stdout.write(self.style.MIGRATE_HEADING(
|
|
f"\nReorganizing Course: '{course.title}' (ID: {course.id})"
|
|
f"\n Total lessons: {n} | Splitting into {c} chapters..."
|
|
))
|
|
|
|
# Partition the ordered lessons list into C slices
|
|
# We randomly pick C-1 unique split boundaries between 1 and n-1
|
|
split_points = sorted(random.sample(range(1, n), c - 1))
|
|
|
|
partitioned_lessons = []
|
|
last_idx = 0
|
|
for sp in split_points:
|
|
partitioned_lessons.append(lessons[last_idx:sp])
|
|
last_idx = sp
|
|
partitioned_lessons.append(lessons[last_idx:])
|
|
|
|
if not dry_run:
|
|
# 1. Unlink lessons from chapters first to prevent CASCADE deletion.
|
|
# Using .update() bypasses the save() method and avoids AttributeErrors
|
|
# caused by _adjust_priorities when chapter is None.
|
|
course.lessons.all().update(chapter=None)
|
|
|
|
# 2. Delete all existing chapters for this course
|
|
course.chapters.all().delete()
|
|
|
|
# 3. Create the new chapters and assign the sliced lessons
|
|
for i, chapter_lessons in enumerate(partitioned_lessons):
|
|
chapter_title = f"فصل {i+1}"
|
|
priority = i + 1
|
|
|
|
self.stdout.write(f" 📂 Chapter '{chapter_title}' (priority {priority}) contains:")
|
|
for idx, lesson in enumerate(chapter_lessons):
|
|
self.stdout.write(f" - [{idx + 1}] {lesson.title} (Original ID: {lesson.id})")
|
|
|
|
if not dry_run:
|
|
# Create the new chapter
|
|
chapter = CourseChapter.objects.create(
|
|
course=course,
|
|
title=chapter_title,
|
|
priority=priority
|
|
)
|
|
|
|
# Assign and save each lesson within this chapter
|
|
for idx, lesson in enumerate(chapter_lessons):
|
|
lesson.chapter = chapter
|
|
lesson.priority = idx + 1
|
|
lesson.save()
|
|
|
|
total_chapters_created += 1
|
|
total_lessons_redistributed += len(chapter_lessons)
|
|
|
|
courses_updated += 1
|
|
|
|
if dry_run:
|
|
self.stdout.write(self.style.WARNING("\n⚠️ Dry run complete. No database changes were made."))
|
|
else:
|
|
self.stdout.write(self.style.SUCCESS(
|
|
f"\n🎉 Successfully redistributed {total_lessons_redistributed} lessons "
|
|
f"across {total_chapters_created} chapters in {courses_updated} courses!"
|
|
))
|