feat: add user id parameter to progress page

This commit is contained in:
Matthew Piatetsky
2021-05-28 15:50:29 -04:00
parent 138941ec03
commit 6922f75a52
5 changed files with 80 additions and 15 deletions

View File

@@ -95,6 +95,7 @@ class ProgressTabSerializer(VerifiedModeSerializerMixin):
"""
Serializer for progress tab
"""
username = serializers.CharField()
certificate_data = CertificateDataSerializer()
completion_summary = serializers.DictField()
course_grade = CourseGradeSerializer()

View File

@@ -183,3 +183,28 @@ class ProgressTabTestViews(BaseCourseHomeTests):
assert ungraded_score['learner_has_access']
assert not gated_score['learner_has_access']
assert ungated_score['learner_has_access']
@override_waffle_flag(COURSE_HOME_MICROFRONTEND_PROGRESS_TAB, active=True)
def test_view_other_students_progress_page(self):
# Test the ability to view progress pages of other students by changing the url
CourseEnrollment.enroll(self.user, self.course.id)
response = self.client.get(self.url)
assert response.data['username'] == self.user.username
other_user = UserFactory()
self.url = reverse('course-home-progress-tab-other-student', args=[self.course.id, other_user.id])
CourseEnrollment.enroll(other_user, self.course.id)
# users with the ccx coach role can view other students' progress pages
with patch(
'lms.djangoapps.course_home_api.progress.v1.views.has_ccx_coach_role',
return_value=True
):
response = self.client.get(self.url)
assert response.data['username'] == other_user.username
# staff users can view other students' progress pages
self.switch_to_staff()
response = self.client.get(self.url)
assert response.data['username'] == other_user.username

View File

@@ -3,6 +3,7 @@ Progress Tab Views
"""
from django.http.response import Http404
from django.contrib.auth.models import User
from edx_django_utils import monitoring as monitoring_utils
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
@@ -15,10 +16,11 @@ from xmodule.modulestore.django import modulestore
from common.djangoapps.student.models import CourseEnrollment
from lms.djangoapps.course_home_api.progress.v1.serializers import ProgressTabSerializer
from lms.djangoapps.course_home_api.toggles import course_home_mfe_progress_tab_is_active
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.access import has_access, has_ccx_coach_role
from lms.djangoapps.course_blocks.api import get_course_blocks
from lms.djangoapps.course_blocks.transformers import start_date
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
from lms.djangoapps.courseware.courses import get_course_blocks_completion_summary, get_course_with_access, get_studio_url
from lms.djangoapps.courseware.masquerade import setup_masquerade
from lms.djangoapps.courseware.views.views import get_cert_data
@@ -30,6 +32,7 @@ from openedx.core.djangoapps.content.block_structure.api import get_block_struct
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.features.content_type_gating.block_transformers import ContentTypeGateTransformer
from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name
class ProgressTabView(RetrieveAPIView):
@@ -41,6 +44,7 @@ class ProgressTabView(RetrieveAPIView):
**Example Requests**
GET api/course_home/v1/progress/{course_key}
GET api/course_home/v1/progress/{course_key}/{student_id}/
**Response Values**
@@ -48,6 +52,7 @@ class ProgressTabView(RetrieveAPIView):
end: (date) end date of the course
user_has_passing_grade: (bool) boolean on if the user has a passing grade in the course
username: (str) username of the student whose progress information is being displayed.
has_scheduled_content: (bool) boolean on if the course has content scheduled with a release date in the future
certificate_data: Object containing information about the user's certificate status
cert_status: (str) the status of a user's certificate (full list of statuses are defined in
@@ -113,6 +118,12 @@ class ProgressTabView(RetrieveAPIView):
def get(self, request, *args, **kwargs):
course_key_string = kwargs.get('course_key_string')
course_key = CourseKey.from_string(course_key_string)
student_id = kwargs.get('student_id')
if student_id:
try:
student_id = int(student_id)
except ValueError:
raise Http404
if not course_home_mfe_progress_tab_is_active(course_key):
raise Http404
@@ -123,17 +134,36 @@ class ProgressTabView(RetrieveAPIView):
monitoring_utils.set_custom_attribute('is_staff', request.user.is_staff)
is_staff = bool(has_access(request.user, 'staff', course_key))
_, request.user = setup_masquerade(
request,
course_key,
staff_access=is_staff,
reset_masquerade_data=True
)
if student_id is None or student_id == request.user.id:
_, student = setup_masquerade(
request,
course_key,
staff_access=is_staff,
reset_masquerade_data=True
)
else:
# When a student_id is passed in, we display the progress page for the user
# with the provided user id, rather than the requesting user
try:
coach_access = has_ccx_coach_role(request.user, course_key)
except CCXLocatorValidationException:
coach_access = False
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False)
has_access_on_students_profiles = is_staff or coach_access
# Requesting access to a different student's profile
if not has_access_on_students_profiles:
raise Http404
try:
student = User.objects.get(id=student_id)
except User.DoesNotExist as exc:
raise Http404 from exc
username = get_enterprise_learner_generic_name(request) or student.username
course = get_course_with_access(student, 'load', course_key, check_if_enrolled=False)
course_overview = CourseOverview.get_from_id(course_key)
enrollment = CourseEnrollment.get_enrollment(request.user, course_key)
enrollment = CourseEnrollment.get_enrollment(student, course_key)
enrollment_mode = getattr(enrollment, 'mode', None)
if not (enrollment and enrollment.is_active) and not is_staff:
@@ -142,14 +172,14 @@ class ProgressTabView(RetrieveAPIView):
# The block structure is used for both the course_grade and has_scheduled content fields
# So it is called upfront and reused for optimization purposes
collected_block_structure = get_block_structure_manager(course_key).get_collected()
course_grade = CourseGradeFactory().read(request.user, collected_block_structure=collected_block_structure)
course_grade = CourseGradeFactory().read(student, collected_block_structure=collected_block_structure)
# Get has_scheduled_content data
transformers = BlockStructureTransformers()
transformers += [start_date.StartDateTransformer(), ContentTypeGateTransformer()]
usage_key = collected_block_structure.root_block_usage_key
course_blocks = get_course_blocks(
request.user,
student,
usage_key,
transformers=transformers,
collected_block_structure=collected_block_structure,
@@ -159,13 +189,13 @@ class ProgressTabView(RetrieveAPIView):
# Get user_has_passing_grade data
user_has_passing_grade = False
if not request.user.is_anonymous:
if not student.is_anonymous:
user_grade = course_grade.percent
user_has_passing_grade = user_grade >= course.lowest_passing_grade
descriptor = modulestore().get_course(course_key)
grading_policy = descriptor.grading_policy
verification_status = IDVerificationService.user_status(request.user)
verification_status = IDVerificationService.user_status(student)
verification_link = None
if verification_status['status'] is None or verification_status['status'] == 'expired':
verification_link = IDVerificationService.get_verify_location(course_id=course_key)
@@ -178,10 +208,11 @@ class ProgressTabView(RetrieveAPIView):
}
data = {
'username': username,
'end': course.end,
'user_has_passing_grade': user_has_passing_grade,
'certificate_data': get_cert_data(request.user, course, enrollment_mode, course_grade),
'completion_summary': get_course_blocks_completion_summary(course_key, request.user),
'certificate_data': get_cert_data(student, course, enrollment_mode, course_grade),
'completion_summary': get_course_blocks_completion_summary(course_key, student),
'course_grade': course_grade,
'has_scheduled_content': has_scheduled_content,
'section_scores': course_grade.chapter_grades.values(),

View File

@@ -57,6 +57,13 @@ urlpatterns += [
]
# Progress Tab URLs
urlpatterns += [
re_path(
fr'v1/progress/{settings.COURSE_KEY_PATTERN}/(?P<student_id>[^/]+)',
ProgressTabView.as_view(),
name='course-home-progress-tab-other-student'
),
]
urlpatterns += [
re_path(
fr'v1/progress/{settings.COURSE_KEY_PATTERN}',

View File

@@ -11,6 +11,7 @@ from lms.djangoapps.experiments.utils import STREAK_DISCOUNT_EXPERIMENT_FLAG
from openedx.features.course_duration_limits.access import get_user_course_expiration_date
from openedx.features.discounts.applicability import can_show_streak_discount_experiment_coupon
def get_celebrations_dict(user, enrollment, course, browser_timezone):
"""
Returns a dict of celebrations that should be performed.