import logging from django.utils import timezone from apps.course.models import Course from apps.chat.models import RoomMessage from apps.course.models.course import Course, extract_text_from_json from apps.course.models.lesson import CourseLesson from apps.course.models.live_session import LiveSessionRecording from django.db.models import Q logger = logging.getLogger(__name__) from django.db.models.signals import post_save, post_delete, pre_save from django.dispatch import receiver from django.core.cache import cache from django.contrib.auth import get_user_model UserModel = get_user_model() @receiver(post_save, sender=Course) def handle_room_message_for_course(sender, instance, created, **kwargs): title_str = extract_text_from_json(instance.title) if created: # فقط برای موارد جدید اجرا شود RoomMessage.objects.create( name=f"{title_str} - Group", description=f"Group chat for course: {title_str}", initiator=instance.professor, # استاد به‌عنوان سازنده اتاق course=instance, room_type=RoomMessage.RoomTypeChoices.GROUP ) else: # این بخش در زمان آپدیت دوره اجرا می‌شود # Find the existing group room for this course and update its details RoomMessage.objects.filter( course=instance, room_type=RoomMessage.RoomTypeChoices.GROUP ).update( name=f"{title_str} - Group", description=f"Group chat for course: {title_str}", initiator=instance.professor ) @receiver(post_save, sender=Course) def ensure_professor_role(sender, instance, **kwargs): professor = getattr(instance, 'professor', None) if professor: professor.ensure_professor_profile() @receiver([post_save, post_delete], sender=Course) def invalidate_professor_course_cache(sender, instance, **kwargs): """ Clears the cached professor detail page AND their course list whenever a course assigned to them is created, updated, or deleted. """ if instance.professor: detail_cache_key = f"professor_detail_{instance.professor.slug}" cache.delete(detail_cache_key) # Optional: If you update a Professor's profile in the admin directly @receiver([post_save, post_delete], sender=UserModel) def invalidate_professor_profile_cache(sender, instance, **kwargs): if instance.user_type == UserModel.UserType.PROFESSOR: cache_key = f"professor_detail_{instance.slug}" cache.delete(cache_key) @receiver(post_save, sender=Course) def sync_course_chat_locks(sender, instance, **kwargs): """ Automatically locks/unlocks the related chat rooms when the admin toggles the chat locks on the Course page. """ # 1. Update the Group Chat RoomMessage.objects.filter( course=instance, room_type=RoomMessage.RoomTypeChoices.GROUP ).update(is_locked=instance.is_group_chat_locked) # 2. Update the Private Chats between the Professor and Students of this course # Get all student IDs enrolled in this course student_ids = instance.participants.values_list('student_id', flat=True) if student_ids: RoomMessage.objects.filter( room_type=RoomMessage.RoomTypeChoices.PRIVATE ).filter( # Find rooms where initiator is prof and recipient is student, OR vice versa Q(initiator_id=instance.professor_id, recipient_id__in=student_ids) | Q(initiator_id__in=student_ids, recipient_id=instance.professor_id) ).update(is_locked=instance.is_professor_chat_locked) def _recalculate_course_lessons_count(course_id): Course.recalculate_lessons_count_for_course(course_id) @receiver(pre_save, sender=CourseLesson) def capture_previous_course_lesson_state(sender, instance, **kwargs): if not instance.pk: instance._previous_course_id = None instance._previous_is_active = None return previous = sender.objects.filter(pk=instance.pk).values('course_id', 'is_active').first() if previous: instance._previous_course_id = previous['course_id'] instance._previous_is_active = previous['is_active'] else: instance._previous_course_id = None instance._previous_is_active = None @receiver(post_save, sender=CourseLesson) def sync_lessons_count_on_course_lesson_save(sender, instance, **kwargs): affected_course_ids = {instance.course_id} previous_course_id = getattr(instance, '_previous_course_id', None) previous_is_active = getattr(instance, '_previous_is_active', None) if previous_course_id and previous_course_id != instance.course_id: affected_course_ids.add(previous_course_id) if previous_is_active is not None and previous_is_active != instance.is_active and previous_course_id: affected_course_ids.add(previous_course_id) for course_id in affected_course_ids: _recalculate_course_lessons_count(course_id) @receiver(post_delete, sender=CourseLesson) def sync_lessons_count_on_course_lesson_delete(sender, instance, **kwargs): _recalculate_course_lessons_count(instance.course_id) @receiver(post_save, sender=LiveSessionRecording) def create_course_lesson_from_recording(sender, instance, **kwargs): """ Automatically creates a CourseChapter (if not exists) and CourseLesson whenever a LiveSessionRecording is saved with a valid file. """ if not instance.file: return # To avoid duplicate creation if save is called multiple times from apps.course.models.lesson import Lesson, CourseChapter, CourseLesson if Lesson.objects.filter(content_file=instance.file.name).exists(): return session = instance.session course = session.course # 1. Determine chapter title as recording date (YYYY-MM-DD) date_val = instance.created_at or timezone.now() recording_date_str = date_val.strftime('%Y-%m-%d') # 2. Get or create CourseChapter for this date from apps.course.models.course import extract_text_from_json chapter = None for ch in CourseChapter.objects.filter(course=course): if extract_text_from_json(ch.title) == recording_date_str: chapter = ch break if not chapter: chapter = CourseChapter.objects.create( course=course, title=recording_date_str, is_active=True ) # 3. Calculate next part number based on total lessons in the chapter lessons_count = CourseLesson.objects.filter(chapter=chapter).count() part_num = lessons_count + 1 lesson_title = f"Part {part_num}" # 4. Calculate duration in minutes duration_minutes = 0 if instance.file_time: duration_minutes = int(instance.file_time.total_seconds() / 60) if duration_minutes == 0 and instance.file_time.total_seconds() > 0: duration_minutes = 1 # 5. Create the Lesson object lesson = Lesson.objects.create( title=lesson_title, content_type="video_file", content_file=instance.file, duration=duration_minutes ) # 6. Create the CourseLesson object linking it to the chapter course_lesson = CourseLesson.objects.create( course=course, chapter=chapter, lesson=lesson, title=lesson_title, is_active=True ) logger.info(f"✨ [Signal] Automatically created CourseLesson {course_lesson.id} ({lesson_title}) for course {course.id} from recording {instance.id}")