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.
 
 
 
 

224 lines
8.6 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. Determine lesson title: use custom title or subject (with parts if multiple recordings exist)
base_title = session.recording_title or session.subject
# Get all recordings for this session, ordered by creation
recordings = list(LiveSessionRecording.objects.filter(session=session).order_by('created_at', 'id'))
recordings_count = len(recordings)
if recordings_count <= 1:
lesson_title = base_title
else:
try:
current_index = recordings.index(instance) + 1
except ValueError:
current_index = recordings_count
lesson_title = f"{base_title} part {current_index}"
# Retroactively rename the first lesson if this is a subsequent recording
if current_index > 1:
first_recording = recordings[0]
if first_recording.file:
first_lesson = Lesson.objects.filter(content_file=first_recording.file.name).first()
if first_lesson:
first_lesson_title = f"{base_title} part 1"
first_lesson.title = first_lesson_title
first_lesson.save(update_fields=['title'])
# Also update corresponding CourseLesson
CourseLesson.objects.filter(lesson=first_lesson).update(title=first_lesson_title)
# 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}")