Files
edx-platform/lms/djangoapps/program_enrollments/api/grades.py
Kyle McCormick af4e7a348a Create Python API for program_enrollments: Part IV
This commit completes the program_enrollments LMS app
Python API for the time being. It does the following:
* Add bulk-lookup of users by external key in api/reading.py
* Add bulk-writing of program enrollments in api/writing.py
* Move grade-reading to api/grades.py
* Refactor api/linking.py to use api/writing.py
* Refactor signals.py to use api/linking.py
* Update rest_api/v1/views.py to utilize all these changes
* Update linking management command and support tool to use API
* Remove outdated tests from test_models.py
* Misc. cleanup

EDUCATOR-4321
2019-09-20 09:55:13 -04:00

136 lines
4.3 KiB
Python

"""
Python API functions related to reading program-course grades.
Outside of this subpackage, import these functions
from `lms.djangoapps.program_enrollments.api`.
"""
from __future__ import absolute_import, unicode_literals
import logging
from six import text_type
from lms.djangoapps.grades.api import CourseGradeFactory, clear_prefetched_course_grades, prefetch_course_grades
from util.query import read_replica_or_default
from .reading import fetch_program_course_enrollments
logger = logging.getLogger(__name__)
def iter_program_course_grades(program_uuid, course_key, paginate_queryset_fn=None):
"""
Load grades (or grading errors) for a given program-course.
Arguments:
program_uuid (str)
course_key (CourseKey)
paginate_queryset_fn (QuerySet -> QuerySet):
Optional function to paginate the results,
generally passed in from `self.request.paginate_queryset`
on a paginated DRF `APIView`.
If `None`, all results will be loaded and returned.
Returns: generator[BaseProgramCourseGrade]
"""
enrollments_qs = fetch_program_course_enrollments(
program_uuid=program_uuid,
course_key=course_key,
realized_only=True,
).select_related(
'program_enrollment',
'program_enrollment__user',
).using(read_replica_or_default())
enrollments = (
paginate_queryset_fn(enrollments_qs) if paginate_queryset_fn
else enrollments_qs
)
if not enrollments:
return []
return _generate_grades(course_key, list(enrollments))
def _generate_grades(course_key, enrollments):
"""
Load enrolled user grades for a program-course,
using bulk fetching for efficiency.
Arguments:
course_key (CourseKey)
enrollments (list[ProgramCourseEnrollment])
Yields: BaseProgramCourseGrade
"""
users = [enrollment.program_enrollment.user for enrollment in enrollments]
prefetch_course_grades(course_key, users)
try:
grades_iter = CourseGradeFactory().iter(users, course_key=course_key)
for enrollment, grade_tuple in zip(enrollments, grades_iter):
user, course_grade, exception = grade_tuple
if course_grade:
yield ProgramCourseGradeOk(enrollment, course_grade)
else:
error_template = 'Failed to load course grade for user ID {} in {}: {}'
error_string = error_template.format(
user.id,
course_key,
text_type(exception) if exception else 'Unknown error'
)
logger.error(error_string)
yield ProgramCourseGradeError(enrollment, exception)
finally:
clear_prefetched_course_grades(course_key)
class BaseProgramCourseGrade(object):
"""
Base for either a courserun grade or grade-loading failure.
Can be passed to ProgramCourseGradeResultSerializer.
"""
is_error = None # Override in subclass
def __init__(self, program_course_enrollment):
"""
Given a ProgramCourseEnrollment,
create a BaseProgramCourseGrade instance.
"""
self.program_course_enrollment = program_course_enrollment
class ProgramCourseGradeOk(BaseProgramCourseGrade):
"""
Represents a courserun grade for a user enrolled through a program.
"""
is_error = False
def __init__(self, program_course_enrollment, course_grade):
"""
Given a ProgramCourseEnrollment and course grade object,
create a ProgramCourseGradeOk.
"""
super(ProgramCourseGradeOk, self).__init__(
program_course_enrollment
)
self.passed = course_grade.passed
self.percent = course_grade.percent
self.letter_grade = course_grade.letter_grade
class ProgramCourseGradeError(BaseProgramCourseGrade):
"""
Represents a failure to load a courserun grade for a user enrolled through
a program.
"""
is_error = True
def __init__(self, program_course_enrollment, exception=None):
"""
Given a ProgramCourseEnrollment and an Exception,
create a ProgramCourseGradeError.
"""
super(ProgramCourseGradeError, self).__init__(
program_course_enrollment
)
self.error = text_type(exception) if exception else "Unknown error"