Commit da3c3e5f by Chris Dodge

initial xlint implementation. Accumulate all import errors during XmlModuleStore…

initial xlint implementation. Accumulate all import errors during XmlModuleStore importing. Also do checks post XmlModuleStore import and assert that the structure (course->chapter->sequential->vertical) is present in the courses.
parent da8efc17
...@@ -9,8 +9,10 @@ unnamed_modules = 0 ...@@ -9,8 +9,10 @@ unnamed_modules = 0
class Command(BaseCommand): class Command(BaseCommand):
help = \ help = \
'''Verify the structure of courseware as to it's suitability for import''' '''
Verify the structure of courseware as to it's suitability for import
To run test: rake cms:xlint DATA_DIR=../data [COURSE_DIR=content-edx-101 (optional parameter)]
'''
def handle(self, *args, **options): def handle(self, *args, **options):
if len(args) == 0: if len(args) == 0:
raise CommandError("import requires at least one argument: <data directory> [<course dir>...]") raise CommandError("import requires at least one argument: <data directory> [<course dir>...]")
......
...@@ -303,7 +303,7 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -303,7 +303,7 @@ class XMLModuleStore(ModuleStoreBase):
try: try:
course_descriptor = self.load_course(course_dir, errorlog.tracker) course_descriptor = self.load_course(course_dir, errorlog.tracker)
except Exception as e: except Exception as e:
msg = "Failed to load course '{0}': {1}".format(course_dir, str(e)) msg = "ERROR: Failed to load course '{0}': {1}".format(course_dir, str(e))
log.exception(msg) log.exception(msg)
errorlog.tracker(msg) errorlog.tracker(msg)
...@@ -337,7 +337,7 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -337,7 +337,7 @@ class XMLModuleStore(ModuleStoreBase):
with open(policy_path) as f: with open(policy_path) as f:
return json.load(f) return json.load(f)
except (IOError, ValueError) as err: except (IOError, ValueError) as err:
msg = "Error loading course policy from {0}".format(policy_path) msg = "ERROR: loading course policy from {0}".format(policy_path)
tracker(msg) tracker(msg)
log.warning(msg + " " + str(err)) log.warning(msg + " " + str(err))
return {} return {}
...@@ -458,7 +458,8 @@ class XMLModuleStore(ModuleStoreBase): ...@@ -458,7 +458,8 @@ class XMLModuleStore(ModuleStoreBase):
module.metadata['data_dir'] = course_dir module.metadata['data_dir'] = course_dir
self.modules[course_descriptor.id][module.location] = module self.modules[course_descriptor.id][module.location] = module
except Exception, e: except Exception, e:
logging.exception("Failed to load {0}. Skipping... Exception: {1}".format(filepath, str(e))) logging.exception("Failed to load {0}. Skipping... Exception: {1}".format(filepath, str(e)))
system.error_tracker("ERROR: " + str(e))
def get_instance(self, course_id, location, depth=0): def get_instance(self, course_id, location, depth=0):
""" """
......
...@@ -227,6 +227,28 @@ def validate_category_hierarcy(module_store, course_id, parent_category, expecte ...@@ -227,6 +227,28 @@ def validate_category_hierarcy(module_store, course_id, parent_category, expecte
def validate_module_structure(module_store): def validate_module_structure(module_store):
err_cnt = 0 err_cnt = 0
warn_cnt = 0 warn_cnt = 0
print module_store.errored_courses
# first count all errors and warnings as part of the XMLModuleStore import
for err_log in module_store._location_errors.itervalues():
for err_log_entry in err_log.errors:
msg = err_log_entry[0]
if msg.startswith('ERROR:'):
err_cnt+=1
else:
warn_cnt+=1
# then count outright all courses that failed to load at all
for err_log in module_store.errored_courses.itervalues():
for err_log_entry in err_log.errors:
msg = err_log_entry[0]
print msg
if msg.startswith('ERROR:'):
err_cnt+=1
else:
warn_cnt+=1
for course_id in module_store.modules.keys(): for course_id in module_store.modules.keys():
# constrain that courses only have 'chapter' children # constrain that courses only have 'chapter' children
err_cnt += validate_category_hierarcy(module_store, course_id, "course", "chapter") err_cnt += validate_category_hierarcy(module_store, course_id, "course", "chapter")
...@@ -235,6 +257,14 @@ def validate_module_structure(module_store): ...@@ -235,6 +257,14 @@ def validate_module_structure(module_store):
# constrain that sequentials only have 'verticals' # constrain that sequentials only have 'verticals'
err_cnt += validate_category_hierarcy(module_store, course_id, "sequential", "vertical") err_cnt += validate_category_hierarcy(module_store, course_id, "sequential", "vertical")
print "SUMMARY: {0} Errors {1} Warnings".format(err_cnt, warn_cnt) print "\n\n------------------------------------------\nVALIDATION SUMMARY: {0} Errors {1} Warnings\n".format(err_cnt, warn_cnt)
if err_cnt > 0:
print "This course is not suitable for importing. Please fix courseware according to specifications before importing."
elif warn_cnt > 0:
print "This course can be imported, but some errors may occur during the run of the course. It is recommend that you fix your courseware before importing"
else:
print "This course can be imported successfully."
...@@ -127,8 +127,10 @@ class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor): ...@@ -127,8 +127,10 @@ class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor):
for child in xml_object: for child in xml_object:
try: try:
children.append(system.process_xml(etree.tostring(child)).location.url()) children.append(system.process_xml(etree.tostring(child)).location.url())
except: except Exception, e:
log.exception("Unable to load child when parsing Sequence. Continuing...") log.exception("Unable to load child when parsing Sequence. Continuing...")
if system.error_tracker is not None:
system.error_tracker("ERROR: " + str(e))
continue continue
return {'children': children} return {'children': children}
......
...@@ -367,7 +367,9 @@ end ...@@ -367,7 +367,9 @@ end
namespace :cms do namespace :cms do
desc "Import course data within the given DATA_DIR variable" desc "Import course data within the given DATA_DIR variable"
task :xlint do task :xlint do
if ENV['DATA_DIR'] if ENV['DATA_DIR'] and ENV['COURSE_DIR']
sh(django_admin(:cms, :dev, :xlint, ENV['DATA_DIR'], ENV['COURSE_DIR']))
elsif ENV['DATA_DIR']
sh(django_admin(:cms, :dev, :xlint, ENV['DATA_DIR'])) sh(django_admin(:cms, :dev, :xlint, ENV['DATA_DIR']))
else else
raise "Please specify a DATA_DIR variable that point to your data directory.\n" + raise "Please specify a DATA_DIR variable that point to your data directory.\n" +
......
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