Initial commit - clean project structure
This commit is contained in:
87
content/services.py
Normal file
87
content/services.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
منطق تغییر 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()
|
||||
Reference in New Issue
Block a user