def test_ambiguous_plugins(): # We can load ok blocks even if there are bad blocks. cls = XBlock.load_class("good_block") assert_is(cls, UnambiguousBlock) # Trying to load bad blocks raises an exception. expected_msg = ( "Ambiguous entry points for bad_block: " "xblock.test.test_plugin.AmbiguousBlock1, " "xblock.test.test_plugin.AmbiguousBlock2" ) with assert_raises_regexp(AmbiguousPluginError, expected_msg): XBlock.load_class("bad_block") # We can use our own function as the select function. class MyOwnException(Exception): """We'll raise this from `boom`.""" pass def boom(identifier, entry_points): """A select function to prove user-defined functions are called.""" assert len(entry_points) == 2 assert identifier == "bad_block" raise MyOwnException("This is boom") with assert_raises_regexp(MyOwnException, "This is boom"): XBlock.load_class("bad_block", select=boom)
def test_nosuch_plugin(): # We can provide a default class to return for missing plugins. cls = XBlock.load_class("nosuch_block", default=UnambiguousBlock) assert_is(cls, UnambiguousBlock) # If we don't provide a default class, an exception is raised. with assert_raises_regexp(PluginMissingError, "nosuch_block"): XBlock.load_class("nosuch_block")
def test_nosuch_plugin(): # We can provide a default class to return for missing plugins. cls = XBlock.load_class("nosuch_block", default=UnambiguousBlock) assert cls is UnambiguousBlock # If we don't provide a default class, an exception is raised. with pytest.raises(PluginMissingError, match="nosuch_block"): XBlock.load_class("nosuch_block")
def test_plugin_caching(): plugin.PLUGIN_CACHE = {} assert_equals(_num_plugins_cached(), 0) XBlock.load_class("thumbs") assert_equals(_num_plugins_cached(), 1) XBlock.load_class("thumbs") assert_equals(_num_plugins_cached(), 1)
def test_plugin_caching(): plugin.PLUGIN_CACHE = {} assert _num_plugins_cached() == 0 XBlock.load_class("thumbs") assert _num_plugins_cached() == 1 XBlock.load_class("thumbs") assert _num_plugins_cached() == 1
def load_from_json(json_data, system, default_class=None, parent_xblock=None): """ This method instantiates the correct subclass of XModuleDescriptor based on the contents of json_data. It does not persist it and can create one which has no usage id. parent_xblock is used to compute inherited metadata as well as to append the new xblock. json_data: - 'location' : must have this field - 'category': the xmodule category (required or location must be a Location) - 'metadata': a dict of locally set metadata (not inherited) - 'children': a list of children's usage_ids w/in this course - 'definition': - '_id' (optional): the usage_id of this. Will generate one if not given one. """ class_ = XBlock.load_class( json_data.get("category", json_data.get("location", {}).get("category")), default_class, select=settings.XBLOCK_SELECT_FUNCTION, ) usage_id = json_data.get("_id", None) if not "_inherited_settings" in json_data and parent_xblock is not None: json_data["_inherited_settings"] = parent_xblock.xblock_kvs.inherited_settings.copy() json_fields = json_data.get("fields", {}) for field_name in inheritance.InheritanceMixin.fields: if field_name in json_fields: json_data["_inherited_settings"][field_name] = json_fields[field_name] new_block = system.xblock_from_json(class_, usage_id, json_data) if parent_xblock is not None: parent_xblock.children.append(new_block.scope_ids.usage_id) # decache pending children field settings parent_xblock.save() return new_block
def test_all_blocks_excluded_from_completion(self, blockclass): xblock = XBlock.load_class(blockclass) self.assertEqual( XBlockCompletionMode.get_mode(xblock), XBlockCompletionMode.EXCLUDED, "Block {!r} did not have completion mode 'excluded'".format(xblock), )
def xblock_type_display_name(xblock, default_display_name=None): """ Returns the display name for the specified type of xblock. Note that an instance can be passed in for context dependent names, e.g. a vertical beneath a sequential is a Unit. :param xblock: An xblock instance or the type of xblock. :param default_display_name: The default value to return if no display name can be found. :return: """ if hasattr(xblock, "category"): category = xblock.category if category == "vertical" and not is_unit(xblock): return _("Vertical") else: category = xblock if category == "chapter": return _("Section") elif category == "sequential": return _("Subsection") elif category == "vertical": return _("Unit") component_class = XBlock.load_class(category, select=settings.XBLOCK_SELECT_FUNCTION) if hasattr(component_class, "display_name") and component_class.display_name.default: return _(component_class.display_name.default) else: return default_display_name
def _load_mixed_class(category): """ Load an XBlock by category name, and apply all defined mixins """ component_class = XBlock.load_class(category, select=settings.XBLOCK_SELECT_FUNCTION) mixologist = Mixologist(settings.XBLOCK_MIXINS) return mixologist.mix(component_class)
def setUp(self): super(TestCourseDetailSerializer, self).setUp() # update the expected_data to include the 'overview' data. about_descriptor = XBlock.load_class('about') overview_template = about_descriptor.get_template('overview.yaml') self.expected_data['overview'] = overview_template.get('data')
def construct_xblock(self, plugin_name, field_data, scope_ids, default_class=None, *args, **kwargs): """ Construct a new xblock of the type identified by plugin_name, passing *args and **kwargs into __init__ """ block_class = XBlock.load_class(plugin_name, default_class) return self.construct_xblock_from_class(cls=block_class, field_data=field_data, scope_ids=scope_ids, *args, **kwargs)
def create_block_from_xml(xml_data, system, org=None, course=None, default_class=None): """ Create an XBlock instance from XML data. `xml_data' is a string containing valid xml. `system` is an XMLParsingSystem. `org` and `course` are optional strings that will be used in the generated block's url identifiers. `default_class` is the class to instantiate of the XML indicates a class that can't be loaded. Returns the fully instantiated XBlock. """ node = etree.fromstring(xml_data) raw_class = XBlock.load_class(node.tag, default_class, select=prefer_xmodules) xblock_class = system.mixologist.mix(raw_class) # leave next line commented out - useful for low-level debugging # log.debug('[create_block_from_xml] tag=%s, class=%s' % (node.tag, xblock_class)) url_name = node.get('url_name', node.get('slug')) location = Location('i4x', org, course, node.tag, url_name) scope_ids = ScopeIds(None, location.category, location, location) xblock = xblock_class.parse_xml(node, system, scope_ids) return xblock
def xblock_type_display_name(xblock, default_display_name=None): """ Returns the display name for the specified type of xblock. Note that an instance can be passed in for context dependent names, e.g. a vertical beneath a sequential is a Unit. :param xblock: An xblock instance or the type of xblock. :param default_display_name: The default value to return if no display name can be found. :return: """ if hasattr(xblock, 'category'): category = xblock.category if category == 'vertical' and not is_unit(xblock): return _('Vertical') else: category = xblock if category == 'chapter': return _('Section') elif category == 'sequential': return _('Subsection') elif category == 'vertical': return _('Unit') component_class = XBlock.load_class(category, select=settings.XBLOCK_SELECT_FUNCTION) if hasattr(component_class, 'display_name') and component_class.display_name.default: return _(component_class.display_name.default) # pylint: disable=translation-of-non-string else: return default_display_name
def _process_node(node, usage_factory): """Give the XBlock classes a chance to manipulate the tree.""" block_cls = XBlock.load_class(node.block_name) node = block_cls.preprocess_input(node, usage_factory) kids = [_process_node(kid, usage_factory) for kid in node.children] if any(old is not new for old, new in zip(node.children, kids)): node = usage_factory(node.block_name, kids, node.initial_state, node.def_id) node = block_cls.postprocess_input(node, usage_factory) return node
def create_xblock(usage, student_id=None): """Create an XBlock instance. This will be invoked to create new instances for every request. """ block_cls = XBlock.load_class(usage.block_name) runtime = WorkbenchRuntime(block_cls, student_id, usage) model = DbModel(MEMORY_KVS, block_cls, student_id, usage) block = block_cls(runtime, model) return block
def package_resource(_request, block_type, resource): """ Wrapper for `pkg_resources` that tries to access a resource and, if it is not found, raises an Http404 error. """ try: xblock_class = XBlock.load_class(block_type) content = xblock_class.open_local_resource(resource) except Exception: # pylint: disable-msg=broad-except raise Http404 mimetype, _ = mimetypes.guess_type(resource) return HttpResponse(content, mimetype=mimetype)
def construct_xblock(self, block_type, scope_ids, field_data=None, default_class=None, *args, **kwargs): r""" Construct a new xblock of the type identified by block_type, passing \*args and \*\*kwargs into `__init__`. """ block_class = XBlock.load_class(block_type, default_class) return self.construct_xblock_from_class( cls=block_class, scope_ids=scope_ids, field_data=field_data, *args, **kwargs )
def get(self, block_type, resource): xblock_class = XBlock.load_class(block_type) mimetype = mimetypes.guess_type(resource)[0] if mimetype is None: mimetype = 'application/octet-stream' self.response.status = 200 self.response.headers['Content-Type'] = mimetype self.response.cache_control.no_cache = None self.response.cache_control.public = 'public' self.response.cache_control.max_age = 600 self.response.write(xblock_class.open_local_resource(resource).read())
def partition_fields_by_scope(self, category, fields): """ Return dictionary of {scope: {field1: val, ..}..} for the fields of this potential xblock :param category: the xblock category :param fields: the dictionary of {fieldname: value} """ if fields is None: return {} cls = self.mixologist.mix(XBlock.load_class(category, select=prefer_xmodules)) result = collections.defaultdict(dict) for field_name, value in fields.iteritems(): field = getattr(cls, field_name) result[field.scope][field_name] = value return result
def xblock_resource(request, block_type, uri): # pylint: disable=unused-argument """ Return a package resource for the specified XBlock. """ try: xblock_class = XBlock.load_class(block_type, select=settings.XBLOCK_SELECT_FUNCTION) content = xblock_class.open_local_resource(uri) except IOError: log.info('Failed to load xblock resource', exc_info=True) raise Http404 except Exception: # pylint: disable=broad-except log.error('Failed to load xblock resource', exc_info=True) raise Http404 mimetype, _ = mimetypes.guess_type(uri) return HttpResponse(content, mimetype=mimetype)
def _usage_id_from_node(self, node): """Create a new usage id from an XML dom node. :param node: The DOM node to interpret. :type node: `lxml.etree.Element` """ block_type = node.tag # TODO: a way for this node to be a usage to an existing definition? def_id = self.usage_store.create_definition(block_type) usage_id = self.usage_store.create_usage(def_id) keys = ScopeIds(UserScope.NONE, block_type, def_id, usage_id) block_class = self.mixologist.mix(XBlock.load_class(block_type)) block = block_class.parse_xml(node, self, keys) block.save() return usage_id
def package_resource(_request, block_type, resource): """ Wrapper for `pkg_resources` that tries to access a resource and, if it is not found, raises an Http404 error. """ try: xblock_class = XBlock.load_class(block_type) except PluginMissingError: try: xblock_class = XBlockAside.load_class(block_type) except PluginMissingError: raise Http404 try: content = xblock_class.open_local_resource(resource) except Exception: raise Http404 mimetype, _ = mimetypes.guess_type(resource) return HttpResponse(content, content_type=mimetype)
def xblock_resource(request, block_type, uri): # pylint: disable=unused-argument """ Return a package resource for the specified XBlock. """ try: # Figure out what the XBlock class is from the block type, and # then open whatever resource has been requested. xblock_class = XBlock.load_class(block_type, select=settings.XBLOCK_SELECT_FUNCTION) content = xblock_class.open_local_resource(uri) except IOError: log.info('Failed to load xblock resource', exc_info=True) raise Http404 except Exception: log.error('Failed to load xblock resource', exc_info=True) raise Http404 mimetype, _ = mimetypes.guess_type(uri) return HttpResponse(content, content_type=mimetype)
def _create_item(request): """View for create items.""" parent_locator = BlockUsageLocator(request.json['parent_locator']) parent_location = loc_mapper().translate_locator_to_location(parent_locator) category = request.json['category'] display_name = request.json.get('display_name') if not has_course_access(request.user, parent_location): raise PermissionDenied() parent = get_modulestore(category).get_item(parent_location) dest_location = parent_location.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 is not None: clz = XBlock.load_class(category, select=prefer_xmodules) 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 get_modulestore(category).create_and_save_xmodule( dest_location, definition_data=data, metadata=metadata, system=parent.runtime, ) # TODO replace w/ nicer accessor if not 'detached' in parent.runtime.load_block_type(category)._class_tags: get_modulestore(parent.location).update_children(parent_location, parent.children + [dest_location.url()]) course_location = loc_mapper().translate_locator_to_location(parent_locator, get_course=True) locator = loc_mapper().translate_location(course_location.course_id, dest_location, False, True) return JsonResponse({"locator": unicode(locator)})
def create_course(self, org, course, run, user_id, fields=None, runtime=None, **kwargs): """ Creates any necessary other things for the course as a side effect and doesn't return anything useful. The real subclass should call this before it returns the course. """ # clone a default 'about' overview module as well about_location = self.make_course_key(org, course, run).make_usage_key('about', 'overview') about_descriptor = XBlock.load_class('about') overview_template = about_descriptor.get_template('overview.yaml') self.create_item( user_id, about_location.course_key, about_location.block_type, block_id=about_location.block_id, definition_data={'data': overview_template.get('data')}, metadata=overview_template.get('metadata'), runtime=runtime, continue_version=True, )
def xblock_resource(request, block_type, uri): # pylint: disable=unused-argument """ Return a package resource for the specified XBlock. """ try: xblock_class = XBlock.load_class(block_type, select=settings.XBLOCK_SELECT_FUNCTION) # Note: in debug mode, return any file rather than going through the XBlock which # will only return public files. This allows unbundled files to be served up # during development. if settings.DEBUG: content = pkg_resources.resource_stream(xblock_class.__module__, uri) else: content = xblock_class.open_local_resource(uri) except IOError: log.info('Failed to load xblock resource', exc_info=True) raise Http404 except Exception: # pylint: disable=broad-except log.error('Failed to load xblock resource', exc_info=True) raise Http404 mimetype, _ = mimetypes.guess_type(uri) return HttpResponse(content, content_type=mimetype)
def scorable_block_completion(sender, **kwargs): # pylint: disable=unused-argument """ When a problem is scored, submit a new BlockCompletion for that block. """ if not waffle.waffle().is_enabled(waffle.ENABLE_COMPLETION_TRACKING): return course_key = CourseKey.from_string(kwargs['course_id']) block_key = UsageKey.from_string(kwargs['usage_id']) block_cls = XBlock.load_class(block_key.block_type) if getattr(block_cls, 'completion_mode', XBlockCompletionMode.COMPLETABLE) != XBlockCompletionMode.COMPLETABLE: return if getattr(block_cls, 'has_custom_completion', False): return user = User.objects.get(id=kwargs['user_id']) if kwargs.get('score_deleted'): completion = 0.0 else: completion = 1.0 BlockCompletion.objects.submit_completion( user=user, course_key=course_key, block_key=block_key, completion=completion, )
def test_load_class(self): vc = XBlock.load_class('sequential') vc_str = "<class 'xmodule.seq_module.SequenceBlock'>" assert str(vc) == vc_str
def _create(cls, target_class, **kwargs): """ Uses ``**kwargs``: :parent_location: (required): the location of the parent module (e.g. the parent course or section) :category: the category of the resulting item. :data: (optional): the data for the item (e.g. XML problem definition for a problem item) :display_name: (optional): the display name of the item :metadata: (optional): dictionary of metadata attributes :boilerplate: (optional) the boilerplate for overriding field values :publish_item: (optional) whether or not to publish the item (default is True) :target_class: is ignored """ # All class attributes (from this class and base classes) are # passed in via **kwargs. However, some of those aren't actual field values, # so pop those off for use separately # catch any old style users before they get into trouble assert 'template' not in kwargs parent_location = kwargs.pop('parent_location', None) data = kwargs.pop('data', None) category = kwargs.pop('category', None) display_name = kwargs.pop('display_name', None) metadata = kwargs.pop('metadata', {}) location = kwargs.pop('location') user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test) publish_item = kwargs.pop('publish_item', True) assert isinstance(location, UsageKey) assert location != parent_location store = kwargs.pop('modulestore') # This code was based off that in cms/djangoapps/contentstore/views.py parent = kwargs.pop('parent', None) or store.get_item(parent_location) with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): if 'boilerplate' in kwargs: template_id = kwargs.pop('boilerplate') clz = XBlock.load_class(category, select=prefer_xmodules) template = clz.get_template(template_id) assert template is not None metadata.update(template.get('metadata', {})) if not isinstance(data, basestring): data.update(template.get('data')) # replace the display name with an optional parameter passed in from the caller if display_name is not None: metadata['display_name'] = display_name runtime = parent.runtime if parent else None store.create_item( user_id, location.course_key, location.block_type, block_id=location.block_id, metadata=metadata, definition_data=data, runtime=runtime ) module = store.get_item(location) for attr, val in kwargs.items(): setattr(module, attr, val) # Save the attributes we just set module.save() store.update_item(module, user_id) # 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': course = store.get_course(location.course_key) course.tabs.append( StaticTab( name=display_name, url_slug=location.name, ) ) store.update_item(course, user_id) # parent and publish the item, so it can be accessed if 'detached' not in module._class_tags: parent.children.append(location) store.update_item(parent, user_id) if publish_item: store.publish(parent.location, user_id) elif publish_item: store.publish(location, user_id) # return the published item return store.get_item(location)
def setUp(self): super(TestCustomBackends, self).setUp() self.player = {} for backend in self.backends: player_class = XBlock.load_class(backend) self.player[backend] = player_class
def _create(cls, target_class, **kwargs): """ Uses ``**kwargs``: :parent_location: (required): the location of the parent module (e.g. the parent course or section) :category: the category of the resulting item. :data: (optional): the data for the item (e.g. XML problem definition for a problem item) :display_name: (optional): the display name of the item :metadata: (optional): dictionary of metadata attributes :boilerplate: (optional) the boilerplate for overriding field values :target_class: is ignored """ # All class attributes (from this class and base classes) are # passed in via **kwargs. However, some of those aren't actual field values, # so pop those off for use separately # catch any old style users before they get into trouble assert 'template' not in kwargs parent_location = kwargs.pop('parent_location', None) data = kwargs.pop('data', None) category = kwargs.pop('category', None) display_name = kwargs.pop('display_name', None) metadata = kwargs.pop('metadata', {}) location = kwargs.pop('location') assert isinstance(location, Location) assert location != parent_location store = kwargs.pop('modulestore') # This code was based off that in cms/djangoapps/contentstore/views.py parent = kwargs.pop('parent', None) or store.get_item(parent_location) if 'boilerplate' in kwargs: template_id = kwargs.pop('boilerplate') clz = XBlock.load_class(category, select=prefer_xmodules) template = clz.get_template(template_id) assert template is not None metadata.update(template.get('metadata', {})) if not isinstance(data, basestring): data.update(template.get('data')) # replace the display name with an optional parameter passed in from the caller if display_name is not None: metadata['display_name'] = display_name store.create_and_save_xmodule(location, metadata=metadata, definition_data=data) module = store.get_item(location) for attr, val in kwargs.items(): setattr(module, attr, val) # Save the attributes we just set module.save() store.update_item(module) if 'detached' not in module._class_tags: parent.children.append(location) store.update_item(parent, '**replace_user**') return store.get_item(location)
def _create(cls, target_class, **kwargs): """ Uses ``**kwargs``: :parent_location: (required): the location of the parent module (e.g. the parent course or section) :category: the category of the resulting item. :data: (optional): the data for the item (e.g. XML problem definition for a problem item) :display_name: (optional): the display name of the item :metadata: (optional): dictionary of metadata attributes :boilerplate: (optional) the boilerplate for overriding field values :publish_item: (optional) whether or not to publish the item (default is True) :target_class: is ignored """ # All class attributes (from this class and base classes) are # passed in via **kwargs. However, some of those aren't actual field values, # so pop those off for use separately # catch any old style users before they get into trouble assert 'template' not in kwargs parent_location = kwargs.pop('parent_location', None) data = kwargs.pop('data', None) category = kwargs.pop('category', None) display_name = kwargs.pop('display_name', None) metadata = kwargs.pop('metadata', {}) location = kwargs.pop('location') user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test) publish_item = kwargs.pop('publish_item', True) assert isinstance(location, UsageKey) assert location != parent_location store = kwargs.pop('modulestore') # This code was based off that in cms/djangoapps/contentstore/views.py parent = kwargs.pop('parent', None) or store.get_item(parent_location) with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): if 'boilerplate' in kwargs: template_id = kwargs.pop('boilerplate') clz = XBlock.load_class(category, select=prefer_xmodules) template = clz.get_template(template_id) assert template is not None metadata.update(template.get('metadata', {})) if not isinstance(data, basestring): data.update(template.get('data')) # replace the display name with an optional parameter passed in from the caller if display_name is not None: metadata['display_name'] = display_name runtime = parent.runtime if parent else None store.create_item( user_id, location.course_key, location.block_type, block_id=location.block_id, metadata=metadata, definition_data=data, runtime=runtime ) module = store.get_item(location) for attr, val in kwargs.items(): setattr(module, attr, val) # Save the attributes we just set module.save() store.update_item(module, user_id) # 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': course = store.get_course(location.course_key) course.tabs.append( StaticTab( name=display_name, url_slug=location.name, ) ) store.update_item(course, user_id) # parent and publish the item, so it can be accessed if 'detached' not in module._class_tags: parent.children.append(location) store.update_item(parent, user_id) if publish_item: store.publish(parent.location, user_id) elif publish_item: store.publish(location, user_id) # return the published item return store.get_item(location)
def load_block_type(self, block_type): """ Returns a subclass of :class:`.XBlock` that corresponds to the specified `block_type`. """ return XBlock.load_class(block_type, self.default_class, self.select)
def _create(cls, target_class, **kwargs): """ Uses ``**kwargs``: :parent_location: (required): the location of the parent module (e.g. the parent course or section) :category: the category of the resulting item. :data: (optional): the data for the item (e.g. XML problem definition for a problem item) :display_name: (optional): the display name of the item :metadata: (optional): dictionary of metadata attributes :boilerplate: (optional) the boilerplate for overriding field values :target_class: is ignored """ # All class attributes (from this class and base classes) are # passed in via **kwargs. However, some of those aren't actual field values, # so pop those off for use separately # catch any old style users before they get into trouble assert 'template' not in kwargs parent_location = kwargs.pop('parent_location', None) data = kwargs.pop('data', None) category = kwargs.pop('category', None) display_name = kwargs.pop('display_name', None) metadata = kwargs.pop('metadata', {}) location = kwargs.pop('location') assert isinstance(location, Location) assert location != parent_location store = kwargs.pop('modulestore') # This code was based off that in cms/djangoapps/contentstore/views.py parent = kwargs.pop('parent', None) or store.get_item(parent_location) if 'boilerplate' in kwargs: template_id = kwargs.pop('boilerplate') clz = XBlock.load_class(category, select=prefer_xmodules) template = clz.get_template(template_id) assert template is not None metadata.update(template.get('metadata', {})) if not isinstance(data, basestring): data.update(template.get('data')) # replace the display name with an optional parameter passed in from the caller if display_name is not None: metadata['display_name'] = display_name store.create_and_save_xmodule(location, metadata=metadata, definition_data=data) module = store.get_item(location) for attr, val in kwargs.items(): setattr(module, attr, val) # Save the attributes we just set module.save() store.update_item(module) if 'detached' not in module._class_tags: parent.children.append(location) store.update_item(parent, '**replace_user**') return store.get_item(location)
def load_block_type(self, block_type): """ Returns a subclass of :class:`.XBlock` that corresponds to the specified `block_type`. """ return XBlock.load_class(block_type, self.default_class, self.select)
def _create(cls, target_class, **kwargs): # lint-amnesty, pylint: disable=arguments-differ, too-many-statements, unused-argument """ Uses ``**kwargs``: :parent_location: (required): the location of the parent module (e.g. the parent course or section) :category: the category of the resulting item. :data: (optional): the data for the item (e.g. XML problem definition for a problem item) :display_name: (optional): the display name of the item :metadata: (optional): dictionary of metadata attributes :boilerplate: (optional) the boilerplate for overriding field values :publish_item: (optional) whether or not to publish the item (default is True) :target_class: is ignored """ # All class attributes (from this class and base classes) are # passed in via **kwargs. However, some of those aren't actual field values, # so pop those off for use separately # catch any old style users before they get into trouble assert 'template' not in kwargs parent_location = kwargs.pop('parent_location', None) data = kwargs.pop('data', None) category = kwargs.pop('category', None) display_name = kwargs.pop('display_name', None) metadata = kwargs.pop('metadata', {}) location = kwargs.pop('location') user_id = kwargs.pop('user_id', ModuleStoreEnum.UserID.test) publish_item = kwargs.pop('publish_item', True) has_score = kwargs.pop('has_score', None) submission_start = kwargs.pop('submission_start', None) submission_end = kwargs.pop('submission_end', None) # Remove the descriptive_tag, it's just for generating display_name, # and doesn't need to be passed into the object constructor kwargs.pop('descriptive_tag') assert isinstance(location, UsageKey) assert location != parent_location store = kwargs.pop('modulestore') # This code was based off that in cms/djangoapps/contentstore/views.py parent = kwargs.pop('parent', None) or store.get_item(parent_location) if isinstance(data, (bytes, bytearray)): # data appears as bytes and data = data.decode('utf-8') with store.branch_setting(ModuleStoreEnum.Branch.draft_preferred): if 'boilerplate' in kwargs: template_id = kwargs.pop('boilerplate') clz = XBlock.load_class(category, select=prefer_xmodules) template = clz.get_template(template_id) assert template is not None metadata.update(template.get('metadata', {})) if not isinstance(data, str): data.update(template.get('data')) # replace the display name with an optional parameter passed in from the caller if display_name is not None: metadata['display_name'] = display_name module = store.create_child( user_id, parent.location, location.block_type, block_id=location.block_id, metadata=metadata, definition_data=data, runtime=parent.runtime, fields=kwargs, ) if has_score: module.has_score = has_score if submission_start: module.submission_start = submission_start if submission_end: module.submission_end = submission_end store.update_item(module, user_id) # 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': course = store.get_course(location.course_key) course.tabs.append( CourseTab.load('static_tab', name='Static Tab', url_slug=location.block_id)) store.update_item(course, user_id) # parent and publish the item, so it can be accessed if 'detached' not in module._class_tags: # lint-amnesty, pylint: disable=protected-access parent.children.append(location) store.update_item(parent, user_id) if publish_item: published_parent = store.publish(parent.location, user_id) # module is last child of parent return published_parent.get_children()[-1] else: return store.get_item(location) elif publish_item: return store.publish(location, user_id) else: return module