155 lines
4.0 KiB
Python
155 lines
4.0 KiB
Python
import os
|
|
import sys
|
|
import traceback
|
|
|
|
from fs.osfs import OSFS
|
|
from path import path
|
|
|
|
from django.core.management.base import BaseCommand
|
|
|
|
from xmodule.modulestore.xml import XMLModuleStore
|
|
|
|
|
|
def traverse_tree(course):
|
|
'''Load every descriptor in course. Return bool success value.'''
|
|
queue = [course]
|
|
while len(queue) > 0:
|
|
node = queue.pop()
|
|
queue.extend(node.get_children())
|
|
|
|
return True
|
|
|
|
|
|
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 import_with_checks(course_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()]
|
|
|
|
# No default class--want to complain if it doesn't find plugins for any
|
|
# module.
|
|
modulestore = XMLModuleStore(data_dir,
|
|
default_class=None,
|
|
course_dirs=course_dirs)
|
|
|
|
def str_of_err(tpl):
|
|
(msg, exc_str) = tpl
|
|
return '{msg}\n{exc}'.format(msg=msg, exc=exc_str)
|
|
|
|
courses = modulestore.get_courses()
|
|
|
|
n = len(courses)
|
|
if n != 1:
|
|
print 'ERROR: Expect exactly 1 course. Loaded {n}: {lst}'.format(
|
|
n=n, lst=courses)
|
|
return (False, None)
|
|
|
|
course = courses[0]
|
|
errors = modulestore.get_item_errors(course.location)
|
|
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'
|
|
|
|
|
|
#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!'
|
|
else:
|
|
print "Course fails some checks. See above for errors."
|
|
return all_ok, course
|
|
|
|
|
|
def check_roundtrip(course_dir):
|
|
'''Check that import->export leaves the course the same'''
|
|
|
|
print "====== Roundtrip import ======="
|
|
(ok, course) = import_with_checks(course_dir)
|
|
if not ok:
|
|
raise Exception("Roundtrip import failed!")
|
|
|
|
print "====== Roundtrip export ======="
|
|
export_dir = course_dir + ".rt"
|
|
export(course, export_dir)
|
|
|
|
# dircmp doesn't do recursive diffs.
|
|
# diff = dircmp(course_dir, export_dir, ignore=[], hide=[])
|
|
print "======== Roundtrip diff: ========="
|
|
sys.stdout.flush() # needed to make diff appear in the right place
|
|
os.system("diff -r {0} {1}".format(course_dir, export_dir))
|
|
print "======== ideally there is no diff above this ======="
|
|
|
|
|
|
def clean_xml(course_dir, export_dir, force):
|
|
(ok, course) = import_with_checks(course_dir)
|
|
if ok or force:
|
|
if not ok:
|
|
print "WARNING: Exporting despite errors"
|
|
export(course, export_dir)
|
|
check_roundtrip(export_dir)
|
|
else:
|
|
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 [force]
|
|
|
|
If 'force' is specified as the last argument, exports even if there
|
|
were import errors.
|
|
"""
|
|
def handle(self, *args, **options):
|
|
n = len(args)
|
|
if n < 2 or n > 3:
|
|
print Command.help
|
|
return
|
|
|
|
force = False
|
|
if n == 3 and args[2] == 'force':
|
|
force = True
|
|
clean_xml(args[0], args[1], force)
|