From 60707a3c584a6c1d15b8d850114aa070edae1173 Mon Sep 17 00:00:00 2001 From: Josue Balandrano Coronel Date: Wed, 3 Jul 2019 17:26:36 -0500 Subject: [PATCH] feat: submision history endpoint from koa nelp (BB-1389) Update enrollment serializer and Add problem submission history endpoint Squashed all commits from https://github.com/edx/edx-platform/pull/20948 (cherry picked from commit c102e41c6d92e5fb7db66de4d3624f03da213631) This cherry-pick was modified and updated to work with mango. --- lms/djangoapps/courseware/access_utils.py | 2 +- lms/envs/test.py | 15 + .../djangoapps/enrollments/serializers.py | 43 +- ...ourse-enrollments-api-list-valid-data.json | 512 +++++++++++++++++- .../enrollments/tests/test_views.py | 2 +- openedx/core/djangoapps/enrollments/urls.py | 4 +- openedx/core/djangoapps/enrollments/views.py | 172 +++++- 7 files changed, 725 insertions(+), 25 deletions(-) diff --git a/lms/djangoapps/courseware/access_utils.py b/lms/djangoapps/courseware/access_utils.py index 860f2810452..287e7108ec8 100644 --- a/lms/djangoapps/courseware/access_utils.py +++ b/lms/djangoapps/courseware/access_utils.py @@ -75,7 +75,7 @@ def check_start_date(user, days_early_for_beta, start, course_key, display_error Returns: AccessResponse: Either ACCESS_GRANTED or StartDateError. """ - start_dates_disabled = settings.FEATURES['DISABLE_START_DATES'] + start_dates_disabled = settings.FEATURES.get('DISABLE_START_DATES', False) masquerading_as_student = is_masquerading_as_student(user, course_key) if start_dates_disabled and not masquerading_as_student: diff --git a/lms/envs/test.py b/lms/envs/test.py index 69b290fc53e..dddb1ca6796 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -586,6 +586,21 @@ RATELIMIT_RATE = '2/m' + +COURSE_ENROLLMENT_MODES['test'] = { + "id": 8, + "slug": u"test", + "display_name": u"Test", + "min_price": 0 +} + +COURSE_ENROLLMENT_MODES['test_mode'] = { + "id": 9, + "slug": u"test_mode", + "display_name": u"Test Mode", + "min_price": 0 +} + ##### LOGISTRATION RATE LIMIT SETTINGS ##### LOGISTRATION_RATELIMIT_RATE = '5/5m' LOGISTRATION_PER_EMAIL_RATELIMIT_RATE = '6/5m' diff --git a/openedx/core/djangoapps/enrollments/serializers.py b/openedx/core/djangoapps/enrollments/serializers.py index 9fde7c04033..a5f31abc179 100644 --- a/openedx/core/djangoapps/enrollments/serializers.py +++ b/openedx/core/djangoapps/enrollments/serializers.py @@ -9,6 +9,9 @@ from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.student.models import CourseEnrollment +from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory +from xmodule.modulestore.django import modulestore +from django.core.exceptions import PermissionDenied log = logging.getLogger(__name__) @@ -83,15 +86,49 @@ class CourseEnrollmentSerializer(serializers.ModelSerializer): """ course_details = CourseSerializer(source="course_overview") - user = serializers.SerializerMethodField('get_username') + user = serializers.SerializerMethodField("get_username") + finished = serializers.SerializerMethodField() + grading = serializers.SerializerMethodField() def get_username(self, model): """Retrieves the username from the associated model.""" return model.username - class Meta: + def get_finished(self, model): + """Retrieve finished course.""" + course = modulestore().get_course(model.course_id) + if course: + try: + coursegrade = CourseGradeFactory().read(model.user, course).passed + except PermissionDenied: + return False + return coursegrade + return False + + def get_grading(self, model): + """Retrieve course grade.""" + course = modulestore().get_course(model.course_id) + course_grade = None + summary = [] + current_grade = 0 + if course: + try: + course_grade = CourseGradeFactory().read(model.user, course) + current_grade = int(course_grade.percent * 100) + for section in course_grade.summary.get(u'section_breakdown'): + if section.get(u'prominent'): + summary.append(section) + except PermissionDenied: + pass + return [ + {u'current_grade': current_grade, + u'certificate_eligible': course_grade.passed if course_grade else False, + u'summary': summary} + ] + + class Meta(object): model = CourseEnrollment - fields = ('created', 'mode', 'is_active', 'course_details', 'user') + fields = ('created', 'mode', 'is_active', 'course_details', 'user', 'finished', 'grading') lookup_field = 'username' diff --git a/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json b/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json index e9fd2f55eca..60324c0f155 100644 --- a/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json +++ b/openedx/core/djangoapps/enrollments/tests/fixtures/course-enrollments-api-list-valid-data.json @@ -9,14 +9,74 @@ "is_active": true, "mode": "honor", "user": "student1", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "e/d/X", "is_active": true, "mode": "honor", "user": "student2", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] } ] ], @@ -30,21 +90,111 @@ "is_active": true, "mode": "verified", "user": "staff", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "x/y/Z", "is_active": true, "mode": "honor", "user": "student2", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "x/y/Z", "is_active": true, "mode": "verified", "user": "student3", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] } ] ], @@ -59,14 +209,74 @@ "is_active": true, "mode": "honor", "user": "student2", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "x/y/Z", "is_active": true, "mode": "verified", "user": "student3", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] } ] ], @@ -81,7 +291,37 @@ "is_active": true, "mode": "honor", "user": "student2", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] } ] ], @@ -95,21 +335,111 @@ "is_active": true, "mode": "verified", "user": "staff", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "e/d/X", "is_active": true, "mode": "honor", "user": "student2", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "x/y/Z", "is_active": true, "mode": "honor", "user": "student2", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] } ] @@ -122,35 +452,185 @@ "is_active": true, "mode": "honor", "user": "student1", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "e/d/X", "is_active": true, "mode": "honor", "user": "student2", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "x/y/Z", "is_active": true, "mode": "verified", "user": "student3", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "x/y/Z", "is_active": true, "mode": "honor", "user": "student2", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] }, { "course_id": "x/y/Z", "is_active": true, "mode": "verified", "user": "staff", - "created": "2018-01-01T00:00:01Z" + "created": "2018-01-01T00:00:01Z", + "finished": false, + "grading": [{ + "certificate_eligible": false, + "current_grade": 0, + "summary": [{ + "category": "Homework", + "prominent": true, + "percent": 0.0, + "detail": "Homework Average = 0%", + "label": "HW Avg" + },{ + "category": "Lab", + "prominent": true, + "percent": 0.0, + "detail": "Lab Average = 0%", + "label": "Lab Avg" + },{ + "category": "Midterm Exam", + "prominent": true, + "percent": 0.0, + "detail": "Midterm Exam = 0%", + "label": "Midterm" + },{ + "category": "Final Exam", + "prominent": true, + "percent": 0.0, + "detail": "Final Exam = 0%", + "label": "Final" + }] + }] } ] ] diff --git a/openedx/core/djangoapps/enrollments/tests/test_views.py b/openedx/core/djangoapps/enrollments/tests/test_views.py index a55f2a77605..c3f0c960433 100644 --- a/openedx/core/djangoapps/enrollments/tests/test_views.py +++ b/openedx/core/djangoapps/enrollments/tests/test_views.py @@ -62,7 +62,7 @@ def assert_enrollment_status( is_active=None, enrollment_attributes=None, min_mongo_calls=0, - max_mongo_calls=0, + max_mongo_calls=8, linked_enterprise_customer=None, cohort=None, ): diff --git a/openedx/core/djangoapps/enrollments/urls.py b/openedx/core/djangoapps/enrollments/urls.py index f50221bae00..c034d144634 100644 --- a/openedx/core/djangoapps/enrollments/urls.py +++ b/openedx/core/djangoapps/enrollments/urls.py @@ -13,7 +13,8 @@ EnrollmentListView, EnrollmentUserRolesView, EnrollmentView, - UnenrollmentView + UnenrollmentView, + SubmissionHistoryView, ) urlpatterns = [ @@ -29,4 +30,5 @@ EnrollmentCourseDetailView.as_view(), name='courseenrollmentdetails'), url(r'^unenroll/$', UnenrollmentView.as_view(), name='unenrollment'), url(r'^roles/$', EnrollmentUserRolesView.as_view(), name='roles'), + url(r'^submission_history$', SubmissionHistoryView.as_view(), name='submissionhistory'), ] diff --git a/openedx/core/djangoapps/enrollments/views.py b/openedx/core/djangoapps/enrollments/views.py index e1616e23e2a..9838a8ab470 100644 --- a/openedx/core/djangoapps/enrollments/views.py +++ b/openedx/core/djangoapps/enrollments/views.py @@ -7,11 +7,15 @@ import logging +import json + from common.djangoapps.course_modes.models import CourseMode from django.core.exceptions import ObjectDoesNotExist, ValidationError # lint-amnesty, pylint: disable=wrong-import-order -from django.utils.decorators import method_decorator # lint-amnesty, pylint: disable=wrong-import-order +from django.utils.decorators import method_decorator from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication # lint-amnesty, pylint: disable=wrong-import-order -from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser # lint-amnesty, pylint: disable=wrong-import-order +from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser # lint-amnesty, pylint: disable=wrong-import-order +from lms.djangoapps.courseware.courses import get_course # lint-amnesty, pylint: disable=wrong-import-order +from lms.djangoapps.courseware.models import StudentModule, BaseStudentModuleHistory # lint-amnesty, pylint: disable=wrong-import-order from opaque_keys import InvalidKeyError # lint-amnesty, pylint: disable=wrong-import-order from opaque_keys.edx.keys import CourseKey # lint-amnesty, pylint: disable=wrong-import-order from openedx.core.djangoapps.cors_csrf.authentication import SessionAuthenticationCrossDomainCsrf @@ -28,7 +32,7 @@ from openedx.core.djangoapps.user_api.accounts.permissions import CanRetireUser from openedx.core.djangoapps.user_api.models import UserRetirementStatus from openedx.core.djangoapps.user_api.preferences.api import update_email_opt_in -from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser +from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser, OAuth2AuthenticationAllowInactiveUser from openedx.core.lib.api.permissions import ApiKeyHeaderPermission, ApiKeyHeaderPermissionIsAuthenticated from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin from openedx.core.lib.exceptions import CourseNotFoundError @@ -48,6 +52,7 @@ from common.djangoapps.student.models import CourseEnrollment, User from common.djangoapps.student.roles import CourseStaffRole, GlobalStaff from common.djangoapps.util.disable_rate_limit import can_disable_rate_limit +from opaque_keys.edx.locator import CourseLocator log = logging.getLogger(__name__) REQUIRED_ATTRIBUTES = { @@ -965,3 +970,164 @@ def get_queryset(self): if usernames: queryset = queryset.filter(user__username__in=usernames) return queryset + + +@can_disable_rate_limit +class SubmissionHistoryView(APIView, ApiKeyPermissionMixIn): + """ + Submission history view. + """ + authentication_classes = (OAuth2AuthenticationAllowInactiveUser, EnrollmentCrossDomainSessionAuth) + permission_classes = (ApiKeyHeaderPermissionIsAuthenticated, ) + + def get(self, request): + """ + Get submission history details. + + **Usecases**: + + Regular users can only retrieve their own submission history and users with GlobalStaff status + can retrieve everyone's submission history. + + **Example Requests**: + + GET /api/enrollment/v1/submission_history?course_id=course_id + GET /api/enrollment/v1/submission_history?course_id=course_id&user=username + GET /api/enrollment/v1/submission_history?course_id=course_id&all_users=true + + **Query Parameters for GET** + + * course_id: Course id to retrieve submission history. + * username: Single username for which this view will retrieve the submission history details. + If no username specified the requester's username will be used. + * all_users: If true and if the requester has the correct permissions, + retrieve history submission from every user in a course id. + + **Response Values**: + + If there's an error while getting the submission history an empty response will + be returned. + The submission history response has the following attributes: + + * Results: A list of submission history: + * course_id: Course id + * course_name: Course name + * user: Username + * problems: List of problems + * location: problem location + * name: problem's display name + * submission_history: List of submission history + * state: State of submission. + * grade: Grade. + * max_grade: Maximum possible grade. + * data: problem's data. + """ + username = request.GET.get('username', request.user.username) + data = [] + if GlobalStaff().has_user(request.user): + all_users = bool(request.GET.get('all', False)) + else: + all_users = False + course_id = request.GET.get('course_id') + + if not (all_users or username == request.user.username or GlobalStaff().has_user(request.user) or + self.has_api_key_permissions(request)): + return Response(data) + + course_enrollments = CourseEnrollment.objects.select_related('user').filter(is_active=True) + if course_id: + if not course_id.startswith("course-v1:"): + course_id = "course-v1:{}".format(course_id) + try: + course_enrollments = course_enrollments.filter( + course_id=CourseLocator.from_string(course_id.replace(' ', '+')) + ).order_by('created') + except KeyError: + return Response(data) + + if not all_users: + course_enrollments = course_enrollments.filter(user__username=username).order_by('created') + + courses = {} + for course_enrollment in course_enrollments: + try: + course_list = courses.get(course_enrollment.course_id) + if course_list: + course, course_children = course_list + else: + course = get_course(course_enrollment.course_id, depth=4) + course_children = course.get_children() + courses[course_enrollment.course_id] = [course, course_children] + except ValueError: + continue + course_data = self._get_course_data(course_enrollment, course, course_children) + data.append(course_data) + + return Response({'results': data}) + + def _get_problem_data(self, course_enrollment, component): + """ + Get problem data from a course enrollment. + + Args: + ----- + course_enrollment: Course Enrollment. + component: Component to analyze. + """ + problem_data = { + 'location': str(component.location), + 'name': component.display_name, + 'submission_history': [], + 'data': component.data + } + + csm = StudentModule.objects.filter( + module_state_key=component.location, + student__username=course_enrollment.user.username, + course_id=course_enrollment.course_id) + + scores = BaseStudentModuleHistory.get_history(csm) + for i, score in enumerate(scores): + if i % 2 == 1: + continue + + state = score.state + if state is not None: + state = json.loads(state) + + history_data = { + 'state': state, + 'grade': score.grade, + 'max_grade': score.max_grade + } + problem_data['submission_history'].append(history_data) + + return problem_data + + def _get_course_data(self, course_enrollment, course, course_children): + """ + Get course data. + + Params: + -------- + + course_enrollment (CourseEnrollment): course enrollment + course: course + course_children: course children + """ + + course_data = { + 'course_id': str(course_enrollment.course_id), + 'course_name': course.display_name_with_default, + 'user': course_enrollment.user.username, + 'problems': [] + } + for section in course_children: + for subsection in section.get_children(): + for vertical in subsection.get_children(): + for component in vertical.get_children(): + if component.location.category == 'problem' and getattr(component, 'has_score', False): + problem_data = self._get_problem_data(course_enrollment, component) + course_data['problems'].append(problem_data) + + return course_data