Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [AXM-40] add courses progress to enrollment endpoint #2519

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions lms/djangoapps/mobile_api/users/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from typing import Dict, List, Optional, Tuple

from django.core.cache import cache
from completion.exceptions import UnavailableCompletionData
from completion.utilities import get_key_to_last_completed_block
from rest_framework import serializers
Expand All @@ -17,6 +18,8 @@
from lms.djangoapps.courseware.block_render import get_block_for_descriptor
from lms.djangoapps.courseware.courses import get_current_child
from lms.djangoapps.courseware.model_data import FieldDataCache
from lms.djangoapps.grades.api import CourseGradeFactory
from openedx.core.djangoapps.content.block_structure.api import get_block_structure_manager
from openedx.features.course_duration_limits.access import get_user_course_expiration_date
from xmodule.modulestore.django import modulestore

Expand Down Expand Up @@ -154,7 +157,11 @@ class CourseEnrollmentSerializerModifiedForPrimary(CourseEnrollmentSerializer):

Adds `course_status` field into serializer data.
"""

course_status = serializers.SerializerMethodField()
progress = serializers.SerializerMethodField()

BLOCK_STRUCTURE_CACHE_TIMEOUT = 60 * 60 # 1 hour

def get_course_status(self, model: CourseEnrollment) -> Optional[Dict[str, List[str]]]:
"""
Expand Down Expand Up @@ -213,6 +220,29 @@ def _get_last_visited_block_path_and_unit_name(
path.reverse()
return path, unit_name

def get_progress(self, model: CourseEnrollment) -> Dict[str, int]:
"""
Returns the progress of the user in the course.
"""
assert isinstance(model, CourseEnrollment), f'Expected CourseEnrollment, got {type(model)}'
is_staff = bool(has_access(model.user, 'staff', model.course.id))

cache_key = f'course_block_structure_{str(model.course.id)}_{model.user.id}'
collected_block_structure = cache.get(cache_key)
if not collected_block_structure:
collected_block_structure = get_block_structure_manager(model.course.id).get_collected()
cache.set(cache_key, collected_block_structure, self.BLOCK_STRUCTURE_CACHE_TIMEOUT)

course_grade = CourseGradeFactory().read(model.user, collected_block_structure=collected_block_structure)

# recalculate course grade from visible grades (stored grade was calculated over all grades, visible or not)
course_grade.update(visible_grades_only=True, has_staff_access=is_staff)
subsection_grades = list(course_grade.subsection_grades.values())
return {
'num_points_earned': sum(map(lambda x: x.graded_total.earned if x.graded else 0, subsection_grades)),
'num_points_possible': sum(map(lambda x: x.graded_total.possible if x.graded else 0, subsection_grades)),
}

class Meta:
model = CourseEnrollment
fields = (
Expand All @@ -224,6 +254,7 @@ class Meta:
'certificate',
'course_modes',
'course_status',
'progress',
)
lookup_field = 'username'

Expand Down
16 changes: 10 additions & 6 deletions lms/djangoapps/mobile_api/users/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


import logging
from functools import cached_property
from typing import List, Optional

from completion.exceptions import UnavailableCompletionData
Expand Down Expand Up @@ -324,7 +325,7 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
certified).
* url: URL to the downloadable version of the certificate, if exists.
"""
queryset = CourseEnrollment.objects.all()

lookup_field = 'username'

# In Django Rest Framework v3, there is a default pagination
Expand Down Expand Up @@ -352,6 +353,13 @@ def get_serializer_class(self):
return CourseEnrollmentSerializerv05
return CourseEnrollmentSerializer

@cached_property
def queryset(self):
return CourseEnrollment.objects.all().select_related('course', 'user').filter(
user__username=self.kwargs['username'],
is_active=True
).order_by('created').reverse()

def get_queryset(self):
api_version = self.kwargs.get('api_version')
mobile_available = self.get_mobile_available_enrollments()
Expand All @@ -377,14 +385,10 @@ def get_mobile_available_enrollments(self) -> List[Optional[CourseEnrollment]]:
"""
Gets list with `CourseEnrollment` for mobile available courses.
"""
enrollments = self.queryset.filter(
user__username=self.kwargs['username'],
is_active=True
).order_by('created').reverse()
org = self.request.query_params.get('org', None)

same_org = (
enrollment for enrollment in enrollments
enrollment for enrollment in self.queryset
if enrollment.course_overview and self.is_org(org, enrollment.course_overview.org)
)
mobile_available = (
Expand Down
Loading