Browse Source

course filters updated

master
Mohsen Taba 2 months ago
parent
commit
b45860cfbd
  1. 145
      apps/course/tests/test_my_courses_api.py
  2. 49
      apps/course/views/course.py

145
apps/course/tests/test_my_courses_api.py

@ -0,0 +1,145 @@
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from apps.account.models import ProfessorUser, StudentUser
from apps.course.models.course import Course, CourseCategory
from apps.course.models.lesson import CourseChapter, CourseLesson, Lesson, LessonCompletion
from apps.course.models.participant import Participant
class MyCoursesAPITests(APITestCase):
def setUp(self):
self.professor = ProfessorUser.objects.create(
email='prof@example.com',
fullname='Professor Example',
experience_years=5,
)
self.student = StudentUser.objects.create(
email='student@example.com',
fullname='Student Example',
)
self.category = CourseCategory.objects.create(
name='Category',
slug='category',
)
def _create_course(self, slug, title):
thumbnail = SimpleUploadedFile('thumb.jpg', b'filecontent', content_type='image/jpeg')
return Course.objects.create(
title=title,
slug=slug,
category=self.category,
professor=self.professor,
thumbnail=thumbnail,
video_type=Course.VedioTypeChoices.YOUTUBE_LINK,
video_link='https://example.com/video',
is_online=False,
level=Course.LevelChoices.BEGINNER,
duration=10,
lessons_count=0,
description='Description',
short_description='Short description',
status=Course.StatusChoices.ONGOING,
is_free=True,
)
def _attach_lessons(self, course, lesson_specs):
chapter = CourseChapter.objects.create(
course=course,
title=f'{course.title} Chapter',
priority=1,
is_active=True,
)
created_lessons = []
for index, spec in enumerate(lesson_specs, start=1):
lesson = Lesson.objects.create(
title=spec['title'],
content_type=Lesson.ContentTypeChoices.VIDEO_FILE,
duration=5,
)
course_lesson = CourseLesson.objects.create(
course=course,
chapter=chapter,
lesson=lesson,
priority=index,
is_active=spec.get('is_active', True),
)
created_lessons.append(course_lesson)
return created_lessons
def test_completed_filter_returns_only_courses_with_all_active_lessons_completed(self):
completed_course = self._create_course('completed-course', 'Completed Course')
partial_course = self._create_course('partial-course', 'Partial Course')
empty_course = self._create_course('empty-course', 'Empty Course')
completed_lessons = self._attach_lessons(
completed_course,
[
{'title': 'Completed Lesson 1'},
{'title': 'Completed Lesson 2'},
{'title': 'Inactive Lesson', 'is_active': False},
],
)
partial_lessons = self._attach_lessons(
partial_course,
[
{'title': 'Partial Lesson 1'},
{'title': 'Partial Lesson 2'},
],
)
Participant.objects.create(student=self.student, course=completed_course)
Participant.objects.create(student=self.student, course=partial_course)
Participant.objects.create(student=self.student, course=empty_course)
LessonCompletion.objects.create(student=self.student, course_lesson=completed_lessons[0])
LessonCompletion.objects.create(student=self.student, course_lesson=completed_lessons[1])
LessonCompletion.objects.create(student=self.student, course_lesson=partial_lessons[0])
self.client.force_authenticate(user=self.student)
url = reverse('course-my-courses-list')
response = self.client.get(url, {'completed': 'true'}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertEqual({item['slug'] for item in response.data['results']}, {completed_course.slug})
def test_my_courses_without_completed_filter_returns_completed_and_incomplete_courses(self):
completed_course = self._create_course('all-completed-course', 'All Completed Course')
partial_course = self._create_course('all-partial-course', 'All Partial Course')
completed_lessons = self._attach_lessons(
completed_course,
[
{'title': 'Completed Lesson 1'},
],
)
partial_lessons = self._attach_lessons(
partial_course,
[
{'title': 'Partial Lesson 1'},
{'title': 'Partial Lesson 2'},
],
)
Participant.objects.create(student=self.student, course=completed_course)
Participant.objects.create(student=self.student, course=partial_course)
LessonCompletion.objects.create(student=self.student, course_lesson=completed_lessons[0])
LessonCompletion.objects.create(student=self.student, course_lesson=partial_lessons[0])
self.client.force_authenticate(user=self.student)
url = reverse('course-my-courses-list')
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 2)
self.assertEqual(
{item['slug'] for item in response.data['results']},
{completed_course.slug, partial_course.slug},
)

49
apps/course/views/course.py

@ -240,27 +240,42 @@ class MyCourseListAPIView(ListAPIView):
# Include courses where user is a student OR the professor # Include courses where user is a student OR the professor
qs = queryset.filter(Q(participants__student=user) | Q(professor=user)).distinct() qs = queryset.filter(Q(participants__student=user) | Q(professor=user)).distinct()
completed_only = filters.get('completed', '').lower() == 'true'
if completed_only == True:
# نمایش دوره‌هایی که همه درس‌هایشان توسط کاربر تکمیل شده‌اند
qs = qs.annotate(
# Count distinct lessons across all chapters
total_lessons=Count('chapters__lessons', distinct=True),
completed_lessons=Count(
'chapters__lessons__completions',
filter=Q(chapters__lessons__completions__student=user),
distinct=True
)
).filter(total_lessons=F('completed_lessons'))
elif completed_only == False:
completed_param = filters.get('completed')
if completed_param is not None:
completed_only = completed_param.lower() == 'true'
# Only active chapters/lessons should participate in completion checks.
total_lessons_filter = Q(
chapters__is_active=True,
chapters__lessons__is_active=True,
)
completed_lessons_filter = Q(
chapters__is_active=True,
chapters__lessons__is_active=True,
chapters__lessons__completions__student=user,
)
qs = qs.annotate( qs = qs.annotate(
total_lessons=Count('chapters__lessons', distinct=True),
total_lessons=Count(
'chapters__lessons',
filter=total_lessons_filter,
distinct=True,
),
completed_lessons=Count( completed_lessons=Count(
'chapters__lessons__completions', 'chapters__lessons__completions',
filter=Q(chapters__lessons__completions__student=user),
distinct=True
filter=completed_lessons_filter,
distinct=True,
),
)
if completed_only:
# A course is "completed" only when every active lesson is completed.
# Courses with no active lessons should not be marked as completed.
qs = qs.filter(
total_lessons__gt=0,
total_lessons=F('completed_lessons'),
) )
).filter(total_lessons__gt=F('completed_lessons'))
else:
qs = qs.filter(total_lessons__gt=F('completed_lessons'))
if 'completed' not in filters: if 'completed' not in filters:
certificate = filters.get('certificate', '').lower() == 'true' certificate = filters.get('certificate', '').lower() == 'true'

Loading…
Cancel
Save