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-297] Add progress to assignments in BlocksInfoInCourseView API #2546

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
5 changes: 5 additions & 0 deletions lms/djangoapps/mobile_api/course_info/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Common constants for the `course_info` API.
"""

BLOCK_STRUCTURE_CACHE_TIMEOUT = 60 * 60 # 1 hour
43 changes: 43 additions & 0 deletions lms/djangoapps/mobile_api/course_info/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""
Common utils for the `course_info` API.
"""

import logging
from typing import List, Optional, Union

from django.core.cache import cache

from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.grades.api import CourseGradeFactory
from openedx.core.djangoapps.content.block_structure.api import get_block_structure_manager

log = logging.getLogger(__name__)


def calculate_progress(
user: 'User', # noqa: F821
course_id: 'CourseLocator', # noqa: F821
cache_timeout: int,
) -> Optional[List[Union['ReadSubsectionGrade', 'ZeroSubsectionGrade']]]: # noqa: F821
"""
Calculate the progress of the user in the course.
"""
is_staff = bool(has_access(user, 'staff', course_id))

try:
cache_key = f'course_block_structure_{str(course_id)}_{user.id}'
collected_block_structure = cache.get(cache_key)
if not collected_block_structure:
collected_block_structure = get_block_structure_manager(course_id).get_collected()
cache.set(cache_key, collected_block_structure, cache_timeout)

course_grade = CourseGradeFactory().read(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())
except Exception as err: # pylint: disable=broad-except
log.warning(f'Could not get grades for the course: {course_id}, error: {err}')
return []

return subsection_grades
43 changes: 42 additions & 1 deletion lms/djangoapps/mobile_api/course_info/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""

import logging
from typing import Optional, Union
from typing import Dict, Optional, Union

import django
from django.contrib.auth import get_user_model
Expand All @@ -20,11 +20,13 @@
from lms.djangoapps.courseware.courses import get_course_info_section_block
from lms.djangoapps.course_goals.models import UserActivity
from lms.djangoapps.course_api.blocks.views import BlocksInCourseView
from lms.djangoapps.mobile_api.course_info.constants import BLOCK_STRUCTURE_CACHE_TIMEOUT
from lms.djangoapps.mobile_api.course_info.serializers import (
CourseInfoOverviewSerializer,
CourseAccessSerializer,
MobileCourseEnrollmentSerializer
)
from lms.djangoapps.mobile_api.course_info.utils import calculate_progress
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.view_utils import view_auth_classes
from openedx.core.lib.xblock_utils import get_course_update_items
Expand Down Expand Up @@ -357,6 +359,12 @@ def list(self, request, **kwargs): # pylint: disable=W0221

course_info_context = {}
if requested_user := self.get_requested_user(request.user, requested_username):
self._extend_sequential_info_with_assignment_progress(
requested_user,
course_key,
response.data['blocks'],
)

course_info_context = {
'user': requested_user
}
Expand All @@ -380,3 +388,36 @@ def list(self, request, **kwargs): # pylint: disable=W0221

response.data.update(course_data)
return response

@staticmethod
def _extend_sequential_info_with_assignment_progress(
requested_user: User,
course_id: CourseKey,
blocks_info_data: Dict[str, Dict],
) -> None:
"""
Extends sequential xblock info with assignment's name and progress.
"""
subsection_grades = calculate_progress(requested_user, course_id, BLOCK_STRUCTURE_CACHE_TIMEOUT)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably it could be wrapped in the try except with potential log to debug easier

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I wrapped into try except calculate_progress function's logic in utils file

grades_with_locations = {str(grade.location): grade for grade in subsection_grades}

for block_id, block_info in blocks_info_data.items():
if block_info['type'] == 'sequential':
grade = grades_with_locations.get(block_id)
if grade:
graded_total = grade.graded_total if grade.graded else None
points_earned = graded_total.earned if graded_total else 0
points_possible = graded_total.possible if graded_total else 0
assignment_type = grade.format
else:
points_earned, points_possible, assignment_type = 0, 0, None

block_info.update(
{
'assignment_progress': {
'assignment_type': assignment_type,
'num_points_earned': points_earned,
'num_points_possible': points_possible,
}
}
)
28 changes: 28 additions & 0 deletions lms/djangoapps/mobile_api/tests/test_course_info_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -422,3 +422,31 @@ def test_course_modes(self):

self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertListEqual(response.data['course_modes'], expected_course_modes)

def test_extend_sequential_info_with_assignment_progress_get_only_sequential(self) -> None:
response = self.verify_response(url=self.url, params={'block_types_filter': 'sequential'})

expected_results = (
{
'assignment_type': 'Lecture Sequence',
'num_points_earned': 0.0,
'num_points_possible': 0.0
},
{
'assignment_type': None,
'num_points_earned': 0.0,
'num_points_possible': 0.0
},
)

self.assertEqual(response.status_code, status.HTTP_200_OK)
for sequential_info, assignment_progress in zip(response.data['blocks'].values(), expected_results):
self.assertDictEqual(sequential_info['assignment_progress'], assignment_progress)

@ddt.data('chapter', 'vertical', 'problem', 'video', 'html')
def test_extend_sequential_info_with_assignment_progress_for_other_types(self, block_type: 'str') -> None:
response = self.verify_response(url=self.url, params={'block_types_filter': block_type})

self.assertEqual(response.status_code, status.HTTP_200_OK)
for block_info in response.data['blocks'].values():
self.assertNotEqual('assignment_progress', block_info)
Loading