Пример #1
0
    def check_toggle_tab_visiblity(self, tab_type, new_is_hidden_setting):
        """Helper method to check changes in tab visibility"""

        # find the tab
        old_tab = CourseTabList.get_tab_by_type(self.course.tabs, tab_type)

        # visibility should be different from new setting
        self.assertNotEqual(old_tab.is_hidden, new_is_hidden_setting)

        # post the request
        resp = self.client.ajax_post(
            self.url,
            data=json.dumps({
                'tab_id_locator': {
                    'tab_id': old_tab.tab_id
                },
                'is_hidden': new_is_hidden_setting,
            }),
        )
        self.assertEqual(resp.status_code, 204)

        # reload the course and verify the new visibility setting
        self.reload_course()
        new_tab = CourseTabList.get_tab_by_type(self.course.tabs, tab_type)
        self.assertEqual(new_tab.is_hidden, new_is_hidden_setting)
Пример #2
0
    def handle(self, *args, **options):
        """
        Gathers all course keys in the modulestore and updates the course tabs
        if there are any new default course tabs. Else, makes no updates.
        """
        store = modulestore()
        course_keys = sorted(
            (course.id for course in store.get_course_summaries()),
            key=
            str  # Different types of CourseKeys can't be compared without this.
        )
        logger.info(f'{len(course_keys)} courses read from modulestore.')

        for course_key in course_keys:
            try:
                course = store.get_course(course_key, depth=1)
                existing_tabs = {tab.type for tab in course.tabs}
                CourseTabList.initialize_default(course)
                new_tabs = {tab.type for tab in course.tabs}

                if existing_tabs != new_tabs:
                    # This will trigger the Course Published Signal which is necessary to update
                    # the corresponding Course Overview
                    logger.info(f'Updating tabs for {course_key}.')
                    store.update_item(course,
                                      ModuleStoreEnum.UserID.mgmt_command)
                    logger.info(f'Successfully updated tabs for {course_key}.')
            except Exception as err:  # pylint: disable=broad-except
                logger.exception(err)
                logger.error(
                    f'Course {course_key} encountered an Exception while trying to update.'
                )
Пример #3
0
def reorder_tabs_handler(course_item, request):
    """
    Helper function for handling reorder of tabs request
    """

    # Tabs are identified by tab_id or locators.
    # The locators are used to identify static tabs since they are xmodules.
    # Although all tabs have tab_ids, newly created static tabs do not know
    # their tab_ids since the xmodule editor uses only locators to identify new objects.
    requested_tab_id_locators = request.json["tabs"]

    # original tab list in original order
    old_tab_list = course_item.tabs

    # create a new list in the new order
    new_tab_list = []
    for tab_id_locator in requested_tab_id_locators:
        tab = get_tab_by_tab_id_locator(old_tab_list, tab_id_locator)
        if tab is None:
            return JsonResponse(
                {"error": "Tab with id_locator '{0}' does not exist.".format(tab_id_locator)}, status=400
            )
        new_tab_list.append(tab)

    # the old_tab_list may contain additional tabs that were not rendered in the UI because of
    # global or course settings.  so add those to the end of the list.
    non_displayed_tabs = set(old_tab_list) - set(new_tab_list)
    new_tab_list.extend(non_displayed_tabs)

    # validate the tabs to make sure everything is Ok (e.g., did the client try to reorder unmovable tabs?)
    try:
        CourseTabList.validate_tabs(new_tab_list)
    except InvalidTabsException, exception:
        return JsonResponse({"error": "New list of tabs is not valid: {0}.".format(str(exception))}, status=400)
Пример #4
0
    def check_toggle_tab_visibility(self, tab_type, new_is_hidden_setting):
        """
        Helper method to check changes in tab visibility.
        """
        old_tab = CourseTabList.get_tab_by_type(self.course.tabs, tab_type)
        # visibility should be different from new setting
        assert old_tab.is_hidden != new_is_hidden_setting

        resp = self.make_update_tab_request({"tab_id": old_tab.tab_id}, {"is_hidden": new_is_hidden_setting})
        assert resp.status_code == 204, resp.content
        # reload the course and verify the new visibility setting
        self.reload_course()
        new_tab = CourseTabList.get_tab_by_type(self.course.tabs, tab_type)
        assert new_tab.is_hidden == new_is_hidden_setting
Пример #5
0
    def handle(self, *args, **options):
        """
        Gathers all course keys in the modulestore and updates the course tabs
        if there are any new default course tabs. Else, makes no updates.
        """
        store = modulestore()
        all_course_keys = sorted(
            (course.id for course in store.get_course_summaries()),
            key=
            str  # Different types of CourseKeys can't be compared without this.
        )

        config = BackfillCourseTabsConfig.current()
        start = config.start_index if config.enabled and config.start_index >= 0 else 0
        end = (start +
               config.count) if config.enabled and config.count > 0 else len(
                   all_course_keys)
        course_keys = all_course_keys[start:end]

        logger.info(
            f'{len(all_course_keys)} courses read from modulestore. Processing {start} to {end}.'
        )

        error_keys = []
        for course_key in course_keys:
            try:
                course = store.get_course(course_key, depth=1)
                existing_tabs = {tab.type for tab in course.tabs}
                CourseTabList.initialize_default(course)
                new_tabs = {tab.type for tab in course.tabs}

                if existing_tabs != new_tabs:
                    # This will trigger the Course Published Signal which is necessary to update
                    # the corresponding Course Overview
                    logger.info(f'Updating tabs for {course_key}.')
                    store.update_item(course,
                                      ModuleStoreEnum.UserID.mgmt_command)
                    logger.info(f'Successfully updated tabs for {course_key}.')
            except Exception as err:  # pylint: disable=broad-except
                logger.exception(err)
                logger.error(
                    f'Course {course_key} encountered an Exception while trying to update.'
                )
                error_keys.append(course_key)

        if error_keys:
            msg = 'The following courses encountered errors and were not updated:\n'
            for error_key in error_keys:
                msg += f' - {error_key}\n'
            logger.info(msg)
Пример #6
0
def reorder_tabs_handler(course_item, request):
    """
    Helper function for handling reorder of tabs request
    """

    # Tabs are identified by tab_id or locators.
    # The locators are used to identify static tabs since they are xmodules.
    # Although all tabs have tab_ids, newly created static tabs do not know
    # their tab_ids since the xmodule editor uses only locators to identify new objects.
    requested_tab_id_locators = request.json['tabs']

    # original tab list in original order
    old_tab_list = course_item.tabs

    # create a new list in the new order
    new_tab_list = []
    for tab_id_locator in requested_tab_id_locators:
        tab = get_tab_by_tab_id_locator(old_tab_list, tab_id_locator)
        if tab is None:
            return JsonResponse(
                {
                    "error":
                    u"Tab with id_locator '{0}' does not exist.".format(
                        tab_id_locator)
                },
                status=400)
        new_tab_list.append(tab)

    # the old_tab_list may contain additional tabs that were not rendered in the UI because of
    # global or course settings.  so add those to the end of the list.
    non_displayed_tabs = set(old_tab_list) - set(new_tab_list)
    new_tab_list.extend(non_displayed_tabs)

    # validate the tabs to make sure everything is Ok (e.g., did the client try to reorder unmovable tabs?)
    try:
        CourseTabList.validate_tabs(new_tab_list)
    except InvalidTabsException as exception:
        return JsonResponse(
            {
                "error":
                u"New list of tabs is not valid: {0}.".format(str(exception))
            },
            status=400)

    # persist the new order of the tabs
    course_item.tabs = new_tab_list
    modulestore().update_item(course_item, request.user.id)

    return JsonResponse()
Пример #7
0
def static_tab(request, course_id, tab_slug):
    """
    Display the courses tab with the given name.

    Assumes the course_id is in a valid format.
    """

    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)

    course = get_course_with_access(request.user, 'load', course_key)

    tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug)
    if tab is None:
        raise Http404

    contents = get_static_tab_contents(
        request,
        course,
        tab
    )
    if contents is None:
        raise Http404

    return render_to_response('courseware/static_tab.html', {
        'course': course,
        'tab': tab,
        'tab_contents': contents,
    })
Пример #8
0
def get_course_tab_list(course, user):
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
    user_is_enrolled = user.is_authenticated(
    ) and CourseEnrollment.is_enrolled(user, course.id)
    xmodule_tab_list = CourseTabList.iterate_displayable(
        course, settings, user.is_authenticated(),
        has_access(user, 'staff', course, course.id), user_is_enrolled)

    # Now that we've loaded the tabs for this course, perform the Entrance Exam work
    # If the user has to take an entrance exam, we'll need to hide away all of the tabs
    # except for the Courseware and Instructor tabs (latter is only viewed if applicable)
    # We don't have access to the true request object in this context, but we can use a mock
    request = RequestFactory().request()
    request.user = user
    course_tab_list = []
    for tab in xmodule_tab_list:
        if user_must_complete_entrance_exam(request, user, course):
            # Hide all of the tabs except for 'Courseware' and 'Instructor'
            # Rename 'Courseware' tab to 'Entrance Exam'
            if tab.type not in ['courseware', 'instructor']:
                continue
            if tab.type == 'courseware':
                tab.name = _("Entrance Exam")
        course_tab_list.append(tab)
    return course_tab_list
Пример #9
0
    def update_item(self, xblock, user_id=None, allow_not_found=False, force=False):
        """
        Update the persisted version of xblock to reflect its current values.

        xblock: which xblock to persist
        user_id: who made the change (ignored for now by this modulestore)
        allow_not_found: whether to create a new object if one didn't already exist or give an error
        force: force is meaningless for this modulestore
        """
        try:
            definition_data = self._convert_reference_fields_to_strings(xblock, xblock.get_explicitly_set_fields_by_scope())
            payload = {
                'definition.data': definition_data,
                'metadata': self._convert_reference_fields_to_strings(xblock, own_metadata(xblock)),
            }
            if xblock.has_children:
                children = self._convert_reference_fields_to_strings(xblock, {'children': xblock.children})
                payload.update({'definition.children': children['children']})
            self._update_single_item(xblock.scope_ids.usage_id, payload)
            # for static tabs, their containing course also records their display name
            if xblock.scope_ids.block_type == 'static_tab':
                course = self._get_course_for_item(xblock.scope_ids.usage_id)
                # find the course's reference to this tab and update the name.
                static_tab = CourseTabList.get_tab_by_slug(course.tabs, xblock.scope_ids.usage_id.name)
                # only update if changed
                if static_tab and static_tab['name'] != xblock.display_name:
                    static_tab['name'] = xblock.display_name
                    self.update_item(course, user_id)

            # recompute (and update) the metadata inheritance tree which is cached
            self.refresh_cached_metadata_inheritance_tree(xblock.scope_ids.usage_id.course_key, xblock.runtime)
            # fire signal that we've written to DB
        except ItemNotFoundError:
            if not allow_not_found:
                raise
Пример #10
0
def get_course_tab_list(request, course):
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
    user = request.user
    is_user_enrolled = user.is_authenticated() and CourseEnrollment.is_enrolled(user, course.id)
    xmodule_tab_list = CourseTabList.iterate_displayable(
        course,
        user=user,
        settings=settings,
        is_user_authenticated=user.is_authenticated(),
        is_user_staff=has_access(user, 'staff', course, course.id),
        is_user_enrolled=is_user_enrolled,
        is_user_sneakpeek=not UserProfile.has_registered(user),
    )

    # Now that we've loaded the tabs for this course, perform the Entrance Exam work.
    # If the user has to take an entrance exam, we'll need to hide away all but the
    # "Courseware" tab. The tab is then renamed as "Entrance Exam".
    course_tab_list = []
    must_complete_ee = user_must_complete_entrance_exam(request, user, course)
    for tab in xmodule_tab_list:
        if must_complete_ee:
            # Hide all of the tabs except for 'Courseware'
            # Rename 'Courseware' tab to 'Entrance Exam'
            if tab.type is not 'courseware':
                continue
            tab.name = _("Entrance Exam")
        course_tab_list.append(tab)

    # Add in any dynamic tabs, i.e. those that are not persisted
    course_tab_list += _get_dynamic_tabs(course, user)
    return course_tab_list
Пример #11
0
def get_course_tab_list(request, course):
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
    user = request.user
    xmodule_tab_list = CourseTabList.iterate_displayable(course, user=user)

    # Now that we've loaded the tabs for this course, perform the Entrance Exam work.
    # If the user has to take an entrance exam, we'll need to hide away all but the
    # "Courseware" tab. The tab is then renamed as "Entrance Exam".
    course_tab_list = []
    must_complete_ee = user_must_complete_entrance_exam(request, user, course)
    for tab in xmodule_tab_list:
        # Rename 'Home' tab to 'Information'
        if tab.type is 'course_info':
            tab.name = _("Information")
        if must_complete_ee:
            # Hide all of the tabs except for 'Courseware'
            # Rename 'Courseware' tab to 'Entrance Exam'
            if tab.type is not 'courseware':
                continue
            tab.name = _("Entrance Exam")
        course_tab_list.append(tab)

    # Add in any dynamic tabs, i.e. those that are not persisted
    course_tab_list += _get_dynamic_tabs(course, user)

    # Add course welcome tab if feature is enabled
    if settings.FEATURES.get('TMA_ENABLE_COURSE_WELCOME_PAGE'
                             ) and CourseWelcomeTab.is_enabled(course, user):
        course_tab_list.insert(0, CourseWelcomeTab({}))

    return course_tab_list
Пример #12
0
def static_tab(request, course_id, tab_slug):
    """
    Display the courses tab with the given name.

    Assumes the course_id is in a valid format.
    """
    try:
        course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    except InvalidKeyError:
        raise Http404

    course = get_course_with_access(request.user, 'load', course_key)

    tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug)
    if tab is None:
        raise Http404

    contents = get_static_tab_contents(
        request,
        course,
        tab
    )
    if contents is None:
        raise Http404

    return render_to_response('courseware/static_tab.html', {
        'course': course,
        'tab': tab,
        'tab_contents': contents,
    })
Пример #13
0
def get_course_tab_list(request, course):
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
    user = request.user
    xmodule_tab_list = CourseTabList.iterate_displayable(course, user=user)

    # Now that we've loaded the tabs for this course, perform the Entrance Exam work.
    # If the user has to take an entrance exam, we'll need to hide away all but the
    # "Courseware" tab. The tab is then renamed as "Entrance Exam".
    course_tab_list = []
    must_complete_ee = user_must_complete_entrance_exam(request, user, course)
    for tab in xmodule_tab_list:
        if must_complete_ee:
            # Hide all of the tabs except for 'Courseware'
            # Rename 'Courseware' tab to 'Entrance Exam'
            if tab.type != 'courseware':
                continue
            tab.name = _("Entrance Exam")
        if tab.type == 'static_tab' and tab.course_staff_only and \
                not bool(user and has_access(user, 'staff', course, course.id)):
            continue
        course_tab_list.append(tab)

    # Add in any dynamic tabs, i.e. those that are not persisted
    course_tab_list += _get_dynamic_tabs(course, user)
    return course_tab_list
Пример #14
0
    def update_item(self, xblock, user_id=None, allow_not_found=False, force=False):
        """
        Update the persisted version of xblock to reflect its current values.

        xblock: which xblock to persist
        user_id: who made the change (ignored for now by this modulestore)
        allow_not_found: whether to create a new object if one didn't already exist or give an error
        force: force is meaningless for this modulestore
        """
        try:
            definition_data = self._convert_reference_fields_to_strings(xblock, xblock.get_explicitly_set_fields_by_scope())
            payload = {
                'definition.data': definition_data,
                'metadata': self._convert_reference_fields_to_strings(xblock, own_metadata(xblock)),
            }
            if xblock.has_children:
                children = self._convert_reference_fields_to_strings(xblock, {'children': xblock.children})
                payload.update({'definition.children': children['children']})
            self._update_single_item(xblock.scope_ids.usage_id, payload)
            # for static tabs, their containing course also records their display name
            if xblock.scope_ids.block_type == 'static_tab':
                course = self._get_course_for_item(xblock.scope_ids.usage_id)
                # find the course's reference to this tab and update the name.
                static_tab = CourseTabList.get_tab_by_slug(course.tabs, xblock.scope_ids.usage_id.name)
                # only update if changed
                if static_tab and static_tab['name'] != xblock.display_name:
                    static_tab['name'] = xblock.display_name
                    self.update_item(course, user_id)

            # recompute (and update) the metadata inheritance tree which is cached
            self.refresh_cached_metadata_inheritance_tree(xblock.scope_ids.usage_id.course_key, xblock.runtime)
            # fire signal that we've written to DB
        except ItemNotFoundError:
            if not allow_not_found:
                raise
Пример #15
0
def static_tab(request, course_id, tab_slug):
    """
    Display the courses tab with the given name.

    Assumes the course_id is in a valid format.
    """
    course = get_course_with_access(request.user, course_id, 'load')

    tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug)
    if tab is None:
        raise Http404

    contents = get_static_tab_contents(
        request,
        course,
        tab
    )
    if contents is None:
        raise Http404

    return render_to_response('courseware/static_tab.html', {
        'course': course,
        'tab': tab,
        'tab_contents': contents,
    })
Пример #16
0
def get_course_tab_list(course, user):
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
    user_is_enrolled = user.is_authenticated() and CourseEnrollment.is_enrolled(user, course.id)
    xmodule_tab_list = CourseTabList.iterate_displayable(
        course,
        settings,
        user.is_authenticated(),
        has_access(user, 'staff', course, course.id),
        user_is_enrolled
    )

    # Now that we've loaded the tabs for this course, perform the Entrance Exam work
    # If the user has to take an entrance exam, we'll need to hide away all of the tabs
    # except for the Courseware and Instructor tabs (latter is only viewed if applicable)
    # We don't have access to the true request object in this context, but we can use a mock
    request = RequestFactory().request()
    request.user = user
    course_tab_list = []
    for tab in xmodule_tab_list:
        if user_must_complete_entrance_exam(request, user, course):
            # Hide all of the tabs except for 'Courseware' and 'Instructor'
            # Rename 'Courseware' tab to 'Entrance Exam'
            if tab.type not in ['courseware', 'instructor']:
                continue
            if tab.type == 'courseware':
                tab.name = _("Entrance Exam")
        course_tab_list.append(tab)
    return course_tab_list
Пример #17
0
def get_course_tab_list(user, course):
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
    xmodule_tab_list = CourseTabList.iterate_displayable(course, user=user)

    # Now that we've loaded the tabs for this course, perform the Entrance Exam work.
    # If the user has to take an entrance exam, we'll need to hide away all but the
    # "Courseware" tab. The tab is then renamed as "Entrance Exam".
    course_tab_list = []
    must_complete_ee = not user_can_skip_entrance_exam(user, course)
    for tab in xmodule_tab_list:
        if must_complete_ee:
            # Hide all of the tabs except for 'Courseware'
            # Rename 'Courseware' tab to 'Entrance Exam'
            if tab.type != 'courseware':
                continue
            tab.name = _("Entrance Exam")
        # TODO: LEARNER-611 - once the course_info tab is removed, remove this code
        if UNIFIED_COURSE_TAB_FLAG.is_enabled(course.id) and tab.type == 'course_info':
            continue
        if tab.type == 'static_tab' and tab.course_staff_only and \
                not bool(user and has_access(user, 'staff', course, course.id)):
            continue
        # We had initially created a CourseTab.load() for dates that ended up
        # persisting the dates tab tomodulestore on Course Run creation, but
        # ignoring any static dates tab here we can fix forward without
        # allowing the bug to continue to surface
        if tab.type == 'dates':
            continue
        course_tab_list.append(tab)

    # Add in any dynamic tabs, i.e. those that are not persisted
    course_tab_list += _get_dynamic_tabs(course, user)
    return course_tab_list
Пример #18
0
    def _process_course_fields(self, fields):
        """ Returns a validated list of course fields """
        all_fields = list(CourseFields.__dict__.keys())
        non_course_fields = [
            "__doc__",
            "__module__",
            "__weakref__",
            "__dict__"
        ]
        for field in non_course_fields:
            all_fields.remove(field)

        # Non-primitive course fields
        date_fields = [
            "certificate_available_date",
            "announcement",
            "enrollment_start",
            "enrollment_end",
            "start",
            "end"
        ]
        course_tab_list_fields = [
            "tabs"
        ]

        for field in dict(fields):
            if field not in all_fields:
                # field does not exist as a CourseField
                del fields[field]
                logger.info(field + "is not a valid CourseField")  # lint-amnesty, pylint: disable=logging-not-lazy
            elif fields[field] is None:
                # field is unset
                del fields[field]
            elif field in date_fields:
                # Generate Date object from the json value
                try:
                    date_json = fields[field]
                    fields[field] = Date().from_json(date_json)
                    logger.info(field + " has been set to " + date_json)
                except Exception:  # pylint: disable=broad-except
                    logger.info("The date string could not be parsed for " + field)  # lint-amnesty, pylint: disable=logging-not-lazy
                    del fields[field]
            elif field in course_tab_list_fields:
                # Generate CourseTabList object from the json value
                try:
                    course_tab_list_json = fields[field]
                    fields[field] = CourseTabList().from_json(course_tab_list_json)
                    logger.info(field + " has been set to " + course_tab_list_json)
                except Exception:  # pylint: disable=broad-except
                    logger.info("The course tab list string could not be parsed for " + field)  # lint-amnesty, pylint: disable=logging-not-lazy
                    del fields[field]
            else:
                # CourseField is valid and has been set
                logger.info(field + " has been set to " + str(fields[field]))  # lint-amnesty, pylint: disable=logging-not-lazy

        for field in all_fields:
            if field not in fields:
                logger.info(field + " has not been set")  # lint-amnesty, pylint: disable=logging-not-lazy
        return fields
Пример #19
0
def get_tab_by_locator(tab_list, usage_key_string):
    """
    Look for a tab with the specified locator.  Returns the first matching tab.
    """
    tab_location = UsageKey.from_string(usage_key_string)
    item = modulestore().get_item(tab_location)
    static_tab = StaticTab(name=item.display_name, url_slug=item.location.name)
    return CourseTabList.get_tab_by_id(tab_list, static_tab.tab_id)
Пример #20
0
def tabs_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None):
    """
    The restful handler for static tabs.

    GET
        html: return page for editing static tabs
        json: not supported
    PUT or POST
        json: update the tab order. It is expected that the request body contains a JSON-encoded dict with entry "tabs".
        The value for "tabs" is an array of tab locators, indicating the desired order of the tabs.

    Creating a tab, deleting a tab, or changing its contents is not supported through this method.
    Instead use the general xblock URL (see item.xblock_handler).
    """
    locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
    if not has_course_access(request.user, locator):
        raise PermissionDenied()

    old_location = loc_mapper().translate_locator_to_location(locator)
    store = get_modulestore(old_location)
    course_item = store.get_item(old_location)

    if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        if request.method == 'GET':
            raise NotImplementedError('coming soon')
        else:
            if 'tabs' in request.json:
                return reorder_tabs_handler(course_item, request)
            elif 'tab_id_locator' in request.json:
                return edit_tab_handler(course_item, request)
            else:
                raise NotImplementedError('Creating or changing tab content is not supported.')

    elif request.method == 'GET':  # assume html
        # get all tabs from the tabs list: static tabs (a.k.a. user-created tabs) and built-in tabs
        # present in the same order they are displayed in LMS

        tabs_to_render = []
        for tab in CourseTabList.iterate_displayable_cms(
                course_item,
                settings,
        ):
            if isinstance(tab, StaticTab):
                # static tab needs its locator information to render itself as an xmodule
                static_tab_loc = old_location.replace(category='static_tab', name=tab.url_slug)
                tab.locator = loc_mapper().translate_location(
                    course_item.location.course_id, static_tab_loc, False, True
                )
            tabs_to_render.append(tab)

        return render_to_response('edit-tabs.html', {
            'context_course': course_item,
            'tabs_to_render': tabs_to_render,
            'course_locator': locator,
            'lms_link': get_lms_link_for_item(course_item.location),
        })
    else:
        return HttpResponseNotFound()
Пример #21
0
def tabs_handler(request, course_key_string):
    """
    The restful handler for static tabs.

    GET
        html: return page for editing static tabs
        json: not supported
    PUT or POST
        json: update the tab order. It is expected that the request body contains a JSON-encoded dict with entry "tabs".
        The value for "tabs" is an array of tab locators, indicating the desired order of the tabs.

    Creating a tab, deleting a tab, or changing its contents is not supported through this method.
    Instead use the general xblock URL (see item.xblock_handler).
    """
    course_key = CourseKey.from_string(course_key_string)
    if not has_course_author_access(request.user, course_key):
        raise PermissionDenied()

    course_item = modulestore().get_course(course_key)

    if 'application/json' in request.META.get('HTTP_ACCEPT',
                                              'application/json'):
        if request.method == 'GET':
            raise NotImplementedError('coming soon')
        else:
            if 'tabs' in request.json:
                return reorder_tabs_handler(course_item, request)
            elif 'tab_id_locator' in request.json:
                return edit_tab_handler(course_item, request)
            else:
                raise NotImplementedError(
                    'Creating or changing tab content is not supported.')

    elif request.method == 'GET':  # assume html
        # get all tabs from the tabs list: static tabs (a.k.a. user-created tabs) and built-in tabs
        # present in the same order they are displayed in LMS

        tabs_to_render = []
        for tab in CourseTabList.iterate_displayable(course_item,
                                                     user=request.user,
                                                     inline_collections=False,
                                                     include_hidden=True):
            if isinstance(tab, StaticTab):
                # static tab needs its locator information to render itself as an xmodule
                static_tab_loc = course_key.make_usage_key(
                    'static_tab', tab.url_slug)
                tab.locator = static_tab_loc
            tabs_to_render.append(tab)

        return render_to_response(
            'edit-tabs.html', {
                'context_course': course_item,
                'tabs_to_render': tabs_to_render,
                'lms_link': get_lms_link_for_item(course_item.location),
            })
    else:
        return HttpResponseNotFound()
Пример #22
0
def get_tab_by_tab_id_locator(tab_list, tab_id_locator):
    """
    Look for a tab with the specified tab_id or locator.  Returns the first matching tab.
    """
    if 'tab_id' in tab_id_locator:
        tab = CourseTabList.get_tab_by_id(tab_list, tab_id_locator['tab_id'])
    elif 'tab_locator' in tab_id_locator:
        tab = get_tab_by_locator(tab_list, tab_id_locator['tab_locator'])
    return tab
Пример #23
0
def get_tab_by_tab_id_locator(tab_list, tab_id_locator):
    """
    Look for a tab with the specified tab_id or locator.  Returns the first matching tab.
    """
    if 'tab_id' in tab_id_locator:
        tab = CourseTabList.get_tab_by_id(tab_list, tab_id_locator['tab_id'])
    elif 'tab_locator' in tab_id_locator:
        tab = get_tab_by_locator(tab_list, tab_id_locator['tab_locator'])
    return tab
Пример #24
0
    def __init__(self, *args, **kwargs):
        """
        Expects the same arguments as XModuleDescriptor.__init__
        """
        super(CourseDescriptor, self).__init__(*args, **kwargs)
        _ = self.runtime.service(self, "i18n").ugettext

        if self.wiki_slug is None:
            if isinstance(self.location, Location):
                self.wiki_slug = self.location.course
            elif isinstance(self.location, CourseLocator):
                self.wiki_slug = self.location.package_id or self.display_name

        if self.due_date_display_format is None and self.show_timezone is False:
            # For existing courses with show_timezone set to False (and no due_date_display_format specified),
            # set the due_date_display_format to what would have been shown previously (with no timezone).
            # Then remove show_timezone so that if the user clears out the due_date_display_format,
            # they get the default date display.
            self.due_date_display_format = "DATE_TIME"
            delattr(self, 'show_timezone')

        # NOTE: relies on the modulestore to call set_grading_policy() right after
        # init.  (Modulestore is in charge of figuring out where to load the policy from)

        # NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
        #   disable the syllabus content for courses that do not provide a syllabus
        if self.system.resources_fs is None:
            self.syllabus_present = False
        else:
            self.syllabus_present = self.system.resources_fs.exists(
                path('syllabus'))

        self._grading_policy = {}
        self.set_grading_policy(self.grading_policy)

        if self.discussion_topics == {}:
            self.discussion_topics = {
                _('General'): {
                    'id': self.location.html_id()
                }
            }

        if not getattr(self, "tabs", []):
            CourseTabList.initialize_default(self)
Пример #25
0
def get_tab_by_locator(tab_list, tab_locator):
    """
    Look for a tab with the specified locator.  Returns the first matching tab.
    """
    tab_location = loc_mapper().translate_locator_to_location(BlockUsageLocator(tab_locator))
    item = modulestore('direct').get_item(tab_location)
    static_tab = StaticTab(
        name=item.display_name,
        url_slug=item.location.name,
    )
    return CourseTabList.get_tab_by_id(tab_list, static_tab.tab_id)
Пример #26
0
    def check_toggle_tab_visiblity(self, tab_type, new_is_hidden_setting):
        """Helper method to check changes in tab visibility"""

        # find the tab
        old_tab = CourseTabList.get_tab_by_type(self.course.tabs, tab_type)

        # visibility should be different from new setting
        self.assertNotEqual(old_tab.is_hidden, new_is_hidden_setting)

        # post the request
        resp = self.client.ajax_post(
            self.url,
            data=json.dumps({"tab_id_locator": {"tab_id": old_tab.tab_id}, "is_hidden": new_is_hidden_setting}),
        )
        self.assertEqual(resp.status_code, 204)

        # reload the course and verify the new visibility setting
        self.reload_course()
        new_tab = CourseTabList.get_tab_by_type(self.course.tabs, tab_type)
        self.assertEqual(new_tab.is_hidden, new_is_hidden_setting)
Пример #27
0
def get_course_tab_by_type(request, course, tab_type):
    """
    Look for a tab with the specified type.   Returns the first matching tab.

    Note: This looks in the dynamic and non-dynamic tabs.
    """
    tab = CourseTabList.get_tab_by_type(course.tabs, tab_type)
    if tab is None:
        tab = next((tab for tab in _get_dynamic_tabs(course, request.user)
                    if tab.type == tab_type), None)
    return tab
Пример #28
0
def get_tab_by_locator(tab_list, usage_key_string):
    """
    Look for a tab with the specified locator.  Returns the first matching tab.
    """
    tab_location = UsageKey.from_string(usage_key_string)
    item = modulestore().get_item(tab_location)
    static_tab = StaticTab(
        name=item.display_name,
        url_slug=item.location.name,
    )
    return CourseTabList.get_tab_by_id(tab_list, static_tab.tab_id)
Пример #29
0
def reorder_tabs_handler(course_item, tabs_data, user):
    """
    Helper function for handling reorder of static tabs request
    """

    # Tabs are identified by tab_id or locators.
    # The locators are used to identify static tabs since they are xmodules.
    # Although all tabs have tab_ids, newly created static tabs do not know
    # their tab_ids since the xmodule editor uses only locators to identify new objects.
    requested_tab_id_locators = tabs_data["tabs"]

    #get original tab list of only static tabs with their original index(position) in the full course tabs list
    old_tab_dict = {}
    for idx, tab in enumerate(course_item.tabs):
        if isinstance(tab, StaticTab):
            old_tab_dict[tab] = idx
    old_tab_list = list(old_tab_dict.keys())

    new_tab_list = create_new_list(requested_tab_id_locators, old_tab_list)

    # Creates a full new course tab list of both default and static course tabs
    # by looping through the new tab list of static only tabs and
    # putting them in their new position in the list of course item tabs
    # original_idx gives the list of positions of all static tabs in course tabs originally
    full_new_tab_list = course_item.tabs
    original_idx = list(old_tab_dict.values())
    for i in range(len(new_tab_list)):
        full_new_tab_list[original_idx[i]] = new_tab_list[i]

    # validate the tabs to make sure everything is Ok (e.g., did the client try to reorder unmovable tabs?)
    try:
        CourseTabList.validate_tabs(full_new_tab_list)
    except InvalidTabsException as exception:
        raise ValidationError({
            "error":
            f"New list of tabs is not valid: {str(exception)}."
        }) from exception

    # persist the new order of the tabs
    course_item.tabs = full_new_tab_list
    modulestore().update_item(course_item, user.id)
Пример #30
0
    def static_updater(self, course, source_courselike, courselike_key, dest_id, runtime):
        """
        Update special static assets, such as PDF textbooks and wiki resources.
        """
        for entry in course.pdf_textbooks:
            for chapter in entry.get('chapters', []):
                if StaticContent.is_c4x_path(chapter.get('url', '')):
                    asset_key = StaticContent.get_location_from_path(chapter['url'])
                    chapter['url'] = StaticContent.get_static_path_from_location(asset_key)

        # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
        # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
        # values then remap it so that the wiki does not point to the old wiki.
        if courselike_key != course.id:
            if self.xml_module_store.course_run is None:
                original_course_run = courselike_key.run
            else:
                original_course_run = self.xml_module_store.course_run

            original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                courselike_key.org,
                courselike_key.course,
                original_course_run,
            )
            if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == courselike_key.course:
                course.wiki_slug = u'{0}.{1}.{2}'.format(
                    course.id.org,
                    course.id.course,
                    course.id.run,
                )

        # cdodge: more hacks (what else). Seems like we have a
        # problem when importing a course (like 6.002) which
        # does not have any tabs defined in the policy file.
        # The import goes fine and then displays fine in LMS,
        # but if someone tries to add a new tab in the CMS, then
        # the LMS barfs because it expects that -- if there are
        # *any* tabs -- then there at least needs to be
        # some predefined ones
        if course.tabs is None or len(course.tabs) == 0:
            CourseTabList.initialize_default(course)
Пример #31
0
def get_tab_by_tab_id_locator(
        tab_list: List[CourseTab],
        tab_id_locator: Dict[str, str]) -> Optional[CourseTab]:
    """
    Look for a tab with the specified tab_id or locator.  Returns the first matching tab.
    """
    tab = None
    if "tab_id" in tab_id_locator:
        tab = CourseTabList.get_tab_by_id(tab_list, tab_id_locator["tab_id"])
    elif "tab_locator" in tab_id_locator:
        tab = get_tab_by_locator(tab_list, tab_id_locator["tab_locator"])
    return tab
Пример #32
0
def tabs_handler(request, course_key_string):
    """
    The restful handler for static tabs.

    GET
        html: return page for editing static tabs
        json: not supported
    PUT or POST
        json: update the tab order. It is expected that the request body contains a JSON-encoded dict with entry "tabs".
        The value for "tabs" is an array of tab locators, indicating the desired order of the tabs.

    Creating a tab, deleting a tab, or changing its contents is not supported through this method.
    Instead use the general xblock URL (see item.xblock_handler).
    """
    course_key = CourseKey.from_string(course_key_string)
    if not has_course_access(request.user, course_key):
        raise PermissionDenied()

    course_item = modulestore().get_course(course_key)

    if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        if request.method == 'GET':
            raise NotImplementedError('coming soon')
        else:
            if 'tabs' in request.json:
                return reorder_tabs_handler(course_item, request)
            elif 'tab_id_locator' in request.json:
                return edit_tab_handler(course_item, request)
            else:
                raise NotImplementedError('Creating or changing tab content is not supported.')

    elif request.method == 'GET':  # assume html
        # get all tabs from the tabs list: static tabs (a.k.a. user-created tabs) and built-in tabs
        # present in the same order they are displayed in LMS

        tabs_to_render = []
        for tab in CourseTabList.iterate_displayable_cms(
                course_item,
                settings,
        ):
            if isinstance(tab, StaticTab):
                # static tab needs its locator information to render itself as an xmodule
                static_tab_loc = course_key.make_usage_key('static_tab', tab.url_slug)
                tab.locator = static_tab_loc
            tabs_to_render.append(tab)

        return render_to_response('edit-tabs.html', {
            'context_course': course_item,
            'tabs_to_render': tabs_to_render,
            'lms_link': get_lms_link_for_item(course_item.location),
        })
    else:
        return HttpResponseNotFound()
Пример #33
0
def get_tab_by_locator(tab_list, tab_locator):
    """
    Look for a tab with the specified locator.  Returns the first matching tab.
    """
    tab_location = loc_mapper().translate_locator_to_location(
        BlockUsageLocator(tab_locator))
    item = modulestore('direct').get_item(tab_location)
    static_tab = StaticTab(
        name=item.display_name,
        url_slug=item.location.name,
    )
    return CourseTabList.get_tab_by_id(tab_list, static_tab.tab_id)
Пример #34
0
    def __init__(self, *args, **kwargs):
        """
        Expects the same arguments as XModuleDescriptor.__init__
        """
        super(CourseDescriptor, self).__init__(*args, **kwargs)
        _ = self.runtime.service(self, "i18n").ugettext

        if self.wiki_slug is None:
            if isinstance(self.location, Location):
                self.wiki_slug = self.location.course
            elif isinstance(self.location, CourseLocator):
                self.wiki_slug = self.location.package_id or self.display_name

        if self.due_date_display_format is None and self.show_timezone is False:
            # For existing courses with show_timezone set to False (and no due_date_display_format specified),
            # set the due_date_display_format to what would have been shown previously (with no timezone).
            # Then remove show_timezone so that if the user clears out the due_date_display_format,
            # they get the default date display.
            self.due_date_display_format = "DATE_TIME"
            delattr(self, 'show_timezone')

        # NOTE: relies on the modulestore to call set_grading_policy() right after
        # init.  (Modulestore is in charge of figuring out where to load the policy from)

        # NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
        #   disable the syllabus content for courses that do not provide a syllabus
        if self.system.resources_fs is None:
            self.syllabus_present = False
        else:
            self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))

        self._grading_policy = {}
        self.set_grading_policy(self.grading_policy)

        if self.discussion_topics == {}:
            self.discussion_topics = {_('General'): {'id': self.location.html_id()}}

        if not getattr(self, "tabs", []):
            CourseTabList.initialize_default(self)
Пример #35
0
    def test_toggle_tab_visibility_fail(self):
        """
        Test that it isn't possible to toggle visibility of unsupported tabs
        """

        tab_type = "courseware"

        tab = CourseTabList.get_tab_by_type(self.course.tabs, tab_type)

        assert not tab.is_hideable
        assert not tab.is_hidden

        resp = self.make_update_tab_request({"tab_id": tab.tab_id}, {"is_hidden": True})

        assert resp.status_code == 400
        error = self.check_invalid_response(resp)
        assert error["error"] == f"Tab of type {tab_type} can not be hidden"

        # Make sure the visibility wasn't affected
        self.reload_course()
        updated_tab = CourseTabList.get_tab_by_type(self.course.tabs, tab_type)
        assert not updated_tab.is_hidden
Пример #36
0
def reorder_tabs_handler(course_item, tabs_data, user):
    """
    Helper function for handling reorder of static tabs request
    """

    # Static tabs are identified by locators (a UsageKey) instead of a tab id like
    # other tabs. These can be used to identify static tabs since they are xmodules.
    # Although all tabs have tab_ids, newly created static tabs do not know
    # their tab_ids since the xmodule editor uses only locators to identify new objects.
    new_tab_list = create_new_list(tabs_data, course_item.tabs)

    # validate the tabs to make sure everything is Ok (e.g., did the client try to reorder unmovable tabs?)
    try:
        CourseTabList.validate_tabs(new_tab_list)
    except InvalidTabsException as exception:
        raise ValidationError({
            "error":
            f"New list of tabs is not valid: {str(exception)}."
        }) from exception

    course_item.tabs = new_tab_list

    modulestore().update_item(course_item, user.id)
Пример #37
0
 def set_enabled(cls, course_key: CourseKey, enabled: bool, user: '******') -> bool:
     """
     Enable/disable edxnotes in the modulestore.
     """
     course = get_course_by_id(course_key)
     course.edxnotes = enabled
     if enabled:
         notes_tab = CourseTabList.get_tab_by_id(course.tabs, 'edxnotes')
         if notes_tab is None:
             # If the course doesn't already have the notes tab, add it.
             notes_tab = CourseTab.load("edxnotes")
             course.tabs.append(notes_tab)
     modulestore().update_item(course, user.id)
     return enabled
Пример #38
0
def get_tab_by_locator(
        tab_list: List[CourseTab],
        tab_location: Union[str, UsageKey]) -> Optional[CourseTab]:
    """
    Look for a tab with the specified locator.  Returns the first matching tab.
    """
    if isinstance(tab_location, str):
        tab_location = UsageKey.from_string(tab_location)
    item = modulestore().get_item(tab_location)
    static_tab = StaticTab(
        name=item.display_name,
        url_slug=item.location.name,
    )
    return CourseTabList.get_tab_by_id(tab_list, static_tab.tab_id)
Пример #39
0
    def static_updater(self, course, source_courselike, courselike_key,
                       dest_id, runtime):
        """
        Update special static assets, such as PDF textbooks and wiki resources.
        """
        for entry in course.pdf_textbooks:
            for chapter in entry.get('chapters', []):
                if StaticContent.is_c4x_path(chapter.get('url', '')):
                    asset_key = StaticContent.get_location_from_path(
                        chapter['url'])
                    chapter[
                        'url'] = StaticContent.get_static_path_from_location(
                            asset_key)

        # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
        # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
        # values then remap it so that the wiki does not point to the old wiki.
        if courselike_key != course.id:
            original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                courselike_key.org, courselike_key.course, courselike_key.run)
            if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == courselike_key.course:
                course.wiki_slug = u'{0}.{1}.{2}'.format(
                    course.id.org,
                    course.id.course,
                    course.id.run,
                )

        # cdodge: more hacks (what else). Seems like we have a
        # problem when importing a course (like 6.002) which
        # does not have any tabs defined in the policy file.
        # The import goes fine and then displays fine in LMS,
        # but if someone tries to add a new tab in the CMS, then
        # the LMS barfs because it expects that -- if there are
        # *any* tabs -- then there at least needs to be
        # some predefined ones
        if course.tabs is None or len(course.tabs) == 0:
            CourseTabList.initialize_default(course)
Пример #40
0
def redirect_view(request, course_key_string=None):
    course_key = CourseKey.from_string(course_key_string)
    if not has_course_author_access(request.user, course_key):
        raise PermissionDenied()

    course_item = modulestore().get_course(course_key)

    if request.method == 'GET':
        for tab in CourseTabList.iterate_displayable(course_item,
                                                     inline_collections=False):
            if isinstance(tab, ExternalLinkCourseTab):
                data = {'name': tab.name, 'link_value': tab.link_value}
                form = ExternalLinkTabForm(data=data)
                csrf_token = csrf(request)['csrf_token']
                return render_to_response('external_blog.html', {
                    'form': form,
                    'csrf': csrf_token
                })
    elif request.method == 'POST':
        for tab in CourseTabList.iterate_displayable(course_item,
                                                     inline_collections=False):
            if isinstance(tab, ExternalLinkCourseTab):
                data = request.POST
                form = ExternalLinkTabForm(data=data)
                if form.is_valid():
                    tab.link_value = form.cleaned_data.get('link_value')
                    tab.name = form.cleaned_data.get('name')
                    modulestore().update_item(course_item, request.user.id)
                    return HttpResponseRedirect(
                        reverse(
                            'contentstore.views.tabs_handler',
                            kwargs={'course_key_string': course_key_string}))
                csrf_token = csrf(request)['csrf_token']
                return render_to_response('external_blog.html', {
                    'form': form,
                    'csrf': csrf_token
                })
Пример #41
0
    def test_get_static_tab_contents(self):
        course = get_course_by_id(self.toy_course_key)
        request = get_request_for_user(UserFactory.create())
        tab = CourseTabList.get_tab_by_slug(course.tabs, 'resources')

        # Test render works okay
        tab_content = get_static_tab_contents(request, course, tab)
        self.assertIn(self.toy_course_key.to_deprecated_string(), tab_content)
        self.assertIn('static_tab', tab_content)

        # Test when render raises an exception
        with patch('courseware.views.get_module') as mock_module_render:
            mock_module_render.return_value = MagicMock(render=Mock(
                side_effect=Exception('Render failed!')))
            static_tab = get_static_tab_contents(request, course, tab)
            self.assertIn("this module is temporarily unavailable", static_tab)
Пример #42
0
 def set_enabled(cls, course_key: CourseKey, enabled: bool,
                 user: '******') -> bool:
     """
     Enabled/disables the wiki tab in the course.
     """
     course = get_course_by_id(course_key)
     wiki_tab = CourseTabList.get_tab_by_id(course.tabs, 'wiki')
     if wiki_tab is None:
         if not enabled:
             return False
         # If the course doesn't already have the wiki tab, add it.
         wiki_tab = CourseTab.load("wiki")
         course.tabs.append(wiki_tab)
     wiki_tab.is_hidden = not enabled
     modulestore().update_item(course, user.id)
     return enabled
Пример #43
0
def calendar_edit(request, course_id):
    url = request.POST.get('url', '')
    message = request.POST.get('message', '')

    course_key = CourseKey.from_string(course_id)
    course = get_course_with_access(request.user, "load", course_key)

    # Find the given tab in the course
    tab = CourseTabList.get_tab_by_id(course.tabs, "calendar_tab")

    if tab is None or not bool(has_access(request.user, 'staff', course)):
        raise Http404("Tab with id_locator calendar_tab does not exist.")

    data = {'url': url, 'message': message}
    tab.data = json.dumps(data)
    modulestore().update_item(course, request.user.id)
    return redirect('calendar_dashboard', course_id=course.id)
Пример #44
0
    def test_get_static_tab_contents(self):
        course = get_course_by_id('edX/toy/2012_Fall')
        request = get_request_for_user(UserFactory.create())
        tab = CourseTabList.get_tab_by_slug(course, 'resources')

        # Test render works okay
        tab_content = get_static_tab_contents(request, course, tab)
        self.assertIn('edX/toy/2012_Fall', tab_content)
        self.assertIn('static_tab', tab_content)

        # Test when render raises an exception
        with patch('courseware.views.get_module') as mock_module_render:
            mock_module_render.return_value = MagicMock(
                render=Mock(side_effect=Exception('Render failed!'))
            )
            static_tab = get_static_tab_contents(request, course, tab)
            self.assertIn("this module is temporarily unavailable", static_tab)
Пример #45
0
def calendar_edit(request, course_id):
    url = request.POST.get('url', '')
    message = request.POST.get('message', '')

    course_key = CourseKey.from_string(course_id)
    course = get_course_with_access(request.user, "load", course_key)

    # Find the given tab in the course
    tab = CourseTabList.get_tab_by_id(course.tabs, "calendar_tab")

    if tab is None or not bool(has_access(request.user, 'staff', course)):
        raise Http404("Tab with id_locator calendar_tab does not exist.")

    data = {'url': url, 'message': message}
    tab.data = json.dumps(data)
    modulestore().update_item(course, request.user.id)
    return redirect('calendar_dashboard', course_id=course.id)
Пример #46
0
def static_tab(request, course_id, tab_slug):
    """
    Display the courses tab with the given name.

    Assumes the course_id is in a valid format.
    """
    course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
    course = get_course_with_access(request.user, "load", course_key)

    tab = CourseTabList.get_tab_by_slug(course.tabs, tab_slug)
    if tab is None:
        raise Http404

    contents = get_static_tab_contents(request, course, tab)
    if contents is None:
        raise Http404

    return render_to_response("courseware/static_tab.html", {"course": course, "tab": tab, "tab_contents": contents})
Пример #47
0
def get_course_tab_list(course, user):
    """
    Retrieves the course tab list from xmodule.tabs and manipulates the set as necessary
    """
    user_is_enrolled = user.is_authenticated() and CourseEnrollment.is_enrolled(user, course.id)
    xmodule_tab_list = CourseTabList.iterate_displayable(
        course,
        settings,
        user.is_authenticated(),
        has_access(user, 'staff', course, course.id),
        user_is_enrolled
    )

    # Entrance Exams Feature
    # If the course has an entrance exam, we'll need to see if the user has not passed it
    # If so, we'll need to hide away all of the tabs except for Courseware and Instructor
    entrance_exam_mode = False
    if settings.FEATURES.get('ENTRANCE_EXAMS', False):
        if getattr(course, 'entrance_exam_enabled', False):
            course_milestones_paths = get_course_milestones_fulfillment_paths(
                unicode(course.id),
                serialize_user(user)
            )
            for __, value in course_milestones_paths.iteritems():
                if len(value.get('content', [])):
                    for content in value['content']:
                        if content == course.entrance_exam_id \
                                and not EntranceExamConfiguration.user_can_skip_entrance_exam(user, course.id):
                            entrance_exam_mode = True
                            break

    # Now that we've loaded the tabs for this course, perform the Entrance Exam mode work
    # Majority case is no entrance exam defined
    course_tab_list = []
    for tab in xmodule_tab_list:
        if entrance_exam_mode:
            # Hide all of the tabs except for 'Courseware' and 'Instructor'
            # Rename 'Courseware' tab to 'Entrance Exam'
            if tab.type not in ['courseware', 'instructor']:
                continue
            if tab.type == 'courseware':
                tab.name = _("Entrance Exam")
        course_tab_list.append(tab)
    return course_tab_list
Пример #48
0
def calendar_dashboard(request, course_id):
    course_key = CourseKey.from_string(course_id)
    course = get_course_with_access(request.user, "load", course_key)
    add_lookup('main', os.path.join(os.path.dirname(os.path.dirname(__file__)), 'calendar_tab/templates'))
    csrf_token = csrf(request)['csrf_token']
    tab = CourseTabList.get_tab_by_id(course.tabs, "calendar_tab")
    is_staff = bool(has_access(request.user, 'staff', course))

    try:
        data = json.loads(tab.data)
    except (TypeError, ValueError):
        data = {}

    context = {
        "course": course,
        "csrf_token": csrf_token,
        'url': data.get('url', '#'),
        'message': data.get('message', _('Open calendar')),
        'is_staff': is_staff
    }
    return render_to_response("calendar_tab/calendar_tab.html", context)
Пример #49
0
    def update_item(self, xblock, user=None, allow_not_found=False):
        """
        Update the persisted version of xblock to reflect its current values.

        location: Something that can be passed to Location
        data: A nested dictionary of problem data
        """
        try:
            definition_data = xblock.get_explicitly_set_fields_by_scope()
            payload = {
                'definition.data': definition_data,
                'metadata': own_metadata(xblock),
            }
            if xblock.has_children:
                # convert all to urls
                xblock.children = [child.url() if isinstance(child, Location) else child
                                   for child in xblock.children]
                payload.update({'definition.children': xblock.children})
            self._update_single_item(xblock.location, payload)
            # for static tabs, their containing course also records their display name
            if xblock.category == 'static_tab':
                course = self._get_course_for_item(xblock.location)
                # find the course's reference to this tab and update the name.
                static_tab = CourseTabList.get_tab_by_slug(course, xblock.location.name)
                # only update if changed
                if static_tab and static_tab['name'] != xblock.display_name:
                    static_tab['name'] = xblock.display_name
                    self.update_item(course, user)

            # recompute (and update) the metadata inheritance tree which is cached
            # was conditional on children or metadata having changed before dhm made one update to rule them all
            self.refresh_cached_metadata_inheritance_tree(xblock.location)
            # fire signal that we've written to DB
            self.fire_updated_modulestore_signal(get_course_id_no_run(xblock.location), xblock.location)
        except ItemNotFoundError:
            if not allow_not_found:
                raise
Пример #50
0
def _save_xblock(user, xblock, data=None, children=None, metadata=None, nullout=None,
                 grader_type=None, publish=None):
    """
    Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata.
    nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert
    to default).
    """
    store = modulestore()

    # Don't allow updating an xblock and discarding changes in a single operation (unsupported by UI).
    if publish == "discard_changes":
        store.revert_to_published(xblock.location, user.id)
        # Returning the same sort of result that we do for other save operations. In the future,
        # we may want to return the full XBlockInfo.
        return JsonResponse({'id': unicode(xblock.location)})

    old_metadata = own_metadata(xblock)
    old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content)

    if data:
        # TODO Allow any scope.content fields not just "data" (exactly like the get below this)
        xblock.data = data
    else:
        data = old_content['data'] if 'data' in old_content else None

    if children is not None:
        children_usage_keys = []
        for child in children:
            child_usage_key = usage_key_with_run(child)
            children_usage_keys.append(child_usage_key)
        xblock.children = children_usage_keys

    # also commit any metadata which might have been passed along
    if nullout is not None or metadata is not None:
        # the postback is not the complete metadata, as there's system metadata which is
        # not presented to the end-user for editing. So let's use the original (existing_item) and
        # 'apply' the submitted metadata, so we don't end up deleting system metadata.
        if nullout is not None:
            for metadata_key in nullout:
                setattr(xblock, metadata_key, None)

        # update existing metadata with submitted metadata (which can be partial)
        # IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
        # the intent is to make it None, use the nullout field
        if metadata is not None:
            for metadata_key, value in metadata.items():
                field = xblock.fields[metadata_key]

                if value is None:
                    field.delete_from(xblock)
                else:
                    try:
                        value = field.from_json(value)
                    except ValueError:
                        return JsonResponse({"error": "Invalid data"}, 400)
                    field.write_to(xblock, value)

    if callable(getattr(xblock, "editor_saved", None)):
        xblock.editor_saved(user, old_metadata, old_content)

    # commit to datastore
    store.update_item(xblock, user.id)

    # for static tabs, their containing course also records their display name
    if xblock.location.category == 'static_tab':
        course = store.get_course(xblock.location.course_key)
        # find the course's reference to this tab and update the name.
        static_tab = CourseTabList.get_tab_by_slug(course.tabs, xblock.location.name)
        # only update if changed
        if static_tab and static_tab['name'] != xblock.display_name:
            static_tab['name'] = xblock.display_name
            store.update_item(course, user.id)

    result = {
        'id': unicode(xblock.location),
        'data': data,
        'metadata': own_metadata(xblock)
    }

    if grader_type is not None:
        result.update(CourseGradingModel.update_section_grader_type(xblock, grader_type, user))

    # If publish is set to 'republish' and this item is not in direct only categories and has previously been published,
    # then this item should be republished. This is used by staff locking to ensure that changing the draft
    # value of the staff lock will also update the published version, but only at the unit level.
    if publish == 'republish' and xblock.category not in DIRECT_ONLY_CATEGORIES:
        if modulestore().has_published_version(xblock):
            publish = 'make_public'

    # Make public after updating the xblock, in case the caller asked for both an update and a publish.
    # Used by Bok Choy tests and by republishing of staff locks.
    if publish == 'make_public':
        modulestore().publish(xblock.location, user.id)

    # Note that children aren't being returned until we have a use case.
    return JsonResponse(result, encoder=EdxJSONEncoder)
Пример #51
0
def _save_item(user, usage_key, data=None, children=None, metadata=None, nullout=None,
               grader_type=None, publish=None):
    """
    Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata.
    nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert
    to default).
    """
    store = modulestore()

    try:
        existing_item = store.get_item(usage_key)
    except ItemNotFoundError:
        if usage_key.category in CREATE_IF_NOT_FOUND:
            # New module at this location, for pages that are not pre-created.
            # Used for course info handouts.
            existing_item = store.create_and_save_xmodule(usage_key, user.id)
        else:
            raise
    except InvalidLocationError:
        log.error("Can't find item by location.")
        return JsonResponse({"error": "Can't find item by location: " + unicode(usage_key)}, 404)

    old_metadata = own_metadata(existing_item)
    old_content = existing_item.get_explicitly_set_fields_by_scope(Scope.content)

    if publish:
        if publish == 'make_private':
            try:
                store.unpublish(existing_item.location, user.id),
            except ItemNotFoundError:
                pass
        elif publish == 'create_draft':
            try:
                # This recursively clones the item subtree and marks the copies as draft
                store.convert_to_draft(existing_item.location, user.id)
            except DuplicateItemError:
                pass


    if data:
        # TODO Allow any scope.content fields not just "data" (exactly like the get below this)
        existing_item.data = data
    else:
        data = old_content['data'] if 'data' in old_content else None

    if children is not None:
        children_usage_keys = [
            UsageKey.from_string(child)
            for child
            in children
        ]
        existing_item.children = children_usage_keys

    # also commit any metadata which might have been passed along
    if nullout is not None or metadata is not None:
        # the postback is not the complete metadata, as there's system metadata which is
        # not presented to the end-user for editing. So let's use the original (existing_item) and
        # 'apply' the submitted metadata, so we don't end up deleting system metadata.
        if nullout is not None:
            for metadata_key in nullout:
                setattr(existing_item, metadata_key, None)

        # update existing metadata with submitted metadata (which can be partial)
        # IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
        # the intent is to make it None, use the nullout field
        if metadata is not None:
            for metadata_key, value in metadata.items():
                field = existing_item.fields[metadata_key]

                if value is None:
                    field.delete_from(existing_item)
                else:
                    try:
                        value = field.from_json(value)
                    except ValueError:
                        return JsonResponse({"error": "Invalid data"}, 400)
                    field.write_to(existing_item, value)

    if callable(getattr(existing_item, "editor_saved", None)):
        existing_item.editor_saved(user, old_metadata, old_content)

    # commit to datastore
    store.update_item(existing_item, user.id)

    # for static tabs, their containing course also records their display name
    if usage_key.category == 'static_tab':
        course = store.get_course(usage_key.course_key)
        # find the course's reference to this tab and update the name.
        static_tab = CourseTabList.get_tab_by_slug(course.tabs, usage_key.name)
        # only update if changed
        if static_tab and static_tab['name'] != existing_item.display_name:
            static_tab['name'] = existing_item.display_name
            store.update_item(course, user.id)

    result = {
        'id': unicode(usage_key),
        'data': data,
        'metadata': own_metadata(existing_item)
    }

    if grader_type is not None:
        result.update(CourseGradingModel.update_section_grader_type(existing_item, grader_type, user))

    # Make public after updating the xblock, in case the caller asked
    # for both an update and a publish.
    if publish and publish == 'make_public':
        modulestore().publish(existing_item.location, user.id)

    # Note that children aren't being returned until we have a use case.
    return JsonResponse(result)
Пример #52
0
def tabs_handler(request, tag=None, package_id=None, branch=None, version_guid=None, block=None):
    """
    The restful handler for static tabs.

    GET
        html: return page for editing static tabs
        json: not supported
    PUT or POST
        json: update the tab order. It is expected that the request body contains a JSON-encoded dict with entry "tabs".
        The value for "tabs" is an array of tab locators, indicating the desired order of the tabs.

    Creating a tab, deleting a tab, or changing its contents is not supported through this method.
    Instead use the general xblock URL (see item.xblock_handler).
    """
    locator = BlockUsageLocator(package_id=package_id, branch=branch, version_guid=version_guid, block_id=block)
    if not has_course_access(request.user, locator):
        raise PermissionDenied()

    old_location = loc_mapper().translate_locator_to_location(locator)
    store = get_modulestore(old_location)
    course_item = store.get_item(old_location)

    if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
        if request.method == 'GET':
            raise NotImplementedError('coming soon')
        else:
            if 'tabs' in request.json:
                def get_location_for_tab(tab):
                    """  Returns the location (old-style) for a tab. """
                    return loc_mapper().translate_locator_to_location(BlockUsageLocator(tab))

                tabs = request.json['tabs']

                # get list of existing static tabs in course
                # make sure they are the same lengths (i.e. the number of passed in tabs equals the number
                # that we know about) otherwise we will inadvertently drop some!
                existing_static_tabs = [t for t in course_item.tabs if t['type'] == 'static_tab']
                if len(existing_static_tabs) != len(tabs):
                    return JsonResponse(
                        {"error": "number of tabs must be {}".format(len(existing_static_tabs))}, status=400
                    )

                # load all reference tabs, return BadRequest if we can't find any of them
                tab_items = []
                for tab in tabs:
                    item = modulestore('direct').get_item(get_location_for_tab(tab))
                    if item is None:
                        return JsonResponse(
                            {"error": "no tab for found location {}".format(tab)}, status=400
                        )

                    tab_items.append(item)

                # now just go through the existing course_tabs and re-order the static tabs
                reordered_tabs = []
                static_tab_idx = 0
                for tab in course_item.tabs:
                    if isinstance(tab, StaticTab):
                        reordered_tabs.append(
                            StaticTab(
                                name=tab_items[static_tab_idx].display_name,
                                url_slug=tab_items[static_tab_idx].location.name,
                            )
                        )
                        static_tab_idx += 1
                    else:
                        reordered_tabs.append(tab)

                # OK, re-assemble the static tabs in the new order
                course_item.tabs = reordered_tabs
                modulestore('direct').update_item(course_item, request.user.id)
                return JsonResponse()
            else:
                raise NotImplementedError('Creating or changing tab content is not supported.')
    elif request.method == 'GET':  # assume html
        # get all tabs from the tabs list: static tabs (a.k.a. user-created tabs) and built-in tabs
        # we do this because this is also the order in which items are displayed in the LMS

        static_tabs = []
        built_in_tabs = []
        for tab in CourseTabList.iterate_displayable(course_item, settings, include_instructor_tab=False):
            if isinstance(tab, StaticTab):
                static_tab_loc = old_location.replace(category='static_tab', name=tab.url_slug)
                static_tabs.append(modulestore('direct').get_item(static_tab_loc))
            else:
                built_in_tabs.append(tab)

        # create a list of components for each static tab
        components = [
            loc_mapper().translate_location(
                course_item.location.course_id, static_tab.location, False, True
            )
            for static_tab
            in static_tabs
        ]

        return render_to_response('edit-tabs.html', {
            'context_course': course_item,
            'built_in_tabs': built_in_tabs,
            'components': components,
            'course_locator': locator
        })
    else:
        return HttpResponseNotFound()
Пример #53
0
def import_from_xml(
        store, user_id, data_dir, course_dirs=None,
        default_class='xmodule.raw_module.RawDescriptor',
        load_error_modules=True, static_content_store=None,
        target_course_id=None, verbose=False,
        do_import_static=True, create_new_course_if_not_present=False):
    """
    Import the specified xml data_dir into the "store" modulestore,
    using org and course as the location org and course.

    course_dirs: If specified, the list of course_dirs to load. Otherwise, load
    all course dirs

    target_course_id is the CourseKey that all modules should be remapped to
    after import off disk. We do this remapping as a post-processing step
    because there's logic in the importing which expects a 'url_name' as an
    identifier to where things are on disk
    e.g. ../policies/<url_name>/policy.json as well as metadata keys in
    the policy.json. so we need to keep the original url_name during import

    :param do_import_static:
        if False, then static files are not imported into the static content
        store. This can be employed for courses which have substantial
        unchanging static content, which is to inefficient to import every
        time the course is loaded. Static content for some courses may also be
        served directly by nginx, instead of going through django.

    : create_new_course_if_not_present:
        If True, then a new course is created if it doesn't already exist.
        The check for existing courses is case-insensitive.
    """

    xml_module_store = XMLModuleStore(
        data_dir,
        default_class=default_class,
        course_dirs=course_dirs,
        load_error_modules=load_error_modules,
        xblock_mixins=store.xblock_mixins,
        xblock_select=store.xblock_select,
    )

    # If we're going to remap the course_id, then we can only do that with
    # a single course
    if target_course_id:
        assert(len(xml_module_store.modules) == 1)

    # NOTE: the XmlModuleStore does not implement get_items()
    # which would be a preferable means to enumerate the entire collection
    # of course modules. It will be left as a TBD to implement that
    # method on XmlModuleStore.
    course_items = []

    with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred):
        for course_key in xml_module_store.modules.keys():

            if target_course_id is not None:
                dest_course_id = target_course_id
            else:
                dest_course_id = course_key

            # Creates a new course if it doesn't already exist
            if create_new_course_if_not_present and not store.has_course(dest_course_id, ignore_case=True):
                try:
                    store.create_course(dest_course_id.org, dest_course_id.course, dest_course_id.run, user_id)
                except InvalidLocationError:
                    # course w/ same org and course exists
                    log.debug(
                        "Skipping import of course with id, {0},"
                        "since it collides with an existing one".format(dest_course_id)
                    )
                    continue

            with store.bulk_write_operations(dest_course_id):
                course_data_path = None

                if verbose:
                    log.debug("Scanning {0} for course module...".format(course_key))

                # Quick scan to get course module as we need some info from there.
                # Also we need to make sure that the course module is committed
                # first into the store
                for module in xml_module_store.modules[course_key].itervalues():
                    if module.scope_ids.block_type == 'course':
                        course_data_path = path(data_dir) / module.data_dir

                        log.debug(u'======> IMPORTING course {course_key}'.format(
                            course_key=course_key,
                        ))

                        if not do_import_static:
                            # for old-style xblock where this was actually linked to kvs
                            module.static_asset_path = module.data_dir
                            module.save()
                            log.debug('course static_asset_path={path}'.format(
                                path=module.static_asset_path
                            ))

                        log.debug('course data_dir={0}'.format(module.data_dir))

                        course = _import_module_and_update_references(
                            module, store, user_id,
                            course_key,
                            dest_course_id,
                            do_import_static=do_import_static
                        )

                        for entry in course.pdf_textbooks:
                            for chapter in entry.get('chapters', []):
                                if StaticContent.is_c4x_path(chapter.get('url', '')):
                                    asset_key = StaticContent.get_location_from_path(chapter['url'])
                                    chapter['url'] = StaticContent.get_static_path_from_location(asset_key)

                        # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
                        # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
                        # values then remap it so that the wiki does not point to the old wiki.
                        if course_key != course.id:
                            original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                                course_key.org,
                                course_key.course,
                                course_key.run
                            )
                            if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course:
                                course.wiki_slug = u'{0}.{1}.{2}'.format(
                                    course.id.org,
                                    course.id.course,
                                    course.id.run,
                                )

                        # cdodge: more hacks (what else). Seems like we have a
                        # problem when importing a course (like 6.002) which
                        # does not have any tabs defined in the policy file.
                        # The import goes fine and then displays fine in LMS,
                        # but if someone tries to add a new tab in the CMS, then
                        # the LMS barfs because it expects that -- if there are
                        # *any* tabs -- then there at least needs to be
                        # some predefined ones
                        if course.tabs is None or len(course.tabs) == 0:
                            CourseTabList.initialize_default(course)

                        store.update_item(course, user_id)

                        course_items.append(course)
                        break

                # TODO: shouldn't this raise an exception if course wasn't found?

                # then import all the static content
                if static_content_store is not None and do_import_static:
                    # first pass to find everything in /static/
                    import_static_content(
                        course_data_path, static_content_store,
                        dest_course_id, subpath='static', verbose=verbose
                    )

                elif verbose and not do_import_static:
                    log.debug(
                        "Skipping import of static content, "
                        "since do_import_static={0}".format(do_import_static)
                    )

                # no matter what do_import_static is, import "static_import" directory

                # This is needed because the "about" pages (eg "overview") are
                # loaded via load_extra_content, and do not inherit the lms
                # metadata from the course module, and thus do not get
                # "static_content_store" properly defined. Static content
                # referenced in those extra pages thus need to come through the
                # c4x:// contentstore, unfortunately. Tell users to copy that
                # content into the "static_import" subdir.

                simport = 'static_import'
                if os.path.exists(course_data_path / simport):
                    import_static_content(
                        course_data_path, static_content_store,
                        dest_course_id, subpath=simport, verbose=verbose
                    )

                # now loop through all the modules
                for module in xml_module_store.modules[course_key].itervalues():
                    if module.scope_ids.block_type == 'course':
                        # we've already saved the course module up at the top
                        # of the loop so just skip over it in the inner loop
                        continue

                    if verbose:
                        log.debug('importing module location {loc}'.format(
                            loc=module.location
                        ))

                    _import_module_and_update_references(
                        module, store,
                        user_id,
                        course_key,
                        dest_course_id,
                        do_import_static=do_import_static,
                        runtime=course.runtime
                    )

                # finally, publish the course
                store.publish(course.location, user_id)

                # now import any DRAFT items
                _import_course_draft(
                    xml_module_store,
                    store,
                    user_id,
                    course_data_path,
                    course_key,
                    dest_course_id,
                    course.runtime
                )

    return xml_module_store, course_items
Пример #54
0
    def _load_extra_content(self, system, course_descriptor, category, content_path, course_dir):
        """
        Import fields data content from files
        """
        for filepath in glob.glob(content_path / "*"):
            if not os.path.isfile(filepath):
                continue

            if filepath.endswith("~"):  # skip *~ files
                continue

            with open(filepath) as f:
                try:
                    if filepath.find(".json") != -1:
                        # json file with json data content
                        slug, loc, data_content = self._import_field_content(course_descriptor, category, filepath)
                        if data_content is None:
                            continue
                        else:
                            try:
                                # get and update data field in xblock runtime
                                module = system.load_item(loc)
                                for key, value in data_content.iteritems():
                                    setattr(module, key, value)
                                module.save()
                            except ItemNotFoundError:
                                module = None
                                data_content["location"] = loc
                                data_content["category"] = category
                    else:
                        slug = os.path.splitext(os.path.basename(filepath))[0]
                        loc = course_descriptor.scope_ids.usage_id.replace(category=category, name=slug)
                        # html file with html data content
                        html = f.read().decode("utf-8")
                        try:
                            module = system.load_item(loc)
                            module.data = html
                            module.save()
                        except ItemNotFoundError:
                            module = None
                            data_content = {"data": html, "location": loc, "category": category}

                    if module is None:
                        module = system.construct_xblock(
                            category,
                            # We're loading a descriptor, so student_id is meaningless
                            # We also don't have separate notions of definition and usage ids yet,
                            # so we use the location for both
                            ScopeIds(None, category, loc, loc),
                            DictFieldData(data_content),
                        )
                        # VS[compat]:
                        # Hack because we need to pull in the 'display_name' for static tabs (because we need to edit them)
                        # from the course policy
                        if category == "static_tab":
                            tab = CourseTabList.get_tab_by_slug(tab_list=course_descriptor.tabs, url_slug=slug)
                            if tab:
                                module.display_name = tab.name
                        module.data_dir = course_dir
                        module.save()

                        self.modules[course_descriptor.id][module.scope_ids.usage_id] = module
                except Exception as exc:  # pylint: disable=broad-except
                    logging.exception(
                        "Failed to load %s. Skipping... \
                            Exception: %s",
                        filepath,
                        unicode(exc),
                    )
                    system.error_tracker("ERROR: " + unicode(exc))
Пример #55
0
def _save_xblock(user, xblock, data=None, children_strings=None, metadata=None, nullout=None,
                 grader_type=None, publish=None):
    """
    Saves xblock w/ its fields. Has special processing for grader_type, publish, and nullout and Nones in metadata.
    nullout means to truly set the field to None whereas nones in metadata mean to unset them (so they revert
    to default).
    """
    store = modulestore()
    # Perform all xblock changes within a (single-versioned) transaction
    with store.bulk_operations(xblock.location.course_key):

        # Don't allow updating an xblock and discarding changes in a single operation (unsupported by UI).
        if publish == "discard_changes":
            store.revert_to_published(xblock.location, user.id)
            # Returning the same sort of result that we do for other save operations. In the future,
            # we may want to return the full XBlockInfo.
            return JsonResponse({'id': unicode(xblock.location)})

        old_metadata = own_metadata(xblock)
        old_content = xblock.get_explicitly_set_fields_by_scope(Scope.content)

        if data:
            # TODO Allow any scope.content fields not just "data" (exactly like the get below this)
            xblock.data = data
        else:
            data = old_content['data'] if 'data' in old_content else None

        if children_strings is not None:
            children = []
            for child_string in children_strings:
                children.append(usage_key_with_run(child_string))

            # if new children have been added, remove them from their old parents
            new_children = set(children) - set(xblock.children)
            for new_child in new_children:
                old_parent_location = store.get_parent_location(new_child)
                if old_parent_location:
                    old_parent = store.get_item(old_parent_location)
                    old_parent.children.remove(new_child)
                    old_parent = _update_with_callback(old_parent, user)
                else:
                    # the Studio UI currently doesn't present orphaned children, so assume this is an error
                    return JsonResponse({"error": "Invalid data, possibly caused by concurrent authors."}, 400)

            # make sure there are no old children that became orphans
            # In a single-author (no-conflict) scenario, all children in the persisted list on the server should be
            # present in the updated list.  If there are any children that have been dropped as part of this update,
            # then that would be an error.
            #
            # We can be even more restrictive in a multi-author (conflict), by returning an error whenever
            # len(old_children) > 0. However, that conflict can still be "merged" if the dropped child had been
            # re-parented. Hence, the check for the parent in the any statement below.
            #
            # Note that this multi-author conflict error should not occur in modulestores (such as Split) that support
            # atomic write transactions.  In Split, if there was another author who moved one of the "old_children"
            # into another parent, then that child would have been deleted from this parent on the server. However,
            # this is error could occur in modulestores (such as Draft) that do not support atomic write-transactions
            old_children = set(xblock.children) - set(children)
            if any(
                    store.get_parent_location(old_child) == xblock.location
                    for old_child in old_children
            ):
                # since children are moved as part of a single transaction, orphans should not be created
                return JsonResponse({"error": "Invalid data, possibly caused by concurrent authors."}, 400)

            # set the children on the xblock
            xblock.children = children

        # also commit any metadata which might have been passed along
        if nullout is not None or metadata is not None:
            # the postback is not the complete metadata, as there's system metadata which is
            # not presented to the end-user for editing. So let's use the original (existing_item) and
            # 'apply' the submitted metadata, so we don't end up deleting system metadata.
            if nullout is not None:
                for metadata_key in nullout:
                    setattr(xblock, metadata_key, None)

            # update existing metadata with submitted metadata (which can be partial)
            # IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
            # the intent is to make it None, use the nullout field
            if metadata is not None:
                for metadata_key, value in metadata.items():
                    field = xblock.fields[metadata_key]

                    if value is None:
                        field.delete_from(xblock)
                    else:
                        try:
                            value = field.from_json(value)
                        except ValueError as verr:
                            reason = _("Invalid data")
                            if verr.message:
                                reason = _("Invalid data ({details})").format(details=verr.message)
                            return JsonResponse({"error": reason}, 400)

                        field.write_to(xblock, value)

        # update the xblock and call any xblock callbacks
        xblock = _update_with_callback(xblock, user, old_metadata, old_content)

        # for static tabs, their containing course also records their display name
        if xblock.location.category == 'static_tab':
            course = store.get_course(xblock.location.course_key)
            # find the course's reference to this tab and update the name.
            static_tab = CourseTabList.get_tab_by_slug(course.tabs, xblock.location.name)
            # only update if changed
            if static_tab and static_tab['name'] != xblock.display_name:
                static_tab['name'] = xblock.display_name
                store.update_item(course, user.id)

        result = {
            'id': unicode(xblock.location),
            'data': data,
            'metadata': own_metadata(xblock)
        }

        if grader_type is not None:
            result.update(CourseGradingModel.update_section_grader_type(xblock, grader_type, user))

        # If publish is set to 'republish' and this item is not in direct only categories and has previously been published,
        # then this item should be republished. This is used by staff locking to ensure that changing the draft
        # value of the staff lock will also update the published version, but only at the unit level.
        if publish == 'republish' and xblock.category not in DIRECT_ONLY_CATEGORIES:
            if modulestore().has_published_version(xblock):
                publish = 'make_public'

        # Make public after updating the xblock, in case the caller asked for both an update and a publish.
        # Used by Bok Choy tests and by republishing of staff locks.
        if publish == 'make_public':
            modulestore().publish(xblock.location, user.id)

        # Note that children aren't being returned until we have a use case.
        return JsonResponse(result, encoder=EdxJSONEncoder)
Пример #56
0
def _import_course_module(
        store, runtime, user_id, data_dir, course_key, dest_course_id, source_course, do_import_static,
        verbose,
):
    if verbose:
        log.debug("Scanning {0} for course module...".format(course_key))

    # Quick scan to get course module as we need some info from there.
    # Also we need to make sure that the course module is committed
    # first into the store
    course_data_path = path(data_dir) / source_course.data_dir

    log.debug(u'======> IMPORTING course {course_key}'.format(
        course_key=course_key,
    ))

    if not do_import_static:
        # for old-style xblock where this was actually linked to kvs
        source_course.static_asset_path = source_course.data_dir
        source_course.save()
        log.debug('course static_asset_path={path}'.format(
            path=source_course.static_asset_path
        ))

    log.debug('course data_dir={0}'.format(source_course.data_dir))

    with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred, dest_course_id):

        course = _import_module_and_update_references(
            source_course, store, user_id,
            course_key,
            dest_course_id,
            do_import_static=do_import_static,
            runtime=runtime,
        )

        for entry in course.pdf_textbooks:
            for chapter in entry.get('chapters', []):
                if StaticContent.is_c4x_path(chapter.get('url', '')):
                    asset_key = StaticContent.get_location_from_path(chapter['url'])
                    chapter['url'] = StaticContent.get_static_path_from_location(asset_key)

        # Original wiki_slugs had value location.course. To make them unique this was changed to 'org.course.name'.
        # If we are importing into a course with a different course_id and wiki_slug is equal to either of these default
        # values then remap it so that the wiki does not point to the old wiki.
        if course_key != course.id:
            original_unique_wiki_slug = u'{0}.{1}.{2}'.format(
                course_key.org,
                course_key.course,
                course_key.run
            )
            if course.wiki_slug == original_unique_wiki_slug or course.wiki_slug == course_key.course:
                course.wiki_slug = u'{0}.{1}.{2}'.format(
                    course.id.org,
                    course.id.course,
                    course.id.run,
                )

        # cdodge: more hacks (what else). Seems like we have a
        # problem when importing a course (like 6.002) which
        # does not have any tabs defined in the policy file.
        # The import goes fine and then displays fine in LMS,
        # but if someone tries to add a new tab in the CMS, then
        # the LMS barfs because it expects that -- if there are
        # *any* tabs -- then there at least needs to be
        # some predefined ones
        if course.tabs is None or len(course.tabs) == 0:
            CourseTabList.initialize_default(course)

        store.update_item(course, user_id)
    return course, course_data_path