Commit 9c32b1e8 by Diana Huang

Refactor course_structure_api to have separate

API Layer.
parent 7df9ab9c
"""
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
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))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment