diff --git a/lms/djangoapps/mobile_api/users/serializers.py b/lms/djangoapps/mobile_api/users/serializers.py index 64dffed1f045..f082f0fe8591 100644 --- a/lms/djangoapps/mobile_api/users/serializers.py +++ b/lms/djangoapps/mobile_api/users/serializers.py @@ -2,6 +2,7 @@ Serializer for user API """ +from datetime import datetime from typing import Dict, List, Optional, Tuple from django.core.cache import cache @@ -16,8 +17,10 @@ from lms.djangoapps.certificates.api import certificate_downloadable_status from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.block_render import get_block_for_descriptor -from lms.djangoapps.courseware.courses import get_current_child +from lms.djangoapps.courseware.context_processor import get_user_timezone_or_last_seen_timezone_or_utc +from lms.djangoapps.courseware.courses import get_course_assignment_date_blocks, get_current_child from lms.djangoapps.courseware.model_data import FieldDataCache +from lms.djangoapps.course_home_api.dates.serializers import DateSummarySerializer 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 @@ -160,9 +163,15 @@ class CourseEnrollmentSerializerModifiedForPrimary(CourseEnrollmentSerializer): course_status = serializers.SerializerMethodField() progress = serializers.SerializerMethodField() + course_assignments = serializers.SerializerMethodField() BLOCK_STRUCTURE_CACHE_TIMEOUT = 60 * 60 # 1 hour + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # import pdb; pdb.set_trace() + self.course = modulestore().get_course(self.instance.course.id) + def get_course_status(self, model: CourseEnrollment) -> Optional[Dict[str, List[str]]]: """ Gets course status for the given user's enrollments. @@ -186,8 +195,8 @@ def get_course_status(self, model: CourseEnrollment) -> Optional[Dict[str, List[ 'last_visited_unit_display_name': unit_name, } - @staticmethod def _get_last_visited_block_path_and_unit_name( + self, request: 'Request', # noqa: F821 model: CourseEnrollment, ) -> Tuple[List[Optional['XBlock']], Optional[str]]: # noqa: F821 @@ -197,12 +206,10 @@ def _get_last_visited_block_path_and_unit_name( If there is no such visit, the first item deep enough down the course tree is used. """ - course = modulestore().get_course(model.course.id) - field_data_cache = FieldDataCache.cache_for_block_descendents( - course.id, model.user, course, depth=3) + field_data_cache = FieldDataCache.cache_for_block_descendents(self.course.id, model.user, self.course, depth=3) course_block = get_block_for_descriptor( - model.user, request, course, field_data_cache, course.id, course=course + model.user, request, self.course, field_data_cache, self.course.id, course=self.course ) unit_name = '' @@ -243,6 +250,32 @@ def get_progress(self, model: CourseEnrollment) -> Dict[str, int]: 'num_points_possible': sum(map(lambda x: x.graded_total.possible if x.graded else 0, subsection_grades)), } + def get_course_assignments(self, model: CourseEnrollment) -> Optional[Dict[str, List[Dict[str, str]]]]: + """ + Returns the future assignment data and past assignments data for the user in the course. + """ + assignments = get_course_assignment_date_blocks( + self.course, + model.user, + self.context.get('request'), + include_past_dates=True + ) + next_assignment = {} + past_assignment = [] + + timezone = get_user_timezone_or_last_seen_timezone_or_utc(model.user) + for assignment in sorted(assignments, key=lambda x: x.date): + if assignment.date < datetime.now(timezone): + past_assignment.append(assignment) + else: + if not next_assignment: + next_assignment = DateSummarySerializer(assignment).data + + return { + 'future_assignment': next_assignment, + 'past_assignments': DateSummarySerializer(past_assignment, many=True).data, + } + class Meta: model = CourseEnrollment fields = ( @@ -255,6 +288,7 @@ class Meta: 'course_modes', 'course_status', 'progress', + 'course_assignments', ) lookup_field = 'username' diff --git a/lms/djangoapps/mobile_api/users/views.py b/lms/djangoapps/mobile_api/users/views.py index 2463ef963b9e..2c5e7736b288 100644 --- a/lms/djangoapps/mobile_api/users/views.py +++ b/lms/djangoapps/mobile_api/users/views.py @@ -12,7 +12,7 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user from django.contrib.auth.signals import user_logged_in from django.db import transaction -from django.shortcuts import redirect +from django.shortcuts import get_object_or_404, redirect from django.utils import dateparse from django.utils.decorators import method_decorator from opaque_keys import InvalidKeyError @@ -28,6 +28,7 @@ from common.djangoapps.student.models import CourseEnrollment, User # lint-amnesty, pylint: disable=reimported from lms.djangoapps.courseware.access import is_mobile_available_for_user from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED +from lms.djangoapps.courseware.context_processor import get_user_timezone_or_last_seen_timezone_or_utc from lms.djangoapps.courseware.courses import get_current_child from lms.djangoapps.courseware.model_data import FieldDataCache from lms.djangoapps.courseware.block_render import get_block_for_descriptor @@ -358,7 +359,7 @@ def queryset(self): return CourseEnrollment.objects.all().select_related('course', 'user').filter( user__username=self.kwargs['username'], is_active=True - ).order_by('created').reverse() + ).order_by('-created') def get_queryset(self): api_version = self.kwargs.get('api_version') @@ -404,6 +405,7 @@ def list(self, request, *args, **kwargs): if api_version in (API_V2, API_V3, API_V4): enrollment_data = { 'configs': MobileConfig.get_structured_configs(), + 'user_timezone': str(get_user_timezone_or_last_seen_timezone_or_utc(self.get_user())), 'enrollments': response.data } if api_version == API_V4: @@ -419,6 +421,12 @@ def list(self, request, *args, **kwargs): return response + def get_user(self) -> User: + """ + Get user object by username. + """ + return get_object_or_404(User, username=self.kwargs['username']) + def get_primary_enrollment_by_latest_enrollment_or_progress(self) -> Optional[CourseEnrollment]: """ Gets primary enrollment obj by latest enrollment or latest progress on the course.