210 lines
8.0 KiB
Python
210 lines
8.0 KiB
Python
""" API v0 views. """
|
|
|
|
|
|
import logging
|
|
from contextlib import contextmanager
|
|
|
|
from edx_rest_framework_extensions import permissions
|
|
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
|
|
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
|
from opaque_keys import InvalidKeyError
|
|
from rest_framework import status
|
|
from rest_framework.generics import ListAPIView
|
|
from rest_framework.response import Response
|
|
|
|
from lms.djangoapps.courseware.access import has_access
|
|
from lms.djangoapps.grades.api import CourseGradeFactory, clear_prefetched_course_grades, prefetch_course_grades
|
|
from lms.djangoapps.grades.rest_api.serializers import GradingPolicySerializer
|
|
from lms.djangoapps.grades.rest_api.v1.utils import CourseEnrollmentPagination, GradeViewMixin
|
|
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
|
|
from openedx.core.lib.api.view_utils import PaginatedAPIView, get_course_key, verify_course_exists
|
|
from xmodule.modulestore.django import modulestore
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
@contextmanager
|
|
def bulk_course_grade_context(course_key, users):
|
|
"""
|
|
Prefetches grades for the given users in the given course
|
|
within a context, storing in a RequestCache and deleting
|
|
on context exit.
|
|
"""
|
|
prefetch_course_grades(course_key, users)
|
|
try:
|
|
yield
|
|
finally:
|
|
clear_prefetched_course_grades(course_key)
|
|
|
|
|
|
class CourseGradesView(GradeViewMixin, PaginatedAPIView):
|
|
"""
|
|
**Use Case**
|
|
* Get course grades of all users who are enrolled in a course.
|
|
The currently logged-in user may request all enrolled user's grades information
|
|
if they are allowed.
|
|
**Example Request**
|
|
GET /api/grades/v1/courses/{course_id}/ - Get grades for all users in course
|
|
GET /api/grades/v1/courses/{course_id}/?username={username} - Get grades for specific user in course
|
|
GET /api/grades/v1/courses/?course_id={course_id} - Get grades for all users in course
|
|
GET /api/grades/v1/courses/?course_id={course_id}&username={username}- Get grades for specific user in course
|
|
**GET Parameters**
|
|
A GET request may include the following parameters.
|
|
* course_id: (required) A string representation of a Course ID.
|
|
* username: (optional) A string representation of a user's username.
|
|
**GET Response Values**
|
|
If the request for information about the course grade
|
|
is successful, an HTTP 200 "OK" response is returned.
|
|
The HTTP 200 response has the following values.
|
|
* username: A string representation of a user's username passed in the request.
|
|
* email: A string representation of a user's email.
|
|
* course_id: A string representation of a Course ID.
|
|
* passed: Boolean representing whether the course has been
|
|
passed according to the course's grading policy.
|
|
* percent: A float representing the overall grade for the course
|
|
* letter_grade: A letter grade as defined in grading policy (e.g. 'A' 'B' 'C' for 6.002x) or None
|
|
**Example GET Response**
|
|
[{
|
|
"username": "bob",
|
|
"email": "bob@example.com",
|
|
"course_id": "course-v1:edX+DemoX+Demo_Course",
|
|
"passed": false,
|
|
"percent": 0.03,
|
|
"letter_grade": null,
|
|
},
|
|
{
|
|
"username": "fred",
|
|
"email": "fred@example.com",
|
|
"course_id": "course-v1:edX+DemoX+Demo_Course",
|
|
"passed": true,
|
|
"percent": 0.83,
|
|
"letter_grade": "B",
|
|
},
|
|
{
|
|
"username": "kate",
|
|
"email": "kate@example.com",
|
|
"course_id": "course-v1:edX+DemoX+Demo_Course",
|
|
"passed": false,
|
|
"percent": 0.19,
|
|
"letter_grade": null,
|
|
}]
|
|
"""
|
|
authentication_classes = (
|
|
JwtAuthentication,
|
|
BearerAuthenticationAllowInactiveUser,
|
|
SessionAuthenticationAllowInactiveUser,
|
|
)
|
|
|
|
permission_classes = (permissions.JWT_RESTRICTED_APPLICATION_OR_USER_ACCESS,)
|
|
|
|
pagination_class = CourseEnrollmentPagination
|
|
|
|
required_scopes = ['grades:read']
|
|
|
|
@verify_course_exists
|
|
def get(self, request, course_id=None):
|
|
"""
|
|
Gets a course progress status.
|
|
Args:
|
|
request (Request): Django request object.
|
|
course_id (string): URI element specifying the course location.
|
|
Can also be passed as a GET parameter instead.
|
|
Return:
|
|
A JSON serialized representation of the requesting user's current grade status.
|
|
"""
|
|
username = request.GET.get('username')
|
|
|
|
course_key = get_course_key(request, course_id)
|
|
|
|
if username:
|
|
# If there is a username passed, get grade for a single user
|
|
with self._get_user_or_raise(request, course_key) as grade_user:
|
|
return self._get_single_user_grade(grade_user, course_key)
|
|
else:
|
|
# If no username passed, get paginated list of grades for all users in course
|
|
return self._get_user_grades(course_key)
|
|
|
|
def _get_user_grades(self, course_key):
|
|
"""
|
|
Get paginated grades for users in a course.
|
|
Args:
|
|
course_key (CourseLocator): The course to retrieve user grades for.
|
|
|
|
Returns:
|
|
A serializable list of grade responses
|
|
"""
|
|
user_grades = []
|
|
users = self._paginate_users(course_key)
|
|
|
|
with bulk_course_grade_context(course_key, users):
|
|
for user, course_grade, exc in CourseGradeFactory().iter(users, course_key=course_key):
|
|
if not exc:
|
|
user_grades.append(self._serialize_user_grade(user, course_key, course_grade))
|
|
|
|
return self.get_paginated_response(user_grades)
|
|
|
|
|
|
class CourseGradingPolicy(GradeViewMixin, ListAPIView):
|
|
"""
|
|
**Use Case**
|
|
|
|
Get the course grading policy.
|
|
|
|
**Example requests**:
|
|
|
|
GET /api/grades/v1/policy/courses/{course_id}/
|
|
|
|
**Response Values**
|
|
|
|
* assignment_type: The type of the assignment, as configured by course
|
|
staff. For example, course staff might make the assignment types Homework,
|
|
Quiz, and Exam.
|
|
|
|
* count: The number of assignments of the type.
|
|
|
|
* dropped: Number of assignments of the type that are dropped.
|
|
|
|
* weight: The weight, or effect, of the assignment type on the learner's
|
|
final grade.
|
|
"""
|
|
allow_empty = False
|
|
|
|
authentication_classes = (
|
|
JwtAuthentication,
|
|
BearerAuthenticationAllowInactiveUser,
|
|
SessionAuthenticationAllowInactiveUser,
|
|
)
|
|
|
|
def _get_course(self, request, course_id):
|
|
"""
|
|
Returns the course after parsing the id, checking access, and checking existence.
|
|
"""
|
|
try:
|
|
course_key = get_course_key(request, course_id)
|
|
except InvalidKeyError:
|
|
raise self.api_error( # lint-amnesty, pylint: disable=raise-missing-from
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
developer_message='The provided course key cannot be parsed.',
|
|
error_code='invalid_course_key'
|
|
)
|
|
|
|
if not has_access(request.user, 'staff', course_key):
|
|
raise self.api_error(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
developer_message='The course does not exist.',
|
|
error_code='user_or_course_does_not_exist',
|
|
)
|
|
|
|
course = modulestore().get_course(course_key, depth=0)
|
|
if not course:
|
|
raise self.api_error(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
developer_message='The course does not exist.',
|
|
error_code='user_or_course_does_not_exist',
|
|
)
|
|
return course
|
|
|
|
def get(self, request, course_id, *args, **kwargs): # pylint: disable=arguments-differ
|
|
course = self._get_course(request, course_id)
|
|
return Response(GradingPolicySerializer(course.raw_grader, many=True).data)
|