138 lines
4.6 KiB
Python
138 lines
4.6 KiB
Python
"""
|
|
Common utilities for Contentstore APIs.
|
|
"""
|
|
|
|
|
|
from contextlib import contextmanager
|
|
|
|
from opaque_keys.edx.keys import CourseKey
|
|
from rest_framework import status
|
|
from rest_framework.generics import GenericAPIView
|
|
|
|
from common.djangoapps.student.auth import has_course_author_access
|
|
from openedx.core.djangoapps.util.forms import to_bool
|
|
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
|
|
from openedx.core.lib.cache_utils import request_cached
|
|
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
|
|
|
|
|
|
@view_auth_classes()
|
|
class BaseCourseView(DeveloperErrorViewMixin, GenericAPIView):
|
|
"""
|
|
A base class for contentstore course api views.
|
|
"""
|
|
@contextmanager
|
|
def get_course(self, request, course_key):
|
|
"""
|
|
Context manager that yields a course, given a request and course_key.
|
|
"""
|
|
store = modulestore()
|
|
with store.bulk_operations(course_key):
|
|
course = store.get_course(course_key, depth=self._required_course_depth(request))
|
|
yield course
|
|
|
|
@staticmethod
|
|
def _required_course_depth(request):
|
|
"""
|
|
Returns how far deep we need to go into the course tree to
|
|
get all of the information required. Will use entire tree if the request's
|
|
`all` param is truthy, otherwise goes to depth of 2 (subsections).
|
|
"""
|
|
all_requested = get_bool_param(request, 'all', False)
|
|
if all_requested:
|
|
return None
|
|
return 2
|
|
|
|
@classmethod
|
|
@request_cached()
|
|
def _get_visible_subsections(cls, course):
|
|
"""
|
|
Returns a list of all visible subsections for a course.
|
|
"""
|
|
_, visible_sections = cls._get_sections(course)
|
|
visible_subsections = []
|
|
for section in visible_sections:
|
|
visible_subsections.extend(cls._get_visible_children(section))
|
|
return visible_subsections
|
|
|
|
@classmethod
|
|
@request_cached()
|
|
def _get_sections(cls, course):
|
|
"""
|
|
Returns all sections in the course.
|
|
"""
|
|
return cls._get_all_children(course)
|
|
|
|
@classmethod
|
|
def _get_all_children(cls, parent):
|
|
"""
|
|
Returns all child nodes of the given parent.
|
|
"""
|
|
store = modulestore()
|
|
children = [store.get_item(child_usage_key) for child_usage_key in cls._get_children(parent)]
|
|
visible_children = [
|
|
c for c in children
|
|
if not c.visible_to_staff_only and not c.hide_from_toc
|
|
]
|
|
return children, visible_children
|
|
|
|
@classmethod
|
|
def _get_visible_children(cls, parent):
|
|
"""
|
|
Returns only the visible children of the given parent.
|
|
"""
|
|
_, visible_chidren = cls._get_all_children(parent)
|
|
return visible_chidren
|
|
|
|
@classmethod
|
|
def _get_children(cls, parent):
|
|
"""
|
|
Returns the value of the 'children' attribute of a node.
|
|
"""
|
|
if not hasattr(parent, 'children'):
|
|
return []
|
|
else:
|
|
return parent.children
|
|
|
|
|
|
def get_bool_param(request, param_name, default):
|
|
"""
|
|
Given a request, parameter name, and default value, returns
|
|
either a boolean value or the default.
|
|
"""
|
|
param_value = request.query_params.get(param_name, None)
|
|
bool_value = to_bool(param_value)
|
|
if bool_value is None:
|
|
return default
|
|
else:
|
|
return bool_value
|
|
|
|
|
|
def course_author_access_required(view):
|
|
"""
|
|
Ensure the user making the API request has course author access to the given course.
|
|
|
|
This decorator parses the course_id parameter, checks course access, and passes
|
|
the parsed course_key to the view as a parameter. It will raise a
|
|
403 error if the user does not have author access.
|
|
|
|
Usage::
|
|
@course_author_access_required
|
|
def my_view(request, course_key):
|
|
# Some functionality ...
|
|
"""
|
|
def _wrapper_view(self, request, course_id, *args, **kwargs):
|
|
"""
|
|
Checks for course author access for the given course by the requesting user.
|
|
Calls the view function if has access, otherwise raises a 403.
|
|
"""
|
|
course_key = CourseKey.from_string(course_id)
|
|
if not has_course_author_access(request.user, course_key):
|
|
raise DeveloperErrorViewMixin.api_error(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
developer_message='The requesting user does not have course author permissions.',
|
|
error_code='user_permissions',
|
|
)
|
|
return view(self, request, course_key, *args, **kwargs)
|
|
return _wrapper_view
|