Commit 23d78fe7 by Don Mitchell

Merge pull request #4803 from edx/dhm/import_split_test

Dhm/import split test
parents b6f82a94 3b43f34b
...@@ -981,7 +981,7 @@ class TestEditSplitModule(ItemTest): ...@@ -981,7 +981,7 @@ class TestEditSplitModule(ItemTest):
# group_id_to_child and children have not changed yet. # group_id_to_child and children have not changed yet.
split_test = self._assert_children(2) split_test = self._assert_children(2)
group_id_to_child = split_test.group_id_to_child group_id_to_child = split_test.group_id_to_child.copy()
self.assertEqual(2, len(group_id_to_child)) self.assertEqual(2, len(group_id_to_child))
# Test environment and Studio use different module systems # Test environment and Studio use different module systems
......
...@@ -707,8 +707,8 @@ class EdxJSONEncoder(json.JSONEncoder): ...@@ -707,8 +707,8 @@ class EdxJSONEncoder(json.JSONEncoder):
ISO date strings ISO date strings
""" """
def default(self, obj): def default(self, obj):
if isinstance(obj, Location): if isinstance(obj, (CourseKey, UsageKey)):
return obj.to_deprecated_string() return unicode(obj)
elif isinstance(obj, datetime.datetime): elif isinstance(obj, datetime.datetime):
if obj.tzinfo is not None: if obj.tzinfo is not None:
if obj.utcoffset() is None: if obj.utcoffset() is None:
......
...@@ -170,15 +170,6 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -170,15 +170,6 @@ class MixedModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
# return the default store # return the default store
return self.default_modulestore return self.default_modulestore
# return the first store, as the default
return self.default_modulestore
@property
def default_modulestore(self):
"""
Return the default modulestore
"""
return self.modulestores[0]
def _get_modulestore_by_type(self, modulestore_type): def _get_modulestore_by_type(self, modulestore_type):
""" """
......
...@@ -229,7 +229,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -229,7 +229,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
# Convert the serialized fields values in self.cached_metadata # Convert the serialized fields values in self.cached_metadata
# to python values # to python values
metadata_to_inherit = self.cached_metadata.get(non_draft_loc.to_deprecated_string(), {}) metadata_to_inherit = self.cached_metadata.get(unicode(non_draft_loc), {})
inherit_metadata(module, metadata_to_inherit) inherit_metadata(module, metadata_to_inherit)
edit_info = json_data.get('edit_info') edit_info = json_data.get('edit_info')
...@@ -238,10 +238,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -238,10 +238,11 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
if not edit_info: if not edit_info:
module.edited_by = module.edited_on = module.subtree_edited_on = \ module.edited_by = module.edited_on = module.subtree_edited_on = \
module.subtree_edited_by = module.published_date = None module.subtree_edited_by = module.published_date = None
raw_metadata = json_data.get('metadata', {})
# published_date was previously stored as a list of time components instead of a datetime # published_date was previously stored as a list of time components instead of a datetime
if metadata.get('published_date'): if raw_metadata.get('published_date'):
module.published_date = datetime(*metadata.get('published_date')[0:6]).replace(tzinfo=UTC) module.published_date = datetime(*raw_metadata.get('published_date')[0:6]).replace(tzinfo=UTC)
module.published_by = metadata.get('published_by') module.published_by = raw_metadata.get('published_by')
# otherwise restore the stored editing information # otherwise restore the stored editing information
else: else:
module.edited_by = edit_info.get('edited_by') module.edited_by = edit_info.get('edited_by')
...@@ -267,7 +268,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -267,7 +268,7 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
""" """
Convert a single serialized UsageKey string in a ReferenceField into a UsageKey. Convert a single serialized UsageKey string in a ReferenceField into a UsageKey.
""" """
key = Location.from_deprecated_string(ref_string) key = Location.from_string(ref_string)
return key.replace(run=self.modulestore.fill_in_run(key.course_key).run) return key.replace(run=self.modulestore.fill_in_run(key.course_key).run)
def __setattr__(self, name, value): def __setattr__(self, name, value):
...@@ -280,22 +281,26 @@ class CachingDescriptorSystem(MakoDescriptorSystem): ...@@ -280,22 +281,26 @@ class CachingDescriptorSystem(MakoDescriptorSystem):
:param course_key: a CourseKey object for the given course :param course_key: a CourseKey object for the given course
:param jsonfields: a dict of the jsonified version of the fields :param jsonfields: a dict of the jsonified version of the fields
""" """
result = {}
for field_name, value in jsonfields.iteritems(): for field_name, value in jsonfields.iteritems():
if value:
field = class_.fields.get(field_name) field = class_.fields.get(field_name)
if field is None: if field is None:
continue continue
elif value is None:
result[field_name] = value
elif isinstance(field, Reference): elif isinstance(field, Reference):
jsonfields[field_name] = self._convert_reference_to_key(value) result[field_name] = self._convert_reference_to_key(value)
elif isinstance(field, ReferenceList): elif isinstance(field, ReferenceList):
jsonfields[field_name] = [ result[field_name] = [
self._convert_reference_to_key(ele) for ele in value self._convert_reference_to_key(ele) for ele in value
] ]
elif isinstance(field, ReferenceValueDict): elif isinstance(field, ReferenceValueDict):
for key, subvalue in value.iteritems(): result[field_name] = {
assert isinstance(subvalue, basestring) key: self._convert_reference_to_key(subvalue) for key, subvalue in value.iteritems()
value[key] = self._convert_reference_to_key(subvalue) }
return jsonfields else:
result[field_name] = value
return result
def lookup_item(self, location): def lookup_item(self, location):
""" """
...@@ -520,7 +525,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -520,7 +525,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
# manually pick it apart b/c the db has tag and we want as_published revision regardless # manually pick it apart b/c the db has tag and we want as_published revision regardless
location = as_published(Location._from_deprecated_son(result['_id'], course_id.run)) location = as_published(Location._from_deprecated_son(result['_id'], course_id.run))
location_url = location.to_deprecated_string() location_url = unicode(location)
if location_url in results_by_url: if location_url in results_by_url:
# found either draft or live to complement the other revision # found either draft or live to complement the other revision
existing_children = results_by_url[location_url].get('definition', {}).get('children', []) existing_children = results_by_url[location_url].get('definition', {}).get('children', [])
...@@ -1125,14 +1130,11 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -1125,14 +1130,11 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
therefore propagate subtree edit info up the tree therefore propagate subtree edit info up the tree
""" """
try: try:
definition_data = self._convert_reference_fields_to_strings( definition_data = self._serialize_scope(xblock, Scope.content)
xblock,
xblock.get_explicitly_set_fields_by_scope()
)
now = datetime.now(UTC) now = datetime.now(UTC)
payload = { payload = {
'definition.data': definition_data, 'definition.data': definition_data,
'metadata': self._convert_reference_fields_to_strings(xblock, own_metadata(xblock)), 'metadata': self._serialize_scope(xblock, Scope.settings),
'edit_info.edited_on': now, 'edit_info.edited_on': now,
'edit_info.edited_by': user_id, 'edit_info.edited_by': user_id,
'edit_info.subtree_edited_on': now, 'edit_info.subtree_edited_on': now,
...@@ -1144,7 +1146,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -1144,7 +1146,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
payload['edit_info.published_by'] = user_id payload['edit_info.published_by'] = user_id
if xblock.has_children: if xblock.has_children:
children = self._convert_reference_fields_to_strings(xblock, {'children': xblock.children}) children = self._serialize_scope(xblock, Scope.children)
payload.update({'definition.children': children['children']}) payload.update({'definition.children': children['children']})
self._update_single_item(xblock.scope_ids.usage_id, payload) self._update_single_item(xblock.scope_ids.usage_id, payload)
...@@ -1185,25 +1187,27 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -1185,25 +1187,27 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
return xblock return xblock
def _convert_reference_fields_to_strings(self, xblock, jsonfields): def _serialize_scope(self, xblock, scope):
""" """
Find all fields of type reference and convert the payload from UsageKeys to deprecated strings Find all fields of type reference and convert the payload from UsageKeys to deprecated strings
:param xblock: the XBlock class :param xblock: the XBlock class
:param jsonfields: a dict of the jsonified version of the fields :param jsonfields: a dict of the jsonified version of the fields
""" """
assert isinstance(jsonfields, dict) jsonfields = {}
for field_name, value in jsonfields.iteritems(): for field_name, field in xblock.fields.iteritems():
if value: if (field.scope == scope and field.is_set_on(xblock)):
if isinstance(xblock.fields[field_name], Reference): if isinstance(field, Reference):
jsonfields[field_name] = value.to_deprecated_string() jsonfields[field_name] = unicode(field.read_from(xblock))
elif isinstance(xblock.fields[field_name], ReferenceList): elif isinstance(field, ReferenceList):
jsonfields[field_name] = [ jsonfields[field_name] = [
ele.to_deprecated_string() for ele in value unicode(ele) for ele in field.read_from(xblock)
] ]
elif isinstance(xblock.fields[field_name], ReferenceValueDict): elif isinstance(field, ReferenceValueDict):
for key, subvalue in value.iteritems(): jsonfields[field_name] = {
assert isinstance(subvalue, UsageKey) key: unicode(subvalue) for key, subvalue in field.read_from(xblock).iteritems()
value[key] = subvalue.to_deprecated_string() }
else:
jsonfields[field_name] = field.read_json(xblock)
return jsonfields return jsonfields
def _get_raw_parent_location(self, location, revision=ModuleStoreEnum.RevisionOption.published_only): def _get_raw_parent_location(self, location, revision=ModuleStoreEnum.RevisionOption.published_only):
...@@ -1217,7 +1221,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -1217,7 +1221,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
# create a query with tag, org, course, and the children field set to the given location # create a query with tag, org, course, and the children field set to the given location
query = self._course_key_to_son(location.course_key) query = self._course_key_to_son(location.course_key)
query['definition.children'] = location.to_deprecated_string() query['definition.children'] = unicode(location)
# if only looking for the PUBLISHED parent, set the revision in the query to None # if only looking for the PUBLISHED parent, set the revision in the query to None
if revision == ModuleStoreEnum.RevisionOption.published_only: if revision == ModuleStoreEnum.RevisionOption.published_only:
...@@ -1292,7 +1296,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase): ...@@ -1292,7 +1296,7 @@ class MongoModuleStore(ModuleStoreDraftAndPublished, ModuleStoreWriteBase):
if item['_id']['category'] != 'course': if item['_id']['category'] != 'course':
# It would be nice to change this method to return UsageKeys instead of the deprecated string. # It would be nice to change this method to return UsageKeys instead of the deprecated string.
item_locs.add( item_locs.add(
as_published(Location._from_deprecated_son(item['_id'], course_key.run)).to_deprecated_string() unicode(as_published(Location._from_deprecated_son(item['_id'], course_key.run)))
) )
all_reachable = all_reachable.union(item.get('definition', {}).get('children', [])) all_reachable = all_reachable.union(item.get('definition', {}).get('children', []))
item_locs -= all_reachable item_locs -= all_reachable
......
...@@ -223,7 +223,7 @@ class MongoContentstoreBuilder(object): ...@@ -223,7 +223,7 @@ class MongoContentstoreBuilder(object):
MODULESTORE_SETUPS = ( MODULESTORE_SETUPS = (
MongoModulestoreBuilder(), MongoModulestoreBuilder(),
VersioningModulestoreBuilder(), # VersioningModulestoreBuilder(), # FIXME LMS-11227
MixedModulestoreBuilder([('draft', MongoModulestoreBuilder())]), MixedModulestoreBuilder([('draft', MongoModulestoreBuilder())]),
MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())]), MixedModulestoreBuilder([('split', VersioningModulestoreBuilder())]),
) )
...@@ -231,6 +231,8 @@ CONTENTSTORE_SETUPS = (MongoContentstoreBuilder(),) ...@@ -231,6 +231,8 @@ CONTENTSTORE_SETUPS = (MongoContentstoreBuilder(),)
COURSE_DATA_NAMES = ( COURSE_DATA_NAMES = (
'toy', 'toy',
'manual-testing-complete', 'manual-testing-complete',
'split_test_module',
'split_test_module_draft',
) )
......
...@@ -4,7 +4,7 @@ Methods for exporting course data to XML ...@@ -4,7 +4,7 @@ Methods for exporting course data to XML
import logging import logging
import lxml.etree import lxml.etree
from xblock.fields import Scope from xblock.fields import Scope, Reference, ReferenceList, ReferenceValueDict
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
from xmodule.modulestore import EdxJSONEncoder, ModuleStoreEnum from xmodule.modulestore import EdxJSONEncoder, ModuleStoreEnum
...@@ -16,6 +16,7 @@ import os ...@@ -16,6 +16,7 @@ import os
from path import path from path import path
import shutil import shutil
from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES from xmodule.modulestore.draft_and_published import DIRECT_ONLY_CATEGORIES
from opaque_keys.edx.locator import CourseLocator
DRAFT_DIR = "drafts" DRAFT_DIR = "drafts"
PUBLISHED_DIR = "published" PUBLISHED_DIR = "published"
...@@ -36,8 +37,7 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir): ...@@ -36,8 +37,7 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
`course_dir`: The name of the directory inside `root_dir` to write the course content to `course_dir`: The name of the directory inside `root_dir` to write the course content to
""" """
course = modulestore.get_course(course_key) course = modulestore.get_course(course_key, depth=None) # None means infinite
fsm = OSFS(root_dir) fsm = OSFS(root_dir)
export_fs = course.runtime.export_fs = fsm.makeopendir(course_dir) export_fs = course.runtime.export_fs = fsm.makeopendir(course_dir)
...@@ -45,6 +45,10 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir): ...@@ -45,6 +45,10 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
# export only the published content # export only the published content
with modulestore.branch_setting(ModuleStoreEnum.Branch.published_only, course_key): with modulestore.branch_setting(ModuleStoreEnum.Branch.published_only, course_key):
# change all of the references inside the course to use the xml expected key type w/o version & branch
xml_centric_course_key = CourseLocator(course_key.org, course_key.course, course_key.run, deprecated=True)
adapt_references(course, xml_centric_course_key, export_fs)
course.add_xml_to_node(root) course.add_xml_to_node(root)
with export_fs.open('course.xml', 'w') as course_xml: with export_fs.open('course.xml', 'w') as course_xml:
...@@ -79,16 +83,16 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir): ...@@ -79,16 +83,16 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
course_image_file.write(course_image.data) course_image_file.write(course_image.data)
# export the static tabs # export the static tabs
export_extra_content(export_fs, modulestore, course_key, 'static_tab', 'tabs', '.html') export_extra_content(export_fs, modulestore, xml_centric_course_key, 'static_tab', 'tabs', '.html')
# export the custom tags # export the custom tags
export_extra_content(export_fs, modulestore, course_key, 'custom_tag_template', 'custom_tags') export_extra_content(export_fs, modulestore, xml_centric_course_key, 'custom_tag_template', 'custom_tags')
# export the course updates # export the course updates
export_extra_content(export_fs, modulestore, course_key, 'course_info', 'info', '.html') export_extra_content(export_fs, modulestore, xml_centric_course_key, 'course_info', 'info', '.html')
# export the 'about' data (e.g. overview, etc.) # export the 'about' data (e.g. overview, etc.)
export_extra_content(export_fs, modulestore, course_key, 'about', 'about', '.html') export_extra_content(export_fs, modulestore, xml_centric_course_key, 'about', 'about', '.html')
# export the grading policy # export the grading policy
course_run_policy_dir = policies_dir.makeopendir(course.location.name) course_run_policy_dir = policies_dir.makeopendir(course.location.name)
...@@ -125,10 +129,39 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir): ...@@ -125,10 +129,39 @@ def export_to_xml(modulestore, contentstore, course_key, root_dir, course_dir):
index = sequential.children.index(draft_vertical.location) index = sequential.children.index(draft_vertical.location)
draft_vertical.xml_attributes['index_in_children_list'] = str(index) draft_vertical.xml_attributes['index_in_children_list'] = str(index)
draft_vertical.runtime.export_fs = draft_course_dir draft_vertical.runtime.export_fs = draft_course_dir
adapt_references(draft_vertical, xml_centric_course_key, draft_course_dir)
node = lxml.etree.Element('unknown') node = lxml.etree.Element('unknown')
draft_vertical.add_xml_to_node(node) draft_vertical.add_xml_to_node(node)
def adapt_references(subtree, destination_course_key, export_fs):
"""
Map every reference in the subtree into destination_course_key and set it back into the xblock fields.
Make sure every runtime knows where the export_fs is.
"""
subtree.runtime.export_fs = export_fs # ensure everything knows where it's going!
for field_name, field in subtree.fields.iteritems():
if field.is_set_on(subtree):
if isinstance(field, Reference):
value = field.read_from(subtree)
if value is not None:
field.write_to(subtree, field.read_from(subtree).map_into_course(destination_course_key))
elif field_name == 'children':
# don't change the children field but do recurse over the children
[adapt_references(child, destination_course_key, export_fs) for child in subtree.get_children()]
elif isinstance(field, ReferenceList):
field.write_to(
subtree,
[ele.map_into_course(destination_course_key) for ele in field.read_from(subtree)]
)
elif isinstance(field, ReferenceValueDict):
field.write_to(
subtree, {
key: ele.map_into_course(destination_course_key) for key, ele in field.read_from(subtree).iteritems()
}
)
def _export_field_content(xblock_item, item_dir): def _export_field_content(xblock_item, item_dir):
""" """
Export all fields related to 'xblock_item' other than 'metadata' and 'data' to json file in provided directory Export all fields related to 'xblock_item' other than 'metadata' and 'data' to json file in provided directory
...@@ -149,6 +182,7 @@ def export_extra_content(export_fs, modulestore, course_key, category_type, dirn ...@@ -149,6 +182,7 @@ def export_extra_content(export_fs, modulestore, course_key, category_type, dirn
if len(items) > 0: if len(items) > 0:
item_dir = export_fs.makeopendir(dirname) item_dir = export_fs.makeopendir(dirname)
for item in items: for item in items:
adapt_references(item, course_key, export_fs)
with item_dir.open(item.location.name + file_suffix, 'w') as item_file: with item_dir.open(item.location.name + file_suffix, 'w') as item_file:
item_file.write(item.data.encode('utf8')) item_file.write(item.data.encode('utf8'))
......
...@@ -431,7 +431,7 @@ def _import_module_and_update_references( ...@@ -431,7 +431,7 @@ def _import_module_and_update_references(
fields[field_name] = { fields[field_name] = {
key: _convert_reference_fields_to_new_namespace(reference) key: _convert_reference_fields_to_new_namespace(reference)
for key, reference for key, reference
in reference_dict.items() in reference_dict.iteritems()
} }
elif field_name == 'xml_attributes': elif field_name == 'xml_attributes':
value = field.read_from(module) value = field.read_from(module)
......
...@@ -2,7 +2,6 @@ from lxml import etree ...@@ -2,7 +2,6 @@ from lxml import etree
from xmodule.editing_module import XMLEditingDescriptor from xmodule.editing_module import XMLEditingDescriptor
from xmodule.xml_module import XmlDescriptor from xmodule.xml_module import XmlDescriptor
import logging import logging
import sys
from xblock.fields import String, Scope from xblock.fields import String, Scope
from exceptions import SerializationError from exceptions import SerializationError
......
...@@ -16,7 +16,7 @@ from mock import Mock ...@@ -16,7 +16,7 @@ from mock import Mock
from path import path from path import path
from xblock.field_data import DictFieldData from xblock.field_data import DictFieldData
from xblock.fields import ScopeIds, Scope from xblock.fields import ScopeIds, Scope, Reference, ReferenceList, ReferenceValueDict
from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin
from xmodule.modulestore.inheritance import InheritanceMixin, own_metadata from xmodule.modulestore.inheritance import InheritanceMixin, own_metadata
...@@ -159,6 +159,21 @@ class LogicTest(unittest.TestCase): ...@@ -159,6 +159,21 @@ class LogicTest(unittest.TestCase):
return json.loads(self.xmodule.handle_ajax(dispatch, data)) return json.loads(self.xmodule.handle_ajax(dispatch, data))
def map_references(value, field, actual_course_key):
"""
Map the references in value to actual_course_key and return value
"""
if not value: # if falsey
return value
if isinstance(field, Reference):
return value.map_into_course(actual_course_key)
if isinstance(field, ReferenceList):
return [sub.map_into_course(actual_course_key) for sub in value]
if isinstance(field, ReferenceValueDict):
return {key: ele.map_into_course(actual_course_key) for key, ele in value.iteritems()}
return value
class CourseComparisonTest(unittest.TestCase): class CourseComparisonTest(unittest.TestCase):
""" """
Mixin that has methods for comparing courses for equality. Mixin that has methods for comparing courses for equality.
...@@ -239,7 +254,7 @@ class CourseComparisonTest(unittest.TestCase): ...@@ -239,7 +254,7 @@ class CourseComparisonTest(unittest.TestCase):
# compare fields # compare fields
self.assertEqual(expected_item.fields, actual_item.fields) self.assertEqual(expected_item.fields, actual_item.fields)
for field_name in expected_item.fields: for field_name, field in expected_item.fields.iteritems():
if (expected_item.scope_ids.usage_id, field_name) in self.field_exclusions: if (expected_item.scope_ids.usage_id, field_name) in self.field_exclusions:
continue continue
...@@ -250,8 +265,8 @@ class CourseComparisonTest(unittest.TestCase): ...@@ -250,8 +265,8 @@ class CourseComparisonTest(unittest.TestCase):
if field_name == 'children': if field_name == 'children':
continue continue
exp_value = getattr(expected_item, field_name) exp_value = map_references(field.read_from(expected_item), field, actual_course_key)
actual_value = getattr(actual_item, field_name) actual_value = field.read_from(actual_item)
self.assertEqual( self.assertEqual(
exp_value, exp_value,
actual_value, actual_value,
......
...@@ -388,8 +388,8 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -388,8 +388,8 @@ class XmlDescriptor(XModuleDescriptor):
url_path = name_to_pathname(self.url_name) url_path = name_to_pathname(self.url_name)
filepath = self._format_filepath(self.category, url_path) filepath = self._format_filepath(self.category, url_path)
resource_fs.makedir(os.path.dirname(filepath), recursive=True, allow_recreate=True) resource_fs.makedir(os.path.dirname(filepath), recursive=True, allow_recreate=True)
with resource_fs.open(filepath, 'w') as file: with resource_fs.open(filepath, 'w') as fileobj:
file.write(etree.tostring(xml_object, pretty_print=True, encoding='utf-8')) fileobj.write(etree.tostring(xml_object, pretty_print=True, encoding='utf-8'))
# And return just a pointer with the category and filename. # And return just a pointer with the category and filename.
record_object = etree.Element(self.category) record_object = etree.Element(self.category)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment