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.
 
 
 
 

199 lines
7.5 KiB

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 course
lessons_count = CourseLesson.objects.filter(course=course).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}")