@@ -1,9 +1,54 @@
|
||||
"""
|
||||
Progress Tab Serializers
|
||||
"""
|
||||
|
||||
from rest_framework import serializers
|
||||
from lms.djangoapps.course_home_api.outline.v1.serializers import CourseBlockSerializer
|
||||
from rest_framework.reverse import reverse
|
||||
|
||||
|
||||
class GradedTotalSerializer(serializers.Serializer):
|
||||
earned = serializers.FloatField()
|
||||
possible = serializers.FloatField()
|
||||
|
||||
|
||||
class SubsectionSerializer(serializers.Serializer):
|
||||
display_name = serializers.CharField()
|
||||
due = serializers.DateTimeField()
|
||||
format = serializers.CharField()
|
||||
graded = serializers.BooleanField()
|
||||
graded_total = GradedTotalSerializer()
|
||||
# TODO: override serializer
|
||||
percent_graded = serializers.FloatField()
|
||||
problem_scores = serializers.SerializerMethodField()
|
||||
show_correctness = serializers.CharField()
|
||||
show_grades = serializers.SerializerMethodField()
|
||||
url = serializers.SerializerMethodField()
|
||||
|
||||
def get_url(self, subsection):
|
||||
relative_path = reverse('jump_to', args=[self.context['course_key'], subsection.location])
|
||||
request = self.context['request']
|
||||
return request.build_absolute_uri(relative_path)
|
||||
|
||||
def get_problem_scores(self, subsection):
|
||||
problem_scores = [
|
||||
{
|
||||
'earned': score.earned,
|
||||
'possible': score.possible,
|
||||
}
|
||||
for score in subsection.problem_scores.values()
|
||||
]
|
||||
return problem_scores
|
||||
|
||||
def get_show_grades(self, subsection):
|
||||
return subsection.show_grades(self.context['staff_access'])
|
||||
|
||||
|
||||
class ChapterSerializer(serializers.Serializer):
|
||||
"""
|
||||
Serializer for chapters in coursewaresummary
|
||||
"""
|
||||
display_name = serializers.CharField()
|
||||
subsections = SubsectionSerializer(source='sections', many=True)
|
||||
|
||||
|
||||
class ProgressTabSerializer(serializers.Serializer):
|
||||
@@ -11,4 +56,6 @@ class ProgressTabSerializer(serializers.Serializer):
|
||||
Serializer for progress tab
|
||||
"""
|
||||
course_blocks = CourseBlockSerializer()
|
||||
courseware_summary = ChapterSerializer(many=True)
|
||||
enrollment_mode = serializers.CharField()
|
||||
user_timezone = serializers.CharField()
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
Tests for Progress Tab API in the Course Home API
|
||||
"""
|
||||
|
||||
import ddt
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
|
||||
from lms.djangoapps.course_home_api.toggles import COURSE_HOME_MICROFRONTEND
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from student.models import CourseEnrollment
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
@override_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True)
|
||||
@ddt.ddt
|
||||
class ProgressTabTestViews(BaseCourseHomeTests):
|
||||
"""
|
||||
Tests for the Progress Tab API
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.url = reverse('course-home-progress-tab', args=[self.course.id])
|
||||
|
||||
@ddt.data(CourseMode.AUDIT, CourseMode.VERIFIED)
|
||||
def test_get_authenticated_enrolled_user(self, enrollment_mode):
|
||||
CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode)
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Pulling out the courseware summary to check that the learner is able to see this info
|
||||
self.assertIsNotNone(response.data['courseware_summary'])
|
||||
for chapter in response.data['courseware_summary']:
|
||||
self.assertIsNotNone(chapter)
|
||||
|
||||
def test_get_authenticated_user_not_enrolled(self):
|
||||
response = self.client.get(self.url)
|
||||
# expecting a redirect
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_get_unauthenticated_user(self):
|
||||
self.client.logout()
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_get_unknown_course(self):
|
||||
url = reverse('course-home-progress-tab', args=['course-v1:unknown+course+2T2020'])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_masquerade(self):
|
||||
user = UserFactory()
|
||||
set_user_preference(user, 'time_zone', 'Asia/Tokyo')
|
||||
CourseEnrollment.enroll(user, self.course.id)
|
||||
|
||||
self.switch_to_staff() # needed for masquerade
|
||||
|
||||
# Sanity check on our normal user
|
||||
self.assertIsNone(self.client.get(self.url).data['user_timezone'])
|
||||
|
||||
# Now switch users and confirm we get a different result
|
||||
self.update_masquerade(username=user.username)
|
||||
self.assertEqual(self.client.get(self.url).data['user_timezone'], 'Asia/Tokyo')
|
||||
@@ -11,8 +11,9 @@ from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from lms.djangoapps.course_home_api.progress.v1.serializers import ProgressTabSerializer
|
||||
|
||||
from student.models import CourseEnrollment, UserTestGroup
|
||||
from student.models import CourseEnrollment
|
||||
from lms.djangoapps.course_api.blocks.transformers.blocks_api import BlocksAPITransformer
|
||||
from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs
|
||||
from lms.djangoapps.courseware.courses import get_course_with_access
|
||||
from lms.djangoapps.courseware.masquerade import setup_masquerade
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
@@ -20,6 +21,7 @@ from xmodule.modulestore.django import modulestore
|
||||
|
||||
from lms.djangoapps.course_blocks.api import get_course_blocks
|
||||
import lms.djangoapps.course_blocks.api as course_blocks_api
|
||||
from lms.djangoapps.grades.api import CourseGradeFactory
|
||||
from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers
|
||||
|
||||
|
||||
@@ -37,10 +39,6 @@ class ProgressTabView(RetrieveAPIView):
|
||||
|
||||
Body consists of the following fields:
|
||||
|
||||
user: Serialized User object. The serialization has the following fields:
|
||||
username: (str) The username of the user
|
||||
email: (str) the email of the user
|
||||
is_staff: (bool) boolean indicating whether the user has staff permisions or not
|
||||
course_blocks:
|
||||
blocks: List of serialized Course Block objects. Each serialization has the following fields:
|
||||
id: (str) The usage ID of the block.
|
||||
@@ -53,11 +51,29 @@ class ProgressTabView(RetrieveAPIView):
|
||||
xBlock on the web LMS.
|
||||
children: (list) If the block has child blocks, a list of IDs of
|
||||
the child blocks.
|
||||
courseware_summary: List of serialized Chapters. each Chapter has the following fields:
|
||||
display_name: (str) a str of what the name of the Chapter is for displaying on the site
|
||||
subsections: List of serialized Subsections, each has the following fields:
|
||||
display_name: (str) a str of what the name of the Subsection is for displaying on the site
|
||||
due: (str) a DateTime string for when the Subsection is due
|
||||
format: (str) the format, if any, of the Subsection (Homework, Exam, etc)
|
||||
graded: (bool) whether or not the Subsection is graded
|
||||
graded_total: an object containing the following fields
|
||||
earned: (float) the amount of points the user earned
|
||||
possible: (float) the amount of points the user could have earned
|
||||
percent_graded: (float) the percentage of the points the user received for the subsection
|
||||
show_correctness: (str) a str representing whether to show the problem/practice scores based on due date
|
||||
show_grades: (bool) a bool for whether to show grades based on the access the user has
|
||||
url: (str) the absolute path url to the Subsection
|
||||
enrollment_mode: (str) a str representing the enrollment the user has ('audit', 'verified', ...)
|
||||
user_timezone: (str) The user's preferred timezone
|
||||
|
||||
|
||||
|
||||
**Returns**
|
||||
|
||||
* 200 on success with above fields.
|
||||
* 302 if the user is not enrolled.
|
||||
* 403 if the user is not authenticated.
|
||||
* 404 if the course is not available or cannot be seen.
|
||||
"""
|
||||
@@ -82,21 +98,31 @@ class ProgressTabView(RetrieveAPIView):
|
||||
reset_masquerade_data=True
|
||||
)
|
||||
|
||||
user_timezone_locale = user_timezone_locale_prefs(request)
|
||||
user_timezone = user_timezone_locale['user_timezone']
|
||||
|
||||
transformers = BlockStructureTransformers()
|
||||
transformers += course_blocks_api.get_course_block_access_transformers(request.user)
|
||||
transformers += [
|
||||
BlocksAPITransformer(None, None, depth=3),
|
||||
]
|
||||
get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
|
||||
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
|
||||
course_blocks = get_course_blocks(request.user, course_usage_key, transformers, include_completion=True)
|
||||
|
||||
enrollment_mode, _ = CourseEnrollment.enrollment_mode_for_user(request.user, course_key)
|
||||
|
||||
course_grade = CourseGradeFactory().read(request.user, course)
|
||||
courseware_summary = course_grade.chapter_grades.values()
|
||||
|
||||
data = {
|
||||
'course_blocks': course_blocks,
|
||||
'courseware_summary': courseware_summary,
|
||||
'enrollment_mode': enrollment_mode,
|
||||
'user_timezone': user_timezone,
|
||||
}
|
||||
|
||||
serializer = self.get_serializer(data)
|
||||
context = self.get_serializer_context()
|
||||
context['staff_access'] = bool(has_access(request.user, 'staff', course))
|
||||
context['course_key'] = course_key
|
||||
serializer = self.get_serializer_class()(data, context=context)
|
||||
|
||||
return Response(serializer.data)
|
||||
|
||||
Reference in New Issue
Block a user