Beispiel #1
0
    def _update_last_visited_module_id(self, request, course, module_key, modification_date):
        """
        Saves the module id if the found modification_date is less recent than the passed modification date
        """
        field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
            course.id, request.user, course, depth=2)
        try:
            module_descriptor = modulestore().get_item(module_key)
        except ItemNotFoundError:
            return Response(errors.ERROR_INVALID_MODULE_ID, status=400)
        module = get_module_for_descriptor(
            request.user, request, module_descriptor, field_data_cache, course.id, course=course
        )

        if modification_date:
            key = KeyValueStore.Key(
                scope=Scope.user_state,
                user_id=request.user.id,
                block_scope_id=course.location,
                field_name='position'
            )
            original_store_date = field_data_cache.last_modified(key)
            if original_store_date is not None and modification_date < original_store_date:
                # old modification date so skip update
                return self._get_course_info(request, course)

        save_positions_recursively_up(request.user, request, field_data_cache, module, course=course)
        return self._get_course_info(request, course)
Beispiel #2
0
def _get_course_module(course_descriptor, user):
    # Adding courseware imports here to insulate other apps (e.g. schedules) to
    # avoid import errors.
    from lms.djangoapps.courseware.model_data import FieldDataCache
    from lms.djangoapps.courseware.module_render import get_module_for_descriptor

    # Fake a request to fool parts of the courseware that want to inspect it.
    request = get_request_or_stub()
    request.user = user

    # Now evil modulestore magic to inflate our descriptor with user state and
    # permissions checks.
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course_descriptor.id,
        user,
        course_descriptor,
        depth=1,
        read_only=True,
    )
    return get_module_for_descriptor(
        user,
        request,
        course_descriptor,
        field_data_cache,
        course_descriptor.id,
        course=course_descriptor,
    )
Beispiel #3
0
def edxnotes_visibility(request, course_id):
    """
    Handle ajax call from "Show notes" checkbox.
    """
    course_key = CourseKey.from_string(course_id)
    course = get_course_with_access(request.user, "load", course_key)
    field_data_cache = FieldDataCache([course], course_key, request.user)
    course_module = get_module_for_descriptor(request.user,
                                              request,
                                              course,
                                              field_data_cache,
                                              course_key,
                                              course=course)

    if not is_feature_enabled(course, request.user):
        raise Http404

    try:
        visibility = json.loads(request.body.decode('utf8'))["visibility"]
        course_module.edxnotes_visibility = visibility
        course_module.save()
        return JsonResponse(status=200)
    except (ValueError, KeyError):
        log.warning(
            u"Could not decode request body as JSON and find a boolean visibility field: '%s'",
            request.body)
        return JsonResponseBadRequest()
Beispiel #4
0
def _get_course_module(course_descriptor, user):
    """ Gets course module that takes into account user state and permissions """
    # Adding courseware imports here to insulate other apps (e.g. schedules) to
    # avoid import errors.
    from lms.djangoapps.courseware.model_data import FieldDataCache
    from lms.djangoapps.courseware.module_render import get_module_for_descriptor

    # Fake a request to fool parts of the courseware that want to inspect it.
    request = get_request_or_stub()
    request.user = user

    # Now evil modulestore magic to inflate our descriptor with user state and
    # permissions checks.
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course_descriptor.id,
        user,
        course_descriptor,
        depth=1,
        read_only=True,
    )
    course_module = get_module_for_descriptor(
        user,
        request,
        course_descriptor,
        field_data_cache,
        course_descriptor.id,
        course=course_descriptor,
    )
    if not course_module:
        raise CourseUpdateDoesNotExist('Course module {} not found'.format(
            course_descriptor.id))
    return course_module
Beispiel #5
0
    def _last_visited_module_path(self, request, course):
        """
        Returns the path from the last module visited by the current user in the given course up to
        the course module. If there is no such visit, the first item deep enough down the course
        tree is used.
        """
        field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
            course.id, request.user, course, depth=2)

        course_module = get_module_for_descriptor(request.user,
                                                  request,
                                                  course,
                                                  field_data_cache,
                                                  course.id,
                                                  course=course)

        path = [course_module]
        chapter = get_current_child(course_module, min_depth=2)
        if chapter is not None:
            path.append(chapter)
            section = get_current_child(chapter, min_depth=1)
            if section is not None:
                path.append(section)

        path.reverse()
        return path
Beispiel #6
0
def get_course_info_section_module(request, user, course, section_key):
    """
    This returns the course info module for a given section_key.

    Valid keys:
    - handouts
    - guest_handouts
    - updates
    - guest_updates
    """
    usage_key = get_course_info_usage_key(course, section_key)

    # Use an empty cache
    field_data_cache = FieldDataCache([], course.id, user)

    return get_module(
        user,
        request,
        usage_key,
        field_data_cache,
        log_if_not_found=False,
        wrap_xmodule_display=False,
        static_asset_path=course.static_asset_path,
        course=course
    )
Beispiel #7
0
    def test_repeated_course_module_instantiation(self, loops, course_depth):

        with modulestore().default_store(ModuleStoreEnum.Type.split):
            course = CourseFactory.create()
            chapter = ItemFactory(parent=course,
                                  category='chapter',
                                  graded=True)
            section = ItemFactory(parent=chapter, category='sequential')
            __ = ItemFactory(parent=section, category='problem')

        fake_request = self.factory.get(
            reverse('progress', kwargs={'course_id': str(course.id)}))

        course = modulestore().get_course(course.id, depth=course_depth)

        for _ in range(loops):
            field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
                course.id, self.user, course, depth=course_depth)
            course_module = get_module_for_descriptor(self.user,
                                                      fake_request,
                                                      course,
                                                      field_data_cache,
                                                      course.id,
                                                      course=course)
            for chapter in course_module.get_children():
                for section in chapter.get_children():
                    for item in section.get_children():
                        assert item.graded
Beispiel #8
0
 def setUp(self):
     super(TestInvalidScopes, self).setUp()
     self.user = UserFactory.create(username='******')
     self.field_data_cache = FieldDataCache(
         [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
         course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
def answer_entrance_exam_problem(course, request, problem, user=None, value=1, max_value=1):
    """
    Takes a required milestone `problem` in a `course` and fulfills it.

    Args:
        course (Course): Course object, the course the required problem is in
        request (Request): request Object
        problem (xblock): xblock object, the problem to be fulfilled
        user (User): User object in case it is different from request.user
        value (int): raw_earned value of the problem
        max_value (int): raw_possible value of the problem
    """
    if not user:
        user = request.user

    grade_dict = {'value': value, 'max_value': max_value, 'user_id': user.id}
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course.id,
        user,
        course,
        depth=2
    )
    module = get_module(
        user,
        request,
        problem.scope_ids.usage_id,
        field_data_cache,
    )
    module.system.publish(problem, 'grade', grade_dict)
Beispiel #10
0
    def _init_field_data_for_block(self, block):
        """
        Initialize the FieldData implementation for the specified XBlock
        """
        if self.user is None:
            # No user is specified, so we want to throw an error if anything attempts to read/write user-specific fields
            student_data_store = None
        elif self.user.is_anonymous:
            # The user is anonymous. Future work will support saving their state
            # in a cache or the django session but for now just use a highly
            # ephemeral dict.
            student_data_store = KvsFieldData(kvs=DictKeyValueStore())
        elif self.system.student_data_mode == XBlockRuntimeSystem.STUDENT_DATA_EPHEMERAL:
            # We're in an environment like Studio where we want to let the
            # author test blocks out but not permanently save their state.
            # This in-memory dict will typically only persist for one
            # request-response cycle, so we need to soon replace it with a store
            # that puts the state into a cache or the django session.
            student_data_store = KvsFieldData(kvs=DictKeyValueStore())
        else:
            # Use database-backed field data (i.e. store user_state in StudentModule)
            context_key = block.scope_ids.usage_id.context_key
            if context_key not in self.django_field_data_caches:
                field_data_cache = FieldDataCache(
                    [block],
                    course_id=context_key,
                    user=self.user,
                    asides=None,
                    read_only=False,
                )
                self.django_field_data_caches[context_key] = field_data_cache
            else:
                field_data_cache = self.django_field_data_caches[context_key]
                field_data_cache.add_descriptors_to_cache([block])
            student_data_store = KvsFieldData(
                kvs=DjangoKeyValueStore(field_data_cache))

        return SplitFieldData({
            Scope.content: self.system.authored_data_store,
            Scope.settings: self.system.authored_data_store,
            Scope.parent: self.system.authored_data_store,
            Scope.children: self.system.authored_data_store,
            Scope.user_state_summary: student_data_store,
            Scope.user_state: student_data_store,
            Scope.user_info: student_data_store,
            Scope.preferences: student_data_store,
        })
Beispiel #11
0
def get_course_child_content(request, user, course_key, child_descriptor):
    """
    Returns course child content
    """
    field_data_cache = FieldDataCache([child_descriptor], course_key, user)
    child_content = module_render.get_module_for_descriptor(
        user, request, child_descriptor, field_data_cache, course_key)
    return child_content
Beispiel #12
0
def get_course_content(request, user, course_key, course_descriptor):  # pylint: disable=W0613
    """
    Returns course content
    """
    field_data_cache = FieldDataCache([course_descriptor], course_key, user)
    course_content = module_render.get_module_for_descriptor(
        user, request, course_descriptor, field_data_cache, course_key)
    return course_content
 def get_course_module(self, course):
     request = RequestFactory().request()
     field_data_cache = FieldDataCache([], course.id, self.user)
     return get_module(self.user,
                       request,
                       course.location,
                       field_data_cache,
                       course=course)
Beispiel #14
0
    def _init_field_data_for_block(self, block):
        """
        Initialize the FieldData implementation for the specified XBlock
        """
        if self.user is None:
            # No user is specified, so we want to throw an error if anything attempts to read/write user-specific fields
            student_data_store = None
        elif self.user.is_anonymous:
            # This is an anonymous (non-registered) user:
            assert self.user_id.startswith("anon")
            kvs = EphemeralKeyValueStore()
            student_data_store = KvsFieldData(kvs)
        elif self.system.student_data_mode == XBlockRuntimeSystem.STUDENT_DATA_EPHEMERAL:
            # We're in an environment like Studio where we want to let the
            # author test blocks out but not permanently save their state.
            kvs = EphemeralKeyValueStore()
            student_data_store = KvsFieldData(kvs)
        else:
            # Use database-backed field data (i.e. store user_state in StudentModule)
            context_key = block.scope_ids.usage_id.context_key
            if context_key not in self.django_field_data_caches:
                field_data_cache = FieldDataCache(
                    [block],
                    course_id=context_key,
                    user=self.user,
                    asides=None,
                    read_only=False,
                )
                self.django_field_data_caches[context_key] = field_data_cache
            else:
                field_data_cache = self.django_field_data_caches[context_key]
                field_data_cache.add_descriptors_to_cache([block])
            student_data_store = KvsFieldData(
                kvs=DjangoKeyValueStore(field_data_cache))

        return SplitFieldData({
            Scope.content: self.system.authored_data_store,
            Scope.settings: self.system.authored_data_store,
            Scope.parent: self.system.authored_data_store,
            Scope.children: self.system.children_data_store,
            Scope.user_state_summary: student_data_store,
            Scope.user_state: student_data_store,
            Scope.user_info: student_data_store,
            Scope.preferences: student_data_store,
        })
def get_module_for_student(student, usage_key, request=None, course=None):
    """Return the module for the (student, location) using a DummyRequest."""
    if request is None:
        request = DummyRequest()
        request.user = student

    descriptor = modulestore().get_item(usage_key, depth=0)
    field_data_cache = FieldDataCache([descriptor], usage_key.course_key, student)
    return get_module(student, request, usage_key, field_data_cache, course=course)
 def setUp(self):
     super().setUp()
     self.user = UserFactory.create(username='******')
     self.field_data_cache = FieldDataCache(
         [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
         COURSE_KEY,
         self.user,
     )
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Beispiel #17
0
    def setUp(self):
        super(TestMissingStudentModule, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments

        self.user = UserFactory.create(username='******')
        self.assertEqual(self.user.id, 1)   # check our assumption hard-coded in the key functions above.

        # The descriptor has no fields, so FDC shouldn't send any queries
        with self.assertNumQueries(0):
            self.field_data_cache = FieldDataCache([mock_descriptor()], course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)
Beispiel #18
0
def _get_module_instance_for_task(course_id,
                                  student,
                                  module_descriptor,
                                  xmodule_instance_args=None,
                                  grade_bucket_type=None,
                                  course=None):
    """
    Fetches a StudentModule instance for a given `course_id`, `student` object, and `module_descriptor`.

    `xmodule_instance_args` is used to provide information for creating a track function and an XQueue callback.
    These are passed, along with `grade_bucket_type`, to get_module_for_descriptor_internal, which sidesteps
    the need for a Request object when instantiating an xmodule instance.
    """
    # reconstitute the problem's corresponding XModule:
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course_id, student, module_descriptor)
    student_data = KvsFieldData(DjangoKeyValueStore(field_data_cache))

    # get request-related tracking information from args passthrough, and supplement with task-specific
    # information:
    request_info = xmodule_instance_args.get(
        'request_info', {}) if xmodule_instance_args is not None else {}
    task_info = {
        "student": student.username,
        "task_id": _get_task_id_from_xmodule_args(xmodule_instance_args)
    }

    def make_track_function():
        '''
        Make a tracking function that logs what happened.

        For insertion into ModuleSystem, and used by CapaModule, which will
        provide the event_type (as string) and event (as dict) as arguments.
        The request_info and task_info (and page) are provided here.
        '''
        return lambda event_type, event: task_track(
            request_info, task_info, event_type, event, page='x_module_task')

    xqueue_callback_url_prefix = xmodule_instance_args.get('xqueue_callback_url_prefix', '') \
        if xmodule_instance_args is not None else ''

    return get_module_for_descriptor_internal(
        user=student,
        descriptor=module_descriptor,
        student_data=student_data,
        course_id=course_id,
        track_function=make_track_function(),
        xqueue_callback_url_prefix=xqueue_callback_url_prefix,
        grade_bucket_type=grade_bucket_type,
        # This module isn't being used for front-end rendering
        request_token=None,
        # pass in a loaded course for override enabling
        course=course)
    def setUp(self):
        super().setUp()

        self.user = UserFactory.create(username='******')
        assert self.user.id == 1
        # check our assumption hard-coded in the key functions above.

        # The descriptor has no fields, so FDC shouldn't send any queries
        with self.assertNumQueries(0):
            self.field_data_cache = FieldDataCache([mock_descriptor()],
                                                   course_id, self.user)
        self.kvs = DjangoKeyValueStore(self.field_data_cache)
    def _return_table_of_contents(self):
        """
        Returns table of content for the entrance exam specific to this test

        Returns the table of contents for course self.course, for chapter
        self.entrance_exam, and for section self.exam1
        """
        self.field_data_cache = FieldDataCache.cache_for_descriptor_descendents(  # pylint: disable=attribute-defined-outside-init
            self.course.id, self.request.user, self.entrance_exam)
        toc = toc_for_course(self.request.user, self.request, self.course,
                             self.entrance_exam.url_name, self.exam_1.url_name,
                             self.field_data_cache)
        return toc['chapters']
Beispiel #21
0
    def setUp(self):
        super(TestStudentModuleStorage, self).setUp()  # lint-amnesty, pylint: disable=super-with-arguments
        student_module = StudentModuleFactory(state=json.dumps({'a_field': 'a_value', 'b_field': 'b_value'}))
        self.user = student_module.student
        self.assertEqual(self.user.id, 1)   # check our assumption hard-coded in the key functions above.

        # There should be only one query to load a single descriptor with a single user_state field
        with self.assertNumQueries(1):
            self.field_data_cache = FieldDataCache(
                [mock_descriptor([mock_field(Scope.user_state, 'a_field')])], course_id, self.user
            )

        self.kvs = DjangoKeyValueStore(self.field_data_cache)
Beispiel #22
0
 def get_module_for_user(self, user, course, problem):
     """Helper function to get useful module at self.location in self.course_id for user"""
     mock_request = mock.MagicMock()
     mock_request.user = user
     field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
         course.id, user, course, depth=2)
     module = module_render.get_module(  # pylint: disable=protected-access
         user,
         mock_request,
         problem.location,
         field_data_cache,
     )
     return module
Beispiel #23
0
def _get_course_module(course_descriptor, user):
    # Fake a request to fool parts of the courseware that want to inspect it.
    request = get_request_or_stub()
    request.user = user

    # Now evil modulestore magic to inflate our descriptor with user state and
    # permissions checks.
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course_descriptor.id, user, course_descriptor, depth=1, read_only=True,
    )
    return get_module_for_descriptor(
        user, request, course_descriptor, field_data_cache, course_descriptor.id, course=course_descriptor,
    )
Beispiel #24
0
 def setUp(self):
     field_storage = self.factory.create()
     if hasattr(field_storage, 'student'):
         self.user = field_storage.student
     else:
         self.user = UserFactory.create()
     self.mock_descriptor = mock_descriptor([
         mock_field(self.scope, 'existing_field'),
         mock_field(self.scope, 'other_existing_field')])
     # Each field is stored as a separate row in the table,
     # but we can query them in a single query
     with self.assertNumQueries(1):
         self.field_data_cache = FieldDataCache([self.mock_descriptor], course_id, self.user)
     self.kvs = DjangoKeyValueStore(self.field_data_cache)
Beispiel #25
0
    def test_changing_position_works(self):
        # Make a mock FieldDataCache for this course, so we can get the course module
        mock_field_data_cache = FieldDataCache([self.course], self.course.id,
                                               self.student)
        course = get_module_for_descriptor(self.student,
                                           MagicMock(name='request'),
                                           self.course,
                                           mock_field_data_cache,
                                           self.course.id,
                                           course=self.course)

        # Now that we have the course, change the position and save, nothing should explode!
        course.position = 2
        course.save()
Beispiel #26
0
def edxnotes(request, course_id):
    """
    Displays the EdxNotes page.

    Arguments:
        request: HTTP request object
        course_id: course id

    Returns:
        Rendered HTTP response.
    """
    course_key = CourseKey.from_string(course_id)
    course = get_course_with_access(request.user, "load", course_key)

    if not is_feature_enabled(course, request.user):
        raise Http404

    notes_info = get_notes(request, course)
    has_notes = (len(notes_info.get('results')) > 0)
    context = {
        "course": course,
        "notes_endpoint": reverse("notes", kwargs={"course_id": course_id}),
        "notes": notes_info,
        "page_size": DEFAULT_PAGE_SIZE,
        "debug": settings.DEBUG,
        'position': None,
        'disabled_tabs': settings.NOTES_DISABLED_TABS,
        'has_notes': has_notes,
    }

    if not has_notes:
        field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
            course.id, request.user, course, depth=2)
        course_module = get_module_for_descriptor(request.user,
                                                  request,
                                                  course,
                                                  field_data_cache,
                                                  course_key,
                                                  course=course)
        position = get_course_position(course_module)
        if position:
            context.update({
                'position': position,
            })

    return render_to_response("edxnotes/edxnotes.html", context)
    def setUp(self):
        super().setUp()
        student_module = StudentModuleFactory(
            state=json.dumps({
                'a_field': 'a_value',
                'b_field': 'b_value'
            }))
        self.user = student_module.student
        assert self.user.id == 1
        # check our assumption hard-coded in the key functions above.

        # There should be only one query to load a single descriptor with a single user_state field
        with self.assertNumQueries(1):
            self.field_data_cache = FieldDataCache(
                [mock_descriptor([mock_field(Scope.user_state, 'a_field')])],
                COURSE_KEY,
                self.user,
            )

        self.kvs = DjangoKeyValueStore(self.field_data_cache)
Beispiel #28
0
def answer_problem(course, request, problem, score=1, max_value=1):
    """
    Records a correct answer for the given problem.

    Arguments:
        course (Course): Course object, the course the required problem is in
        request (Request): request Object
        problem (xblock): xblock object, the problem to be answered
    """

    user = request.user
    grade_dict = {'value': score, 'max_value': max_value, 'user_id': user.id}
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        course.id, user, course, depth=2)
    module = get_module(
        user,
        request,
        problem.scope_ids.usage_id,
        field_data_cache,
    )
    module.system.publish(problem, 'grade', grade_dict)
Beispiel #29
0
def complete_student_attempt_task(user_identifier: str, content_id: str) -> None:
    """
    Marks all completable children of content_id as complete for the user

    Submits all completable xblocks inside of the content_id block to the
    Completion Service to mark them as complete. One use case of this function is
    for special exams (timed/proctored) where regardless of submission status on
    individual problems, we want to mark the entire exam as complete when the exam
    is finished.

    params:
        user_identifier (str): username or email of a user
        content_id (str): the block key for a piece of content
    """
    err_msg_prefix = (
        'Error occurred while attempting to complete student attempt for user '
        f'{user_identifier} for content_id {content_id}. '
    )
    err_msg = None
    try:
        user = get_user_by_username_or_email(user_identifier)
        block_key = UsageKey.from_string(content_id)
        root_descriptor = modulestore().get_item(block_key)
    except ObjectDoesNotExist:
        err_msg = err_msg_prefix + 'User does not exist!'
    except InvalidKeyError:
        err_msg = err_msg_prefix + 'Invalid content_id!'
    except ItemNotFoundError:
        err_msg = err_msg_prefix + 'Block not found in the modulestore!'
    if err_msg:
        log.error(err_msg)
        return

    # This logic has been copied over from openedx/core/djangoapps/schedules/content_highlights.py
    # in the _get_course_module function.
    # I'm not sure if this is an anti-pattern or not, so if you can avoid re-copying this, please do.
    # We are using it here because we ran into issues with the User service being undefined when we
    # encountered a split_test xblock.

    # Fake a request to fool parts of the courseware that want to inspect it.
    request = get_request_or_stub()
    request.user = user

    # Now evil modulestore magic to inflate our descriptor with user state and
    # permissions checks.
    field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
        root_descriptor.course_id, user, root_descriptor, read_only=True,
    )
    root_module = get_module_for_descriptor(
        user, request, root_descriptor, field_data_cache, root_descriptor.course_id,
    )
    if not root_module:
        err_msg = err_msg_prefix + 'Module unable to be created from descriptor!'
        log.error(err_msg)
        return

    def _submit_completions(block, user):
        """
        Recursively submits the children for completion to the Completion Service
        """
        mode = XBlockCompletionMode.get_mode(block)
        if mode == XBlockCompletionMode.COMPLETABLE:
            block.runtime.publish(block, 'completion', {'completion': 1.0, 'user_id': user.id})
        elif mode == XBlockCompletionMode.AGGREGATOR:
            # I know this looks weird, but at the time of writing at least, there isn't a good
            # single way to get the children assigned for a partcular user. Some blocks define the
            # child descriptors method, but others don't and with blocks like Randomized Content
            # (Library Content), the get_children method returns all children and not just assigned
            # children. So this is our way around situations like that. See also Split Test Module
            # for another use case where user state has to be taken into account via get_child_descriptors
            block_children = ((hasattr(block, 'get_child_descriptors') and block.get_child_descriptors())
                              or (hasattr(block, 'get_children') and block.get_children())
                              or [])
            for child in block_children:
                _submit_completions(child, user)

    _submit_completions(root_module, user)
Beispiel #30
0
def get_course_about_section(request, course, section_key):
    """
    This returns the snippet of html to be rendered on the course about page,
    given the key for the section.

    Valid keys:
    - overview
    - about_sidebar_html
    - short_description
    - description
    - key_dates (includes start, end, exams, etc)
    - video
    - course_staff_short
    - course_staff_extended
    - requirements
    - syllabus
    - textbook
    - faq
    - effort
    - more_info
    - ocw_links
    """

    # Many of these are stored as html files instead of some semantic
    # markup. This can change without effecting this interface when we find a
    # good format for defining so many snippets of text/html.

    html_sections = {
        'short_description',
        'description',
        'key_dates',
        'video',
        'course_staff_short',
        'course_staff_extended',
        'requirements',
        'syllabus',
        'textbook',
        'faq',
        'more_info',
        'overview',
        'effort',
        'end_date',
        'prerequisites',
        'about_sidebar_html',
        'ocw_links'
    }

    if section_key in html_sections:
        try:
            loc = course.location.replace(category='about', name=section_key)

            # Use an empty cache
            field_data_cache = FieldDataCache([], course.id, request.user)
            about_module = get_module(
                request.user,
                request,
                loc,
                field_data_cache,
                log_if_not_found=False,
                wrap_xmodule_display=False,
                static_asset_path=course.static_asset_path,
                course=course
            )

            html = ''

            if about_module is not None:
                try:
                    html = about_module.render(STUDENT_VIEW).content
                except Exception:  # pylint: disable=broad-except
                    html = render_to_string('courseware/error-message.html', None)
                    log.exception(
                        u"Error rendering course=%s, section_key=%s",
                        course, section_key
                    )
            return html

        except ItemNotFoundError:
            log.warning(
                u"Missing about section %s in course %s",
                section_key, text_type(course.location)
            )
            return None

    raise KeyError("Invalid about key " + str(section_key))