Files
edx-platform/lms/djangoapps/grades/api/v1/views.py
Kabir Khan c0f353467e V1 Grades API with added support for bulk read of all user grades in a course.
URL patterns changed to follow edx API conventions
2018-05-03 10:23:33 -07:00

203 lines
8.2 KiB
Python

""" API v0 views. """
import logging
from django.contrib.auth import get_user_model
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework import status
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from enrollment import data as enrollment_data
from student.models import CourseEnrollment
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.permissions import IsUserInUrlOrStaff
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
log = logging.getLogger(__name__)
USER_MODEL = get_user_model()
@view_auth_classes()
class GradeViewMixin(DeveloperErrorViewMixin):
"""
Mixin class for Grades related views.
"""
def _get_single_user_grade(self, request, course_key):
"""
Returns a grade response for the user object corresponding to the request's 'username' parameter,
or the current request.user if no 'username' was provided.
Args:
request (Request): django request object to check for username or request.user object
course_key (CourseLocator): The course to retrieve user grades for.
Returns:
A serializable list of grade responses
"""
if 'username' in request.GET:
username = request.GET.get('username')
else:
username = request.user.username
grade_user = USER_MODEL.objects.get(username=username)
if not enrollment_data.get_course_enrollment(username, str(course_key)):
raise CourseEnrollment.DoesNotExist
course_grade = CourseGradeFactory().read(grade_user, course_key=course_key)
return Response([self._make_grade_response(grade_user, course_key, course_grade)])
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
"""
enrollments_in_course = enrollment_data.get_user_enrollments(course_key)
paged_enrollments = self.paginator.paginate_queryset(
enrollments_in_course, self.request, view=self
)
users = (enrollment.user for enrollment in paged_enrollments)
grades = CourseGradeFactory().iter(users, course_key=course_key)
grade_responses = []
for user, course_grade, exc in grades:
if not exc:
grade_responses.append(self._make_grade_response(user, course_key, course_grade))
return Response(grade_responses)
def _make_grade_response(self, user, course_key, course_grade):
"""
Serialize a single grade to dict to use in Responses
"""
return {
'username': user.username,
'email': user.email,
'course_id': str(course_key),
'passed': course_grade.passed,
'percent': course_grade.percent,
'letter_grade': course_grade.letter_grade,
}
def perform_authentication(self, request):
"""
Ensures that the user is authenticated (e.g. not an AnonymousUser).
"""
super(GradeViewMixin, self).perform_authentication(request)
if request.user.is_anonymous():
raise AuthenticationFailed
class CourseGradesView(GradeViewMixin, GenericAPIView):
"""
**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,
}]
"""
permission_classes = (IsUserInUrlOrStaff,)
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')
if not course_id:
course_id = request.GET.get('course_id')
# Validate course exists with provided course_id
try:
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
return self.make_error_response(
status_code=status.HTTP_404_NOT_FOUND,
developer_message='The provided course key cannot be parsed.',
error_code='invalid_course_key'
)
if not CourseOverview.get_from_id_if_exists(course_key):
return self.make_error_response(
status_code=status.HTTP_404_NOT_FOUND,
developer_message="Requested grade for unknown course {course}".format(course=course_id),
error_code='course_does_not_exist'
)
if username:
# If there is a username passed, get grade for a single user
try:
return self._get_single_user_grade(request, course_key)
except USER_MODEL.DoesNotExist:
return self.make_error_response(
status_code=status.HTTP_404_NOT_FOUND,
developer_message='The user matching the requested username does not exist.',
error_code='user_does_not_exist'
)
except CourseEnrollment.DoesNotExist:
return self.make_error_response(
status_code=status.HTTP_404_NOT_FOUND,
developer_message='The user matching the requested username is not enrolled in this course',
error_code='user_not_enrolled'
)
else:
# If no username passed, get paginated list of grades for all users in course
return self._get_user_grades(course_key)