Пример #1
0
    def test_update_section_grader_type(self):
        # Get the descriptor and the section_grader_type and assert they are the default values
        descriptor = get_modulestore(self.course.location).get_item(
            self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(
            self.course_locator)

        self.assertEqual('notgraded', section_grader_type['graderType'])
        self.assertEqual(None, descriptor.format)
        self.assertEqual(False, descriptor.graded)

        # Change the default grader type to Homework, which should also mark the section as graded
        CourseGradingModel.update_section_grader_type(self.course, 'Homework',
                                                      self.user)
        descriptor = get_modulestore(self.course.location).get_item(
            self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(
            self.course_locator)

        self.assertEqual('Homework', section_grader_type['graderType'])
        self.assertEqual('Homework', descriptor.format)
        self.assertEqual(True, descriptor.graded)

        # Change the grader type back to notgraded, which should also unmark the section as graded
        CourseGradingModel.update_section_grader_type(self.course, 'notgraded',
                                                      self.user)
        descriptor = get_modulestore(self.course.location).get_item(
            self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(
            self.course_locator)

        self.assertEqual('notgraded', section_grader_type['graderType'])
        self.assertEqual(None, descriptor.format)
        self.assertEqual(False, descriptor.graded)
    def test_update_section_grader_type(self):
        # Get the descriptor and the section_grader_type and assert they are the default values
        descriptor = get_modulestore(self.course.location).get_item(self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)

        self.assertEqual("Not Graded", section_grader_type["graderType"])
        self.assertEqual(None, descriptor.lms.format)
        self.assertEqual(False, descriptor.lms.graded)

        # Change the default grader type to Homework, which should also mark the section as graded
        CourseGradingModel.update_section_grader_type(self.course.location, {"graderType": "Homework"})
        descriptor = get_modulestore(self.course.location).get_item(self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)

        self.assertEqual("Homework", section_grader_type["graderType"])
        self.assertEqual("Homework", descriptor.lms.format)
        self.assertEqual(True, descriptor.lms.graded)

        # Change the grader type back to Not Graded, which should also unmark the section as graded
        CourseGradingModel.update_section_grader_type(self.course.location, {"graderType": "Not Graded"})
        descriptor = get_modulestore(self.course.location).get_item(self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)

        self.assertEqual("Not Graded", section_grader_type["graderType"])
        self.assertEqual(None, descriptor.lms.format)
        self.assertEqual(False, descriptor.lms.graded)
    def test_update_section_grader_type(self):
        # Get the descriptor and the section_grader_type and assert they are the default values
        descriptor = modulestore().get_item(self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)

        self.assertEqual('notgraded', section_grader_type['graderType'])
        self.assertEqual(None, descriptor.format)
        self.assertEqual(False, descriptor.graded)

        # Change the default grader type to Homework, which should also mark the section as graded
        CourseGradingModel.update_section_grader_type(self.course, 'Homework', self.user)
        descriptor = modulestore().get_item(self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)

        self.assertEqual('Homework', section_grader_type['graderType'])
        self.assertEqual('Homework', descriptor.format)
        self.assertEqual(True, descriptor.graded)

        # Change the grader type back to notgraded, which should also unmark the section as graded
        CourseGradingModel.update_section_grader_type(self.course, 'notgraded', self.user)
        descriptor = modulestore().get_item(self.course.location)
        section_grader_type = CourseGradingModel.get_section_grader_type(self.course.location)

        self.assertEqual('notgraded', section_grader_type['graderType'])
        self.assertEqual(None, descriptor.format)
        self.assertEqual(False, descriptor.graded)
Пример #4
0
def assignment_type_update(request, org, course, category, name):
    '''
    CRUD operations on assignment types for sections and subsections and anything else gradable.
    '''
    location = Location(['i4x', org, course, category, name])
    if not has_access(request.user, location):
        return HttpResponseForbidden()

    if request.method == 'GET':
        return JsonResponse(CourseGradingModel.get_section_grader_type(location))
    elif request.method in ('POST', 'PUT'):  # post or put, doesn't matter.
        return JsonResponse(CourseGradingModel.update_section_grader_type(location, request.POST))
Пример #5
0
def assignment_type_update(request, org, course, category, name):
    '''
    CRUD operations on assignment types for sections and subsections and anything else gradable.
    '''
    location = Location(['i4x', org, course, category, name])
    if not has_access(request.user, location):
        return HttpResponseForbidden()

    if request.method == 'GET':
        return JsonResponse(CourseGradingModel.get_section_grader_type(location))
    elif request.method in ('POST', 'PUT'):  # post or put, doesn't matter.
        return JsonResponse(CourseGradingModel.update_section_grader_type(location, request.POST))
Пример #6
0
def assignment_type_update(request, org, course, category, name):
    """
    CRUD operations on assignment types for sections and subsections and
    anything else gradable.
    """
    location = Location(["i4x", org, course, category, name])
    if not has_access(request.user, location):
        return HttpResponseForbidden()

    if request.method == "GET":
        rsp = CourseGradingModel.get_section_grader_type(location)
    elif request.method in ("POST", "PUT"):  # post or put, doesn't matter.
        rsp = CourseGradingModel.update_section_grader_type(location, request.POST)
    return JsonResponse(rsp)
Пример #7
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)
Пример #8
0
def _save_item(request, usage_loc, item_location, 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).

    The item_location is still the old-style location whereas usage_loc is a BlockUsageLocator
    """
    store = get_modulestore(item_location)

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

    old_metadata = own_metadata(existing_item)

    if publish:
        if publish == 'make_private':
            _xmodule_recurse(existing_item, lambda i: modulestore().unpublish(i.location))
        elif publish == 'create_draft':
            # This clones the existing item location to a draft location (the draft is
            # implicit, because modulestore is a Draft modulestore)
            modulestore().convert_to_draft(item_location)

    if data:
        # TODO Allow any scope.content fields not just "data" (exactly like the get below this)
        existing_item.data = data
    else:
        data = existing_item.get_explicitly_set_fields_by_scope(Scope.content)

    if children is not None:
        children_ids = [
            loc_mapper().translate_locator_to_location(BlockUsageLocator(child_locator)).url()
            for child_locator
            in children
        ]
        existing_item.children = children_ids

    # 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 existing_item.category == 'video':
            manage_video_subtitles_save(existing_item, request.user, old_metadata, generate_translation=True)

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

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

    if grader_type is not None:
        result.update(CourseGradingModel.update_section_grader_type(existing_item, grader_type, request.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':
        def _publish(block):
            # This is super gross, but prevents us from publishing something that
            # we shouldn't. Ideally, all modulestores would have a consistant
            # interface for publishing. However, as of now, only the DraftMongoModulestore
            # does, so we have to check for the attribute explicitly.
            store = get_modulestore(block.location)
            if hasattr(store, 'publish'):
                store.publish(block.location, request.user.id)

        _xmodule_recurse(
            existing_item,
            _publish
        )

    # Note that children aren't being returned until we have a use case.
    return JsonResponse(result)
Пример #9
0
def create_xblock(parent_locator, user, category, display_name, boilerplate=None, is_entrance_exam=False):
    """
    Performs the actual grunt work of creating items/xblocks -- knows nothing about requests, views, etc.
    """
    store = modulestore()
    usage_key = usage_key_with_run(parent_locator)
    with store.bulk_operations(usage_key.course_key):
        parent = store.get_item(usage_key)
        dest_usage_key = usage_key.replace(category=category, name=uuid4().hex)

        # get the metadata, display_name, and definition from the caller
        metadata = {}
        data = None
        template_id = boilerplate
        if template_id:
            clz = parent.runtime.load_block_type(category)
            if clz is not None:
                template = clz.get_template(template_id)
                if template is not None:
                    metadata = template.get('metadata', {})
                    data = template.get('data')

        if display_name is not None:
            metadata['display_name'] = display_name

        # We should use the 'fields' kwarg for newer module settings/values (vs. metadata or data)
        fields = {}

        # Entrance Exams: Chapter module positioning
        child_position = None
        if is_entrance_exams_enabled():
            if category == 'chapter' and is_entrance_exam:
                fields['is_entrance_exam'] = is_entrance_exam
                fields['in_entrance_exam'] = True  # Inherited metadata, all children will have it
                child_position = 0

        # TODO need to fix components that are sending definition_data as strings, instead of as dicts
        # For now, migrate them into dicts here.
        if isinstance(data, basestring):
            data = {'data': data}

        created_block = store.create_child(
            user.id,
            usage_key,
            dest_usage_key.block_type,
            block_id=dest_usage_key.block_id,
            fields=fields,
            definition_data=data,
            metadata=metadata,
            runtime=parent.runtime,
            position=child_position,
        )

        # Entrance Exams: Grader assignment
        if is_entrance_exams_enabled():
            course_key = usage_key.course_key
            course = store.get_course(course_key)
            if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled:
                if category == 'sequential' and parent_locator == course.entrance_exam_id:
                    # Clean up any pre-existing entrance exam graders
                    remove_entrance_exam_graders(course_key, user)
                    grader = {
                        "type": GRADER_TYPES['ENTRANCE_EXAM'],
                        "min_count": 0,
                        "drop_count": 0,
                        "short_label": "Entrance",
                        "weight": 0
                    }
                    grading_model = CourseGradingModel.update_grader_from_json(
                        course.id,
                        grader,
                        user
                    )
                    CourseGradingModel.update_section_grader_type(
                        created_block,
                        grading_model['type'],
                        user
                    )

        # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
        # if we add one then we need to also add it to the policy information (i.e. metadata)
        # we should remove this once we can break this reference from the course to static tabs
        if category == 'static_tab':

            dog_stats_api.increment(
                DEPRECATION_VSCOMPAT_EVENT,
                tags=(
                    "location:create_xblock_static_tab",
                    u"course:{}".format(unicode(dest_usage_key.course_key)),
                )
            )

            display_name = display_name or _("Empty")  # Prevent name being None
            course = store.get_course(dest_usage_key.course_key)
            course.tabs.append(
                StaticTab(
                    name=display_name,
                    url_slug=dest_usage_key.name,
                )
            )
            store.update_item(course, user.id)

        return created_block
Пример #10
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:
                            return JsonResponse({"error": "Invalid data"}, 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)
Пример #11
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)
Пример #12
0
def _save_item(request, usage_loc, item_location, 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).

    The item_location is still the old-style location whereas usage_loc is a BlockUsageLocator
    """
    store = get_modulestore(item_location)

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

    if publish:
        if publish == 'make_private':
            _xmodule_recurse(existing_item, lambda i: modulestore().unpublish(i.location))
        elif publish == 'create_draft':
            # This clones the existing item location to a draft location (the draft is
            # implicit, because modulestore is a Draft modulestore)
            modulestore().convert_to_draft(item_location)

    if data:
        store.update_item(item_location, data)
    else:
        data = existing_item.get_explicitly_set_fields_by_scope(Scope.content)

    if children is not None:
        children_ids = [
            loc_mapper().translate_locator_to_location(BlockUsageLocator(child_locator)).url()
            for child_locator
            in children
        ]
        store.update_children(item_location, children_ids)

    # cdodge: 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():

                if metadata_key == "locator_term":
                    temp_key = "direct_term"
                    json_array = json.loads(value)

                    for x in json_array:
                        old_loc = str(loc_mapper().translate_locator_to_location(x["direct_element_id"]))
                        i = old_loc.rfind("/")
                        short_name = old_loc[i+1:]
                        x["direct_element_id"] = short_name
                        for every_edge in x["disjunctions"]:
                            for every_cond in every_edge["conjunctions"]:
                                old_loc = str(loc_mapper().translate_locator_to_location(every_cond["source_element_id"]))
                                i = old_loc.rfind("/")
                                short_name = old_loc[i+1:]
                                every_cond["source_element_id"] = short_name

                    temp_value = json.dumps(json_array)

                else:
                    temp_key = metadata_key
                    temp_value = value

                field = existing_item.fields[temp_key]

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

        # Save the data that we've just changed to the underlying
        # MongoKeyValueStore before we update the mongo datastore.
        existing_item.save()
        # commit to datastore
        store.update_metadata(item_location, own_metadata(existing_item))

        if existing_item.category == 'video':
            manage_video_subtitles_save(existing_item, existing_item)

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

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

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

    # Note that children aren't being returned until we have a use case.
    return JsonResponse(result)
Пример #13
0
def _save_item(usage_loc, item_location, data=None, children=None, metadata=None, nullout=None,
        grader_type=None
    ):
    """
    Saves xblock w/ its fields. Has special processing for grader_type 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).

    The item_location is still the old-style location whereas usage_loc is a BlockUsageLocator
    """
    store = get_modulestore(item_location)

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

    if data:
        store.update_item(item_location, data)
    else:
        data = existing_item.get_explicitly_set_fields_by_scope(Scope.content)

    if children is not None:
        children_ids = [
            loc_mapper().translate_locator_to_location(BlockUsageLocator(child_locator)).url()
            for child_locator
            in children
        ]
        store.update_children(item_location, children_ids)

    # cdodge: 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)

        # Save the data that we've just changed to the underlying
        # MongoKeyValueStore before we update the mongo datastore.
        existing_item.save()
        # commit to datastore
        store.update_metadata(item_location, own_metadata(existing_item))

        if existing_item.category == 'video':
            manage_video_subtitles_save(existing_item, existing_item)

    result = {
        'id': unicode(usage_loc),
        'data': data,
        'metadata': own_metadata(existing_item)
    }
    if grader_type is not None:
        result.update(CourseGradingModel.update_section_grader_type(existing_item, grader_type))

    # Note that children aren't being returned until we have a use case.
    return JsonResponse(result)
Пример #14
0
def _create_item(request):
    """View for create items."""
    usage_key = usage_key_with_run(request.json['parent_locator'])
    if not has_studio_write_access(request.user, usage_key.course_key):
        raise PermissionDenied()

    category = request.json['category']
    display_name = request.json.get('display_name')

    if isinstance(usage_key, LibraryUsageLocator):
        # Only these categories are supported at this time.
        if category not in ['html', 'problem', 'video']:
            return HttpResponseBadRequest(
                "Category '%s' not supported for Libraries" % category, content_type='text/plain'
            )

    store = modulestore()
    with store.bulk_operations(usage_key.course_key):
        parent = store.get_item(usage_key)
        dest_usage_key = usage_key.replace(category=category, name=uuid4().hex)

        # get the metadata, display_name, and definition from the request
        metadata = {}
        data = None
        template_id = request.json.get('boilerplate')
        if template_id:
            clz = parent.runtime.load_block_type(category)
            if clz is not None:
                template = clz.get_template(template_id)
                if template is not None:
                    metadata = template.get('metadata', {})
                    data = template.get('data')

        if display_name is not None:
            metadata['display_name'] = display_name

        # Entrance Exams: Chapter module positioning
        child_position = None
        if settings.FEATURES.get('ENTRANCE_EXAMS', False):
            is_entrance_exam = request.json.get('is_entrance_exam', False)
            if category == 'chapter' and is_entrance_exam:
                metadata['is_entrance_exam'] = is_entrance_exam
                metadata['in_entrance_exam'] = True  # Inherited metadata, all children will have it
                child_position = 0

        # TODO need to fix components that are sending definition_data as strings, instead of as dicts
        # For now, migrate them into dicts here.
        if isinstance(data, basestring):
            data = {'data': data}

        created_block = store.create_child(
            request.user.id,
            usage_key,
            dest_usage_key.block_type,
            block_id=dest_usage_key.block_id,
            definition_data=data,
            metadata=metadata,
            runtime=parent.runtime,
            position=child_position
        )

        # Entrance Exams: Grader assignment
        if settings.FEATURES.get('ENTRANCE_EXAMS', False):
            course = store.get_course(usage_key.course_key)
            if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled:
                if category == 'sequential' and request.json.get('parent_locator') == course.entrance_exam_id:
                    grader = {
                        "type": "Entrance Exam",
                        "min_count": 0,
                        "drop_count": 0,
                        "short_label": "Entrance",
                        "weight": 0
                    }
                    grading_model = CourseGradingModel.update_grader_from_json(
                        course.id,
                        grader,
                        request.user
                    )
                    CourseGradingModel.update_section_grader_type(
                        created_block,
                        grading_model['type'],
                        request.user
                    )

        # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
        # if we add one then we need to also add it to the policy information (i.e. metadata)
        # we should remove this once we can break this reference from the course to static tabs
        if category == 'static_tab':
            display_name = display_name or _("Empty")  # Prevent name being None
            course = store.get_course(dest_usage_key.course_key)
            course.tabs.append(
                StaticTab(
                    name=display_name,
                    url_slug=dest_usage_key.name,
                )
            )
            store.update_item(course, request.user.id)

        return JsonResponse(
            {"locator": unicode(created_block.location), "courseKey": unicode(created_block.location.course_key)}
        )
Пример #15
0
def block_clone_handler(request, course_key_string):
    """

    Permite clonar o conteúdo de uma dada semana do experimento. Além de clonar, nesta função faz a definição do experimento. o que insere entradas nas
    tabelas ExperimentDefinition, StrategyRandomization e OpcoesExperiment.

    :param request: http request com parent_location (location do curso) e mais source_locator (location da section)
    :param course_key_string: useless
    :return: json com OK
    """

    letras = ['A', 'B', 'C', 'D']

    if request.method in ('PUT', 'POST'):
        #  Get location Course com base no valor passado pelo Json
        locatorCursokey = UsageKey.from_string(request.json['parent_locator'])
        locatorSectionKey = UsageKey.from_string(request.json['source_locator'])

        if not has_course_access(request.user, locatorSectionKey):
            raise PermissionDenied()

        # Verifica se já tem um experimento de acordo com o SourceLocation
        expSection = None
        try:
            opcExp = OpcoesExperiment.objects.get(sectionExp='%s' % request.json['source_locator'])
            temExp = True
            expSection = opcExp.experimento # pega o experimento para cadastrar a nova versao
        except:
            temExp = False

        # Pesquisa no banco de dados o Curso do qual duplicará os módulos
        course = modulestore().get_item(locatorCursokey, depth=3)
        sections = course.get_children()
        quantidade = 0

        for section in sections:

            if locatorSectionKey == section.location:
                NewLocatorItemSection = create_item(locatorCursokey, 'chapter', section.display_name_with_default, request)

                # tem que Mudar para HomeWork, ou qualquer tipo que eu definir
                SectionLocation =  NewLocatorItemSection
                descript = get_modulestore(NewLocatorItemSection).get_item(NewLocatorItemSection)

                storeSection = get_modulestore(NewLocatorItemSection)

                try:
                    existing_item = storeSection.get_item(SectionLocation)

                    field = existing_item.fields['start']

                    if section.start is not None:
                        print "NÃO É NULO "
                        field.write_to(existing_item, section.start)

                        storeSection.update_item(existing_item, request.user.id)

                except:
                    print "Start Date and end Date"


                subsections = section.get_children()
                quantidade = 0

                if temExp:
                    opcExp3 = OpcoesExperiment()
                    opcExp3.experimento = expSection
                    opcExp3.sectionExp = "%s" % NewLocatorItemSection
                    opcExp3.sectionExp_url = '%s' % getURLSection(locatorCursokey, NewLocatorItemSection)

                    lenOpcs = len(OpcoesExperiment.objects.filter(experimento=expSection))

                    opcExp3.version = letras[lenOpcs]
                    opcExp3.save()

                    st = opcExp3.experimento.strategy
                    st.percents +=';0.0'
                    st.save()

                else:
                    st = StrategyRandomization()
                    st.strategyType = 'UniformChoice'
                    st.percents = '0.0;0.0'
                    st.save()

                    # Experiment defintion
                    exp = ExperimentDefinition()
                    exp.course = request.json['parent_locator']
                    exp.userTeacher = request.user
                    now = datetime.datetime.now()
                    exp.descricao='MyExperiment %s ' % now
                    exp.strategy = st
                    exp.save()

                    # Define a primeira versão do experimento
                    opcExp = OpcoesExperiment()
                    opcExp.experimento = exp
                    opcExp.sectionExp = "%s" % locatorSectionKey
                    opcExp.sectionExp_url = "%s" % section.url_name
                    opcExp.version = 'A'
                    opcExp.save()

                    # Define a segunda versão do experimento
                    opcExp2 = OpcoesExperiment()
                    opcExp2.experimento = exp
                    opcExp2.sectionExp = "%s" % NewLocatorItemSection
                    opcExp2.sectionExp_url = '%s' % getURLSection(locatorCursokey, NewLocatorItemSection)
                    opcExp2.version = 'B'
                    opcExp2.save()

                for subsection in subsections:
                    print
                    print
                    print "Clonando SubSeção: ", subsection.location

                    NewLocatorItemSubsection = create_item(NewLocatorItemSection, 'sequential', subsection.display_name_with_default, request)

                    # Agora iremos testar os Units
                    units_Subsection = subsection.get_children()


                    print "Information about the subsection: "
                    print "subsection.format: ", subsection.format
                    print "subsection.start: ", subsection.start
                    print "Subsection locator: ", NewLocatorItemSubsection

                    # tem que Mudar para HomeWork, ou qualquer tipo que eu definir
                    # subLocation = loc_mapper().translate_locator_to_location(NewLocatorItemSubsection)

                    # print "vert Location: ", subLocation
                    # old_location = course_location.replace(category='course_info', name=block)
                    #
                    #
                    descript = get_modulestore(NewLocatorItemSubsection).get_item(NewLocatorItemSubsection)
                    print "Descript: ", descript

                    CourseGradingModel.update_section_grader_type(descript, subsection.format, request.user)

                    # Start Value
                    storeSection = get_modulestore(NewLocatorItemSubsection)

                    try:
                        existing_item = storeSection.get_item(NewLocatorItemSubsection)

                        field = existing_item.fields['start']

                        if subsection.start is not None:
                            print "NÃO É NULO "
                            field.write_to(existing_item, subsection.start)

                            storeSection.update_item(existing_item, request.user.id)

                    except:
                        print "Deu erro"

                    # Print all Units
                    for unit in units_Subsection:

                        originalState = compute_publish_state(unit)
                        destinationUnit = duplicate_item(NewLocatorItemSubsection, unit.location, unit.display_name_with_default, request.user)


                        # Nesta parte faz-se a leitura se e privado ou publico, se publico, seta a variavel como publico
                        try:

                            print "Vou fazer o translation -- destinationUnit ", destinationUnit

                            # unitLocation = loc_mapper().translate_locator_to_location(destinationUnit)
                            unitLocation = destinationUnit
                            print "unity location: ", unitLocation


                            # Start Value
                            storeUnit = get_modulestore(unitLocation)
                            print "STORE UNIT"

                            try:
                                existing_itemUnit = storeUnit.get_item(unitLocation)

                            except:
                                print "Deu erro"

                            print "Antes do public"

                            if originalState == 'public':
                                def _publish(block):
                                    # This is super gross, but prevents us from publishing something that
                                    # we shouldn't. Ideally, all modulestores would have a consistant
                                    # interface for publishing. However, as of now, only the DraftMongoModulestore
                                    # does, so we have to check for the attribute explicitly.
                                    store = get_modulestore(block.location)
                                    print "Peguei o Store"

                                    if hasattr(store, 'publish'):
                                        store.publish(block.location, request.user.id)

                                _xmodule_recurse(
                                    existing_itemUnit,
                                    _publish
                                )



                        except:
                            print "Erro ao setar publico"

    dataR = {'ok': quantidade }
    #
    return JsonResponse(dataR)
Пример #16
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)
Пример #17
0
def _save_item(request,
               usage_loc,
               item_location,
               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).

    The item_location is still the old-style location whereas usage_loc is a BlockUsageLocator
    """
    store = get_modulestore(item_location)

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

    old_metadata = own_metadata(existing_item)

    if publish:
        if publish == 'make_private':
            _xmodule_recurse(existing_item,
                             lambda i: modulestore().unpublish(i.location))
        elif publish == 'create_draft':
            # This recursively clones the existing item location to a draft location (the draft is
            # implicit, because modulestore is a Draft modulestore)
            _xmodule_recurse(
                existing_item,
                lambda i: modulestore().convert_to_draft(i.location))

    if data:
        # TODO Allow any scope.content fields not just "data" (exactly like the get below this)
        existing_item.data = data
    else:
        data = existing_item.get_explicitly_set_fields_by_scope(Scope.content)

    if children is not None:
        children_ids = [
            loc_mapper().translate_locator_to_location(
                BlockUsageLocator(child_locator)).url()
            for child_locator in children
        ]
        existing_item.children = children_ids

    # 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 existing_item.category == 'video':
            manage_video_subtitles_save(existing_item,
                                        request.user,
                                        old_metadata,
                                        generate_translation=True)

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

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

    if grader_type is not None:
        result.update(
            CourseGradingModel.update_section_grader_type(
                existing_item, grader_type, request.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':

        def _publish(block):
            # This is super gross, but prevents us from publishing something that
            # we shouldn't. Ideally, all modulestores would have a consistant
            # interface for publishing. However, as of now, only the DraftMongoModulestore
            # does, so we have to check for the attribute explicitly.
            store = get_modulestore(block.location)
            if hasattr(store, 'publish'):
                store.publish(block.location, request.user.id)

        _xmodule_recurse(existing_item, _publish)

    # Note that children aren't being returned until we have a use case.
    return JsonResponse(result)
Пример #18
0
def create_xblock(parent_locator, user, category, display_name, boilerplate=None, is_entrance_exam=False):
    """
    Performs the actual grunt work of creating items/xblocks -- knows nothing about requests, views, etc.
    """
    store = modulestore()
    usage_key = usage_key_with_run(parent_locator)
    with store.bulk_operations(usage_key.course_key):
        parent = store.get_item(usage_key)
        dest_usage_key = usage_key.replace(category=category, name=uuid4().hex)

        # get the metadata, display_name, and definition from the caller
        metadata = {}
        data = None
        template_id = boilerplate
        if template_id:
            clz = parent.runtime.load_block_type(category)
            if clz is not None:
                template = clz.get_template(template_id)
                if template is not None:
                    metadata = template.get('metadata', {})
                    data = template.get('data')

        if display_name is not None:
            metadata['display_name'] = display_name

        # We should use the 'fields' kwarg for newer module settings/values (vs. metadata or data)
        fields = {}

        # Entrance Exams: Chapter module positioning
        child_position = None
        if settings.FEATURES.get('ENTRANCE_EXAMS', False):
            if category == 'chapter' and is_entrance_exam:
                fields['is_entrance_exam'] = is_entrance_exam
                fields['in_entrance_exam'] = True  # Inherited metadata, all children will have it
                child_position = 0

        # TODO need to fix components that are sending definition_data as strings, instead of as dicts
        # For now, migrate them into dicts here.
        if isinstance(data, basestring):
            data = {'data': data}

        created_block = store.create_child(
            user.id,
            usage_key,
            dest_usage_key.block_type,
            block_id=dest_usage_key.block_id,
            fields=fields,
            definition_data=data,
            metadata=metadata,
            runtime=parent.runtime,
            position=child_position,
        )

        # Entrance Exams: Grader assignment
        if settings.FEATURES.get('ENTRANCE_EXAMS', False):
            course_key = usage_key.course_key
            course = store.get_course(course_key)
            if hasattr(course, 'entrance_exam_enabled') and course.entrance_exam_enabled:
                if category == 'sequential' and parent_locator == course.entrance_exam_id:
                    # Clean up any pre-existing entrance exam graders
                    remove_entrance_exam_graders(course_key, user)
                    grader = {
                        "type": GRADER_TYPES['ENTRANCE_EXAM'],
                        "min_count": 0,
                        "drop_count": 0,
                        "short_label": "Entrance",
                        "weight": 0
                    }
                    grading_model = CourseGradingModel.update_grader_from_json(
                        course.id,
                        grader,
                        user
                    )
                    CourseGradingModel.update_section_grader_type(
                        created_block,
                        grading_model['type'],
                        user
                    )

        # VS[compat] cdodge: This is a hack because static_tabs also have references from the course module, so
        # if we add one then we need to also add it to the policy information (i.e. metadata)
        # we should remove this once we can break this reference from the course to static tabs
        if category == 'static_tab':

            dog_stats_api.increment(
                DEPRECATION_VSCOMPAT_EVENT,
                tags=(
                    "location:create_xblock_static_tab",
                    u"course:{}".format(unicode(dest_usage_key.course_key)),
                )
            )

            display_name = display_name or _("Empty")  # Prevent name being None
            course = store.get_course(dest_usage_key.course_key)
            course.tabs.append(
                StaticTab(
                    name=display_name,
                    url_slug=dest_usage_key.name,
                )
            )
            store.update_item(course, user.id)

        return created_block
Пример #19
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)
Пример #20
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_item(user.id, usage_key.course_key,
                                              usage_key.block_type,
                                              usage_key.block_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:
                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 = []
        for child in children:
            child_usage_key = UsageKey.from_string(child)
            child_usage_key = child_usage_key.replace(course_key=modulestore(
            ).fill_in_run(child_usage_key.course_key))
            children_usage_keys.append(child_usage_key)
        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)