def test_join_description_path(self): """Test for join_description_path""" self.assertEqual(api.join_description_paths(""), "") self.assertEqual(api.join_description_paths("", "foo"), "foo") self.assertEqual(api.join_description_paths("foo", ""), "foo") self.assertEqual(api.join_description_paths("foo", "", "bar"), "foo / bar") self.assertEqual(api.join_description_paths("foo", "bar", "b az"), "foo / bar / b az")
def test_join_description_path(self): """Test for join_description_path""" self.assertEqual(api.join_description_paths(''), '') self.assertEqual(api.join_description_paths('', 'foo'), 'foo') self.assertEqual(api.join_description_paths('foo', ''), 'foo') self.assertEqual(api.join_description_paths('foo', '', 'bar'), 'foo / bar') self.assertEqual(api.join_description_paths('foo', 'bar', 'b az'), 'foo / bar / b az')
def test_update_description_path(self): """Tests for update_description_path""" # after created a resource without parent has the description path # equal to the title self.assertIsNone(self.resource.parent) self.assertEqual(self.resource.title, self.resource.description_path) # changing the title does not update the description path automatically self.resource.title = "123 xyz" self.resource.save() self.assertNotEqual(self.resource.title, self.resource.description_path) # update the description path api.update_description_path(self.resource) self.assertEqual(self.resource.title, self.resource.description_path) # create a child resource child_res = self.create_resource(parent=self.resource) # the description path is the combination of the child resource title # and the parent description path self.assertEqual( child_res.description_path, api.join_description_paths(self.resource.description_path, child_res.title) ) # change both resources title self.resource.title = "1234 xyza" self.resource.save() child_res.title = "foo 1234" child_res.save() # note: child_res.parent and self.resource are 2 different instances # of the same record, but they need to be refreshed separately # after a change made to one of them child_res.parent.refresh_from_db() # update the description path of the child # will not update the parent's one api.update_description_path(child_res) self.assertNotEqual(self.resource.title, self.resource.description_path) self.assertEqual( child_res.description_path, api.join_description_paths(self.resource.description_path, child_res.title) ) # but the parent's update can be forced api.update_description_path(child_res, force_parent_update=True) self.resource.refresh_from_db() self.assertEqual(self.resource.title, self.resource.description_path) self.assertEqual( child_res.description_path, api.join_description_paths(self.resource.description_path, child_res.title) ) # removing the description path of the parent self.resource.description_path = "" self.resource.title = "999 new title" self.resource.save() child_res.parent.refresh_from_db() # the update of the child will update the parent without forcing it api.update_description_path(child_res) self.resource.refresh_from_db() self.assertEqual(self.resource.title, self.resource.description_path)
def test_join_description_path(self): """Test for join_description_path""" self.assertEqual( api.join_description_paths(''), '' ) self.assertEqual( api.join_description_paths('', 'foo'), 'foo' ) self.assertEqual( api.join_description_paths('foo', ''), 'foo' ) self.assertEqual( api.join_description_paths('foo', '', 'bar'), 'foo / bar' ) self.assertEqual( api.join_description_paths('foo', 'bar', 'b az'), 'foo / bar / b az' )
def import_children(course, element, parent, parent_dpath): """ Create LearningResource instances for each element of an XML tree. Args: course (learningresources.models.Course): Course element (lxml.etree): XML element within xbundle parent (learningresources.models.LearningResource): Parent LearningResource parent_dpath (unicode): parent description path Returns: None """ # pylint: disable=too-many-locals title = element.attrib.get("display_name", "MISSING") mpath = etree.ElementTree(element).getpath(element) dpath = join_description_paths(parent_dpath, title) resource = create_resource( course=course, parent=parent, resource_type=element.tag, title=title, content_xml=etree.tostring(element), mpath=mpath, url_name=element.attrib.get("url_name", None), dpath=dpath, ) target = "/static/" if element.tag == "video": subname = get_video_sub(element) if subname != "": assets = StaticAsset.objects.filter( course__id=resource.course_id, asset=course_asset_basepath(course, subname), ) for asset in assets: resource.static_assets.add(asset) else: # Recursively find all sub-elements, looking for anything which # refers to /static/. Then make the association between the # LearningResource and StaticAsset if the StaticAsset exists. # This is like doing soup.findAll("a") and checking for whether # "/static/" is in the href, which would work but also requires # more code to check for link, img, iframe, script, and others, # and within those, check for href or src existing. soup = BeautifulSoup(etree.tostring(element), 'lxml') for child in soup.findAll(): for _, val in child.attrs.items(): try: if val.startswith(target): path = val[len(target):] try: asset = StaticAsset.objects.get( course__id=resource.course_id, asset=course_asset_basepath(course, path), ) resource.static_assets.add(asset) except StaticAsset.DoesNotExist: continue except AttributeError: continue # not a string for child in element.getchildren(): if child.tag in DESCRIPTOR_TAGS: import_children(course, child, resource, dpath)
def test_update_description_path(self): """Tests for update_description_path""" # after created a resource without parent has the description path # equal to the title self.assertIsNone(self.resource.parent) self.assertEqual( self.resource.title, self.resource.description_path ) # changing the title does not update the description path automatically self.resource.title = '123 xyz' self.resource.save() self.assertNotEqual( self.resource.title, self.resource.description_path ) # update the description path api.update_description_path(self.resource) self.assertEqual( self.resource.title, self.resource.description_path ) # create a child resource child_res = self.create_resource( parent=self.resource ) # the description path is the combination of the child resource title # and the parent description path self.assertEqual( child_res.description_path, api.join_description_paths( self.resource.description_path, child_res.title ) ) # change both resources title self.resource.title = '1234 xyza' self.resource.save() child_res.title = 'foo 1234' child_res.save() # note: child_res.parent and self.resource are 2 different instances # of the same record, but they need to be refreshed separately # after a change made to one of them child_res.parent.refresh_from_db() # update the description path of the child # will not update the parent's one api.update_description_path(child_res) self.assertNotEqual( self.resource.title, self.resource.description_path ) self.assertEqual( child_res.description_path, api.join_description_paths( self.resource.description_path, child_res.title ) ) # but the parent's update can be forced api.update_description_path(child_res, force_parent_update=True) self.resource.refresh_from_db() self.assertEqual( self.resource.title, self.resource.description_path ) self.assertEqual( child_res.description_path, api.join_description_paths( self.resource.description_path, child_res.title ) ) # removing the description path of the parent self.resource.description_path = '' self.resource.title = '999 new title' self.resource.save() child_res.parent.refresh_from_db() # the update of the child will update the parent without forcing it api.update_description_path(child_res) self.resource.refresh_from_db() self.assertEqual( self.resource.title, self.resource.description_path ) # update the title of the child to the "missing title" string self.resource.title = api.MissingTitle.for_title_field self.resource.save() api.update_description_path(self.resource) self.assertEqual( self.resource.title, api.MissingTitle.for_title_field ) self.assertEqual( self.resource.description_path, api.MissingTitle.for_desc_path_field ) # update also the child child_res.parent.refresh_from_db() api.update_description_path(child_res) self.assertEqual( child_res.description_path, api.join_description_paths( api.MissingTitle.for_desc_path_field, child_res.title ) )
def import_children(course, element, parent, parent_dpath): """ Create LearningResource instances for each element of an XML tree. Args: course (learningresources.models.Course): Course element (lxml.etree): XML element within xbundle parent (learningresources.models.LearningResource): Parent LearningResource parent_dpath (unicode): parent description path Returns: None """ # pylint: disable=too-many-locals title = element.attrib.get("display_name", MissingTitle.for_title_field) desc_path = title if desc_path == MissingTitle.for_title_field: desc_path = MissingTitle.for_desc_path_field mpath = etree.ElementTree(element).getpath(element) dpath = join_description_paths(parent_dpath, desc_path) resource = create_resource( course=course, parent=parent, resource_type=element.tag, title=title, content_xml=etree.tostring(element), mpath=mpath, url_name=element.attrib.get("url_name", None), dpath=dpath, ) # temp variable to store static assets for bulk insert static_assets_to_save = set() target = "/static/" if element.tag == "video": subname = get_video_sub(element) if subname != "": assets = StaticAsset.objects.filter( course__id=resource.course_id, asset=course_asset_basepath(course, subname), ) for asset in assets: static_assets_to_save.add((resource, asset)) else: # Recursively find all sub-elements, looking for anything which # refers to /static/. Then make the association between the # LearningResource and StaticAsset if the StaticAsset exists. # This is like doing soup.findAll("a") and checking for whether # "/static/" is in the href, which would work but also requires # more code to check for link, img, iframe, script, and others, # and within those, check for href or src existing. soup = BeautifulSoup(etree.tostring(element), 'lxml') for child in soup.findAll(): for _, val in child.attrs.items(): try: if val.startswith(target): path = val[len(target):] try: asset = StaticAsset.objects.get( course__id=resource.course_id, asset=course_asset_basepath(course, path), ) static_assets_to_save.add((resource, asset)) except StaticAsset.DoesNotExist: continue except AttributeError: continue # not a string # Bulk insert of static assets # Using this approach to avoid signals during the learning resource .save() # Each signal triggers a reindex of the learning resource that is useless # during import because all the learning resources are indexed in bulk at # the end of the import anyway ThroughModel = LearningResource.static_assets.through ThroughModel.objects.bulk_create([ ThroughModel(learningresource_id=resource.id, staticasset_id=asset.id) for resource, asset in static_assets_to_save ]) for child in element.getchildren(): if child.tag in DESCRIPTOR_TAGS: import_children(course, child, resource, dpath)
def import_children(course, element, parent, parent_dpath): """ Create LearningResource instances for each element of an XML tree. Args: course (learningresources.models.Course): Course element (lxml.etree): XML element within xbundle parent (learningresources.models.LearningResource): Parent LearningResource parent_dpath (unicode): parent description path Returns: None """ # pylint: disable=too-many-locals title = element.attrib.get( "display_name", MissingTitle.for_title_field) desc_path = title if desc_path == MissingTitle.for_title_field: desc_path = MissingTitle.for_desc_path_field mpath = etree.ElementTree(element).getpath(element) dpath = join_description_paths(parent_dpath, desc_path) resource = create_resource( course=course, parent=parent, resource_type=element.tag, title=title, content_xml=etree.tostring(element), mpath=mpath, url_name=element.attrib.get("url_name", None), dpath=dpath, ) # temp variable to store static assets for bulk insert static_assets_to_save = set() target = "/static/" if element.tag == "video": subname = get_video_sub(element) if subname != "": assets = StaticAsset.objects.filter( course__id=resource.course_id, asset=course_asset_basepath(course, subname), ) for asset in assets: static_assets_to_save.add((resource, asset)) else: # Recursively find all sub-elements, looking for anything which # refers to /static/. Then make the association between the # LearningResource and StaticAsset if the StaticAsset exists. # This is like doing soup.findAll("a") and checking for whether # "/static/" is in the href, which would work but also requires # more code to check for link, img, iframe, script, and others, # and within those, check for href or src existing. soup = BeautifulSoup(etree.tostring(element), 'lxml') for child in soup.findAll(): for _, val in child.attrs.items(): try: if val.startswith(target): path = val[len(target):] try: asset = StaticAsset.objects.get( course__id=resource.course_id, asset=course_asset_basepath(course, path), ) static_assets_to_save.add((resource, asset)) except StaticAsset.DoesNotExist: continue except AttributeError: continue # not a string # Bulk insert of static assets # Using this approach to avoid signals during the learning resource .save() # Each signal triggers a reindex of the learning resource that is useless # during import because all the learning resources are indexed in bulk at # the end of the import anyway ThroughModel = LearningResource.static_assets.through ThroughModel.objects.bulk_create( [ ThroughModel( learningresource_id=resource.id, staticasset_id=asset.id ) for resource, asset in static_assets_to_save ] ) for child in element.getchildren(): if child.tag in DESCRIPTOR_TAGS: import_children(course, child, resource, dpath)