From beda4f95b26694cf17bcf8cf22a9e98db4392970 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 25 Jul 2012 14:12:16 -0400 Subject: [PATCH] Initial version of import/check/export script. * uses xml modulestore, and new error_handler hook --- .../management/commands/clean_xml.py | 143 ++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 lms/djangoapps/courseware/management/commands/clean_xml.py diff --git a/lms/djangoapps/courseware/management/commands/clean_xml.py b/lms/djangoapps/courseware/management/commands/clean_xml.py new file mode 100644 index 0000000000..1803553ba3 --- /dev/null +++ b/lms/djangoapps/courseware/management/commands/clean_xml.py @@ -0,0 +1,143 @@ +import sys +import traceback + +from fs.osfs import OSFS +from path import path +from lxml import etree + +from django.core.management.base import BaseCommand + +from xmodule.modulestore.xml import XMLModuleStore + + +def export(course, export_dir): + """Export the specified course to course_dir. Creates dir if it doesn't exist. + Overwrites files, does not clean out dir beforehand. + """ + fs = OSFS(export_dir, create=True) + if not fs.isdirempty('.'): + print ('WARNING: Directory {dir} not-empty.' + ' May clobber/confuse things'.format(dir=export_dir)) + + try: + xml = course.export_to_xml(fs) + with fs.open('course.xml', mode='w') as f: + f.write(xml) + + return True + except: + print 'Export failed!' + traceback.print_exc() + + return False + + +def traverse_tree(course): + '''Load every descriptor in course. Return bool success value.''' + queue = [course] + while len(queue) > 0: + node = queue.pop() +# print '{0}:'.format(node.location) +# if 'data' in node.definition: +# print '{0}'.format(node.definition['data']) + queue.extend(node.get_children()) + + return True + +def make_logging_error_handler(): + '''Return a tuple (handler, error_list), where + the handler appends the message and any exc_info + to the error_list on every call. + ''' + errors = [] + + def error_handler(msg, exc_info=None): + '''Log errors''' + if exc_info is None: + if sys.exc_info() != (None, None, None): + exc_info = sys.exc_info() + + errors.append((msg, exc_info)) + + return (error_handler, errors) + +def clean_xml(course_dir, export_dir, verbose=True): + all_ok = True + + print "Attempting to load '{0}'".format(course_dir) + + course_dir = path(course_dir) + data_dir = course_dir.dirname() + course_dirs = [course_dir.basename()] + + (error_handler, errors) = make_logging_error_handler() + # No default class--want to complain if it doesn't find plugins for any + # module. + modulestore = XMLModuleStore(data_dir, + default_class=None, + eager=True, + course_dirs=course_dirs, + error_handler=error_handler) + + def str_of_err(tpl): + (msg, exc_info) = tpl + if exc_info is None: + return msg + + exc_str = '\n'.join(traceback.format_exception(*exc_info)) + return '{msg}\n{exc}'.format(msg=msg, exc=exc_str) + + courses = modulestore.get_courses() + if len(errors) != 0: + all_ok = False + print '\n' + print "=" * 40 + print 'ERRORs during import:' + print '\n'.join(map(str_of_err,errors)) + print "=" * 40 + print '\n' + + n = len(courses) + if n != 1: + print 'ERROR: Expect exactly 1 course. Loaded {n}: {lst}'.format( + n=n, lst=courses) + return + + course = courses[0] + + print course + validators = ( + traverse_tree, + ) + + print "=" * 40 + print "Running validators..." + + for validate in validators: + print 'Running {0}'.format(validate.__name__) + all_ok = validate(course) and all_ok + + + if all_ok: + print 'Course passes all checks!' + export(course, export_dir) + + else: + print "Course fails some checks. See above for errors." + print "Did NOT export" + + + +class Command(BaseCommand): + help = """Imports specified course.xml, validate it, then exports in + a canonical format. + +Usage: clean_xml PATH-TO-COURSE-DIR PATH-TO-OUTPUT-DIR +""" + + def handle(self, *args, **options): + if len(args) != 2: + print Command.help + return + + clean_xml(args[0], args[1])