Files
edx-platform/lms/djangoapps/grades/rest_api/v1/utils.py
2021-02-22 12:58:41 +05:00

187 lines
7.3 KiB
Python

"""
Define some view level utility functions here that multiple view modules will share
"""
from contextlib import contextmanager
from django.contrib.auth import get_user_model
from django.db.models import Q
from rest_framework import status
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.pagination import CursorPagination
from rest_framework.response import Response
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.query import use_read_replica_if_available
from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin
USER_MODEL = get_user_model()
class CourseEnrollmentPagination(CursorPagination):
"""
Paginates over CourseEnrollment objects.
"""
ordering = 'id'
page_size = 50
page_size_query_param = 'page_size'
def get_page_size(self, request):
"""
Get the page size based on the defined page size parameter if defined.
"""
try:
page_size_string = request.query_params[self.page_size_query_param]
return int(page_size_string)
except (KeyError, ValueError):
pass
return self.page_size
def get_paginated_response(self, data, status_code=200, **kwargs): # pylint: disable=arguments-differ
"""
Return a response given serialized page data, optional status_code (defaults to 200),
and kwargs. Each key-value pair of kwargs is added to the response data.
"""
resp = super().get_paginated_response(data)
for (key, value) in kwargs.items():
resp.data[key] = value
resp.status_code = status_code
return resp
class GradeViewMixin(DeveloperErrorViewMixin):
"""
Mixin class for Grades related views.
"""
def _get_single_user(self, request, course_key, user_id=None):
"""
Returns a single USER_MODEL object corresponding to either the user_id provided, or if no id is provided,
then 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.
user_id (int): Optional user id to fetch the user object for.
Returns:
A USER_MODEL object.
Raises:
USER_MODEL.DoesNotExist if no such user exists.
CourseEnrollment.DoesNotExist if the user is not enrolled in the given course.
"""
# May raise USER_MODEL.DoesNotExist if no user matching the given query exists.
if user_id:
grade_user = USER_MODEL.objects.get(id=user_id)
elif 'username' in request.GET:
grade_user = USER_MODEL.objects.get(username=request.GET.get('username'))
else:
grade_user = request.user
# May raise CourseEnrollment.DoesNotExist if no enrollment exists for this user/course.
_ = CourseEnrollment.objects.get(user=grade_user, course_id=course_key)
return grade_user
@contextmanager
def _get_user_or_raise(self, request, course_key):
"""
Raises an API error if the username specified by the request does not exist, or if the
user is not enrolled in the given course.
Args:
request (Request): django request object to check for username or request.user object
course_key (CourseLocator): The course to retrieve user grades for.
Yields:
A USER_MODEL object.
"""
try:
yield self._get_single_user(request, course_key)
except USER_MODEL.DoesNotExist:
raise self.api_error( # lint-amnesty, pylint: disable=raise-missing-from
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:
raise self.api_error( # lint-amnesty, pylint: disable=raise-missing-from
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'
)
def _get_single_user_grade(self, grade_user, 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
"""
course_grade = CourseGradeFactory().read(grade_user, course_key=course_key)
return Response([self._serialize_user_grade(grade_user, course_key, course_grade)])
def _paginate_users(self, course_key, course_enrollment_filter=None, related_models=None, annotations=None):
"""
Args:
course_key (CourseLocator): The course to retrieve grades for.
course_enrollment_filter: Optional list of Q objects to pass
to `CourseEnrollment.filter()`.
related_models: Optional list of related models to join to the CourseEnrollment table.
annotations: Optional dict of fields to add to the queryset via annotation
Returns:
A list of users, pulled from a paginated queryset of enrollments, who are enrolled in the given course.
"""
queryset = CourseEnrollment.objects
if annotations:
queryset = queryset.annotate(**annotations)
filter_args = [
Q(course_id=course_key) & Q(is_active=True)
]
filter_args.extend(course_enrollment_filter or [])
enrollments_in_course = use_read_replica_if_available(
queryset.filter(*filter_args)
)
if related_models:
enrollments_in_course = enrollments_in_course.select_related(*related_models)
paged_enrollments = self.paginate_queryset(enrollments_in_course)
retlist = []
for enrollment in paged_enrollments:
enrollment.user.enrollment_mode = enrollment.mode
retlist.append(enrollment.user)
return retlist
def _serialize_user_grade(self, user, course_key, course_grade):
"""
Serialize a single grade to dict to use in Responses
"""
return {
'username': user.username,
# per business requirements, email should only be visible for students in masters track only
'email': user.email if getattr(user, 'enrollment_mode', '') == 'masters' else '',
'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().perform_authentication(request)
if request.user.is_anonymous:
raise AuthenticationFailed