88 lines
3.4 KiB
Python
88 lines
3.4 KiB
Python
"""
|
||
منطق تغییر status روی NodeProgress. دو نقطهی ورودی، هر دو از
|
||
content/receivers.py صدا زده میشن (نه مستقیم از views.py) تا اگه جای
|
||
دیگهای هم (مثلاً یک import/bulk-completion از ai_tutor) رکورد UserAnswer
|
||
یا UserLessonCompletion ساخته بشه، وضعیت Node هم خودکار بهروز بمونه —
|
||
دقیقاً همون الگوی signal-based که برای discount/subscription استفاده شده.
|
||
"""
|
||
from django.utils import timezone
|
||
|
||
from statuslog.signals import status_changed
|
||
|
||
from .models import Node, NodeProgress, UserLessonCompletion
|
||
|
||
|
||
def _get_or_create_progress(user_id, node):
|
||
return NodeProgress.objects.get_or_create(user_id=user_id, node=node)
|
||
|
||
|
||
def _emit_if_changed(progress, old_status, event, user_id):
|
||
if progress.status != old_status:
|
||
status_changed.send(
|
||
sender='content.NodeProgress', object_id=progress.id,
|
||
old_status=old_status, new_status=progress.status,
|
||
event=event, user_id=user_id,
|
||
)
|
||
|
||
|
||
def touch_node_progress_on_answer(user_id, question):
|
||
"""
|
||
وقتی کاربر به یک سوال پاسخ میده (چه درست چه غلط)، یعنی روی Node
|
||
مربوطه فعالیت داشته. اگه Node هنوز شروع نشده یا رها شده بوده،
|
||
به in_progress برمیگرده.
|
||
"""
|
||
node = question.lesson.node
|
||
progress, _created = _get_or_create_progress(user_id, node)
|
||
old_status = progress.status
|
||
now = timezone.now()
|
||
|
||
if progress.status in ('not_started', 'abandoned'):
|
||
progress.status = 'in_progress'
|
||
if progress.started_at is None:
|
||
progress.started_at = now
|
||
progress.last_activity_at = now
|
||
progress.save(update_fields=['status', 'started_at', 'last_activity_at'])
|
||
|
||
_emit_if_changed(progress, old_status, event='answer_activity', user_id=user_id)
|
||
return progress
|
||
|
||
|
||
def update_node_progress_on_lesson_completion(user_id, lesson):
|
||
"""
|
||
وقتی یک Lesson تکمیل میشه، تعداد Lessonهای تکمیلشدهی Node رو
|
||
بهروز میکنه؛ اگه همهی Lessonهای Node تکمیل شده باشن، status
|
||
میره روی completed، وگرنه (یا اگه از abandoned برگشته) in_progress.
|
||
"""
|
||
node = lesson.node
|
||
progress, _created = _get_or_create_progress(user_id, node)
|
||
old_status = progress.status
|
||
now = timezone.now()
|
||
|
||
lesson_ids = list(node.lessons.values_list('id', flat=True))
|
||
completed_count = UserLessonCompletion.objects.filter(
|
||
user_id=user_id, lesson_id__in=lesson_ids
|
||
).values('lesson_id').distinct().count()
|
||
|
||
progress.lessons_completed_count = completed_count
|
||
if progress.started_at is None:
|
||
progress.started_at = now
|
||
progress.last_activity_at = now
|
||
|
||
if lesson_ids and completed_count >= len(lesson_ids):
|
||
progress.status = 'completed'
|
||
progress.completed_at = now
|
||
else:
|
||
progress.status = 'in_progress'
|
||
|
||
progress.save(update_fields=[
|
||
'lessons_completed_count', 'status', 'started_at', 'completed_at', 'last_activity_at',
|
||
])
|
||
|
||
_emit_if_changed(progress, old_status, event='lesson_completed', user_id=user_id)
|
||
return progress
|
||
|
||
|
||
def node_progress_summary(node):
|
||
"""برای سریالایزر: تعداد کل Lessonهای یک Node (لازم برای lessonsTotal)."""
|
||
return node.lessons.count()
|