Refactor course_structure_api to have separate
API Layer.
This commit is contained in:
94
lms/djangoapps/course_structure_api/v0/api.py
Normal file
94
lms/djangoapps/course_structure_api/v0/api.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
API implementation of the Course Structure API for Python code.
|
||||
|
||||
Note: The course list and course detail functionality isn't currently supported here because of the tricky interactions between DRF and the code.
|
||||
Most of that information is available by accessing the course objects directly.
|
||||
"""
|
||||
|
||||
from course_structure_api.v0 import serializers
|
||||
from course_structure_api.v0.errors import CourseNotFoundError, CourseStructureNotAvailableError
|
||||
from openedx.core.djangoapps.content.course_structures import models, tasks
|
||||
from courseware import courses
|
||||
|
||||
|
||||
def _retrieve_course(course_key):
|
||||
"""Retrieves the course for the given course key.
|
||||
|
||||
Args:
|
||||
course_key: The CourseKey for the course we'd like to retrieve.
|
||||
Returns:
|
||||
the course that matches the CourseKey
|
||||
Raises:
|
||||
CourseNotFoundError
|
||||
|
||||
"""
|
||||
try:
|
||||
course = courses.get_course(course_key)
|
||||
return course
|
||||
except ValueError:
|
||||
raise CourseNotFoundError
|
||||
|
||||
|
||||
def course_structure(course_key):
|
||||
"""
|
||||
Retrieves the entire course structure, including information about all the blocks used in the course.
|
||||
|
||||
Args:
|
||||
course_key: the CourseKey of the course we'd like to retrieve.
|
||||
Returns:
|
||||
The serialized output of the course structure:
|
||||
* root: The ID of the root node of the course structure.
|
||||
|
||||
* blocks: A dictionary that maps block IDs to a collection of
|
||||
information about each block. Each block contains the following
|
||||
fields.
|
||||
|
||||
* id: The ID of the block.
|
||||
|
||||
* type: The type of block. Possible values include sequential,
|
||||
vertical, html, problem, video, and discussion. The type can also be
|
||||
the name of a custom type of block used for the course.
|
||||
|
||||
* display_name: The display name configured for the block.
|
||||
|
||||
* graded: Whether or not the sequential or problem is graded. The
|
||||
value is true or false.
|
||||
|
||||
* format: The assignment type.
|
||||
|
||||
* children: If the block has child blocks, a list of IDs of the child
|
||||
blocks.
|
||||
Raises:
|
||||
CourseStructureNotAvailableError, CourseNotFoundError
|
||||
"""
|
||||
course = _retrieve_course(course_key)
|
||||
try:
|
||||
course_structure = models.CourseStructure.objects.get(course_id=course.id)
|
||||
return serializers.CourseStructureSerializer(course_structure.structure).data
|
||||
except models.CourseStructure.DoesNotExist:
|
||||
# If we don't have data stored, generate it and return an error.
|
||||
tasks.update_course_structure.delay(unicode(course_key))
|
||||
raise CourseStructureNotAvailableError
|
||||
|
||||
|
||||
def course_grading_policy(course_key):
|
||||
"""
|
||||
Retrieves the course grading policy.
|
||||
|
||||
Args:
|
||||
course_key: CourseKey the corresponds to the course we'd like to know grading policy information about.
|
||||
Returns:
|
||||
The serialized version of the course grading policy containing the following information:
|
||||
* 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.
|
||||
"""
|
||||
course = _retrieve_course(course_key)
|
||||
return serializers.GradingPolicySerializer(course.raw_grader).data
|
||||
9
lms/djangoapps/course_structure_api/v0/errors.py
Normal file
9
lms/djangoapps/course_structure_api/v0/errors.py
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
class CourseNotFoundError(Exception):
|
||||
""" The course was not found. """
|
||||
pass
|
||||
|
||||
|
||||
class CourseStructureNotAvailableError(Exception):
|
||||
""" The course structure still needs to be generated. """
|
||||
pass
|
||||
@@ -11,7 +11,8 @@ from rest_framework.response import Response
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
from course_structure_api.v0 import serializers
|
||||
from course_structure_api.v0 import api, serializers
|
||||
from course_structure_api.v0.errors import CourseNotFoundError, CourseStructureNotAvailableError
|
||||
from courseware import courses
|
||||
from courseware.access import has_access
|
||||
from openedx.core.djangoapps.content.course_structures import models, tasks
|
||||
@@ -40,13 +41,37 @@ class CourseViewMixin(object):
|
||||
course_id = self.kwargs.get('course_id')
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = courses.get_course(course_key)
|
||||
|
||||
self.check_course_permissions(self.request.user, course)
|
||||
self.check_course_permissions(self.request.user, course_key)
|
||||
|
||||
return course
|
||||
except ValueError:
|
||||
raise Http404
|
||||
|
||||
@staticmethod
|
||||
def course_check(func):
|
||||
"""Decorator responsible for catching errors finding and returning a 404 if the user does not have access
|
||||
to the API function.
|
||||
|
||||
:param func: function to be wrapped
|
||||
:returns: the wrapped function
|
||||
"""
|
||||
def func_wrapper(self, *args, **kwargs):
|
||||
"""Wrapper function for this decorator.
|
||||
|
||||
:param *args: the arguments passed into the function
|
||||
:param **kwargs: the keyword arguments passed into the function
|
||||
:returns: the result of the wrapped function
|
||||
"""
|
||||
try:
|
||||
course_id = self.kwargs.get('course_id')
|
||||
self.course_key = CourseKey.from_string(course_id)
|
||||
self.check_course_permissions(self.request.user, self.course_key)
|
||||
return func(self, *args, **kwargs)
|
||||
except CourseNotFoundError:
|
||||
raise Http404
|
||||
|
||||
return func_wrapper
|
||||
|
||||
def user_can_access_course(self, user, course):
|
||||
"""
|
||||
Determines if the user is staff or an instructor for the course.
|
||||
@@ -185,7 +210,6 @@ class CourseDetail(CourseViewMixin, RetrieveAPIView):
|
||||
* end: The course end date. If course end date is not specified, the
|
||||
value is null.
|
||||
"""
|
||||
|
||||
serializer_class = serializers.CourseSerializer
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
@@ -227,22 +251,16 @@ class CourseStructure(CourseViewMixin, RetrieveAPIView):
|
||||
* children: If the block has child blocks, a list of IDs of the child
|
||||
blocks.
|
||||
"""
|
||||
serializer_class = serializers.CourseStructureSerializer
|
||||
course = None
|
||||
|
||||
def retrieve(self, request, *args, **kwargs):
|
||||
@CourseViewMixin.course_check
|
||||
def get(self, request, course_id=None):
|
||||
try:
|
||||
return super(CourseStructure, self).retrieve(request, *args, **kwargs)
|
||||
except models.CourseStructure.DoesNotExist:
|
||||
# If we don't have data stored, generate it and return a 503.
|
||||
tasks.update_course_structure.delay(unicode(self.course.id))
|
||||
return Response(api.course_structure(self.course_key))
|
||||
except CourseStructureNotAvailableError:
|
||||
# If we don't have data stored, we will try to regenerate it, so
|
||||
# return a 503 and as them to retry in 2 minutes.
|
||||
return Response(status=503, headers={'Retry-After': '120'})
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
# Make sure the course exists and the user has permissions to view it.
|
||||
self.course = self.get_course_or_404()
|
||||
course_structure = models.CourseStructure.objects.get(course_id=self.course.id)
|
||||
return course_structure.structure
|
||||
|
||||
|
||||
class CourseGradingPolicy(CourseViewMixin, ListAPIView):
|
||||
@@ -269,11 +287,8 @@ class CourseGradingPolicy(CourseViewMixin, ListAPIView):
|
||||
final grade.
|
||||
"""
|
||||
|
||||
serializer_class = serializers.GradingPolicySerializer
|
||||
allow_empty = False
|
||||
|
||||
def get_queryset(self):
|
||||
course = self.get_course_or_404()
|
||||
|
||||
# Return the raw data. The serializer will handle the field mappings.
|
||||
return course.raw_grader
|
||||
@CourseViewMixin.course_check
|
||||
def get(self, request, course_id=None):
|
||||
return Response(api.course_grading_policy(self.course_key))
|
||||
|
||||
Reference in New Issue
Block a user