Browse Source

V2 lesson list swagger docs updated + script added for courses without message rooms

master
sina_sajjadi 2 months ago
parent
commit
8b1e3da427
  1. 50
      apps/course/doc.py
  2. 63
      apps/course/management/commands/ensure_course_rooms.py
  3. 77
      apps/course/views/lesson.py

50
apps/course/doc.py

@ -112,6 +112,56 @@ def doc_courses_lesson():
"""
def doc_courses_lesson_v2():
return """
# Scenario
Nested chapter-based lesson list for a course.
This endpoint returns active chapters for a course, and inside each chapter returns active lessons in priority order.
Important behavior:
- Pagination is applied on chapters, not individual lessons.
- `lessons` is a nested array under each chapter.
- `permission` indicates whether the current user can access lesson content.
- `is_complated` indicates whether the authenticated user has completed the lesson.
- `quizs` is either `null` or a list of quizzes related to that lesson.
Request:
`GET /api/course/v2/<slug>/lessons/`
Success example:
```json
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 14,
"title": "فصل 1",
"priority": 1,
"is_active": true,
"lessons": [
{
"id": 52,
"title": "Greetings",
"priority": 1,
"is_active": true,
"permission": true,
"duration": 16,
"content_type": "youtube_link",
"content_file": null,
"video_link": "https://youtu.be/t5Qo7W5a8qQ?si=WKPxmb4G9F0Va_6e",
"is_complated": false,
"quizs": null
}
]
}
]
}
```
"""
def doc_courses_my_courses():
return """

63
apps/course/management/commands/ensure_course_rooms.py

@ -0,0 +1,63 @@
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Exists, OuterRef
from django.utils.translation import gettext_lazy as _
from apps.chat.models import RoomMessage
from apps.course.models import Course
class Command(BaseCommand):
help = _('Ensure every course has a group chat room')
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
dest='dry_run',
help=_('Show courses missing group rooms without creating them'),
)
def handle(self, *args, **options):
dry_run = options['dry_run']
group_room_exists = RoomMessage.objects.filter(
course=OuterRef('pk'),
room_type=RoomMessage.RoomTypeChoices.GROUP,
)
courses_without_group_room = Course.objects.select_related('professor').annotate(
has_group_room=Exists(group_room_exists)
).filter(
has_group_room=False
)
missing_count = courses_without_group_room.count()
if missing_count == 0:
self.stdout.write(self.style.SUCCESS(_('All courses already have a group room.')))
return
self.stdout.write(self.style.WARNING(_(f'Found {missing_count} course(s) without a group room.')))
for course in courses_without_group_room:
self.stdout.write(f'- Course #{course.id}: {course.title}')
if dry_run:
self.stdout.write(self.style.WARNING(_('Dry run completed. No rooms were created.')))
return
created_count = 0
with transaction.atomic():
for course in courses_without_group_room:
RoomMessage.objects.create(
name=f"{course.title} - Group",
description=f"Group chat for course: {course.title}",
initiator=course.professor,
course=course,
room_type=RoomMessage.RoomTypeChoices.GROUP,
is_locked=course.is_group_chat_locked,
)
created_count += 1
self.stdout.write(self.style.SUCCESS(_(f'Created {created_count} missing group room(s).')))

77
apps/course/views/lesson.py

@ -189,8 +189,81 @@ class LessonListV2APIView(ListAPIView):
authentication_classes = [TokenAuthentication]
@swagger_auto_schema(
operation_description="V2: Get a nested list of chapters and their lessons",
operation_description=doc_courses_lesson_v2(),
tags=['Imam-Javad - Course (V2)'],
manual_parameters=[
openapi.Parameter(
'slug', openapi.IN_PATH,
description="Course slug",
type=openapi.TYPE_STRING,
required=True
),
openapi.Parameter(
'page', openapi.IN_QUERY,
description="Page number for chapter pagination",
type=openapi.TYPE_INTEGER,
required=False
),
],
responses={
200: openapi.Response(
description="Paginated list of active chapters with nested active lessons",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'count': openapi.Schema(type=openapi.TYPE_INTEGER),
'next': openapi.Schema(type=openapi.TYPE_STRING, format='uri', nullable=True),
'previous': openapi.Schema(type=openapi.TYPE_STRING, format='uri', nullable=True),
'results': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'id': openapi.Schema(type=openapi.TYPE_INTEGER),
'title': openapi.Schema(type=openapi.TYPE_STRING),
'priority': openapi.Schema(type=openapi.TYPE_INTEGER),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN),
'lessons': openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'id': openapi.Schema(type=openapi.TYPE_INTEGER),
'title': openapi.Schema(type=openapi.TYPE_STRING),
'priority': openapi.Schema(type=openapi.TYPE_INTEGER),
'is_active': openapi.Schema(type=openapi.TYPE_BOOLEAN),
'permission': openapi.Schema(type=openapi.TYPE_BOOLEAN),
'duration': openapi.Schema(type=openapi.TYPE_INTEGER, nullable=True),
'content_type': openapi.Schema(type=openapi.TYPE_STRING, nullable=True),
'content_file': openapi.Schema(type=openapi.TYPE_STRING, nullable=True),
'video_link': openapi.Schema(type=openapi.TYPE_STRING, nullable=True),
'is_complated': openapi.Schema(type=openapi.TYPE_BOOLEAN),
'quizs': openapi.Schema(
type=openapi.TYPE_ARRAY,
nullable=True,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
'id': openapi.Schema(type=openapi.TYPE_INTEGER),
'title': openapi.Schema(type=openapi.TYPE_STRING),
'description': openapi.Schema(type=openapi.TYPE_STRING, nullable=True),
'permission': openapi.Schema(type=openapi.TYPE_BOOLEAN),
'each_question_timing': openapi.Schema(type=openapi.TYPE_INTEGER, nullable=True),
'is_complated': openapi.Schema(type=openapi.TYPE_BOOLEAN),
}
)
),
}
)
),
}
)
),
}
)
),
404: openapi.Response(description="Course not found"),
}
)
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
@ -208,4 +281,4 @@ class LessonListV2APIView(ListAPIView):
).filter(
course=course,
is_active=True
).order_by('priority', 'id')
).order_by('priority', 'id')
Loading…
Cancel
Save