From 981f5cee45507bff72f85215696de74db5937359 Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Thu, 8 Nov 2012 16:07:17 -0500 Subject: [PATCH] initial buildout of a 'xlint' test to verify legacy coursewar --- .../contentstore/management/commands/xlint.py | 26 +++++++++++ .../xmodule/modulestore/xml_importer.py | 43 +++++++++++++++++-- rakefile | 12 ++++++ 3 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 cms/djangoapps/contentstore/management/commands/xlint.py diff --git a/cms/djangoapps/contentstore/management/commands/xlint.py b/cms/djangoapps/contentstore/management/commands/xlint.py new file mode 100644 index 0000000000..355b639f2d --- /dev/null +++ b/cms/djangoapps/contentstore/management/commands/xlint.py @@ -0,0 +1,26 @@ +from django.core.management.base import BaseCommand, CommandError +from xmodule.modulestore.xml_importer import import_from_xml +from xmodule.modulestore.django import modulestore +from xmodule.contentstore.django import contentstore + + +unnamed_modules = 0 + + +class Command(BaseCommand): + help = \ +'''Verify the structure of courseware as to it's suitability for import''' + + def handle(self, *args, **options): + if len(args) == 0: + raise CommandError("import requires at least one argument: [...]") + + data_dir = args[0] + if len(args) > 1: + course_dirs = args[1:] + else: + course_dirs = None + print "Importing. Data_dir={data}, course_dirs={courses}".format( + data=data_dir, + courses=course_dirs) + import_from_xml(None, data_dir, course_dirs, load_error_modules=False, validate_only=True) diff --git a/common/lib/xmodule/xmodule/modulestore/xml_importer.py b/common/lib/xmodule/xmodule/modulestore/xml_importer.py index 00ddb6a948..4a3526b53e 100644 --- a/common/lib/xmodule/xmodule/modulestore/xml_importer.py +++ b/common/lib/xmodule/xmodule/modulestore/xml_importer.py @@ -88,7 +88,7 @@ def verify_content_links(module, base_dir, static_content_store, link, remap_dic def import_from_xml(store, data_dir, course_dirs=None, default_class='xmodule.raw_module.RawDescriptor', - load_error_modules=True, static_content_store=None, target_location_namespace = None): + load_error_modules=True, static_content_store=None, target_location_namespace=None, validate_only=False): """ Import the specified xml data_dir into the "store" modulestore, using org and course as the location org and course. @@ -110,6 +110,10 @@ def import_from_xml(store, data_dir, course_dirs=None, load_error_modules=load_error_modules ) + if validate_only: + validate_module_structure(module_store) + return module_store, [] + # NOTE: the XmlModuleStore does not implement get_items() which would be a preferable means # to enumerate the entire collection of course modules. It will be left as a TBD to implement that # method on XmlModuleStore. @@ -192,7 +196,6 @@ def import_from_xml(store, data_dir, course_dirs=None, store.update_item(module.location, module_data) - if 'children' in module.definition: store.update_children(module.location, module.definition['children']) @@ -200,6 +203,38 @@ def import_from_xml(store, data_dir, course_dirs=None, # inherited metadata everywhere. store.update_metadata(module.location, dict(module.own_metadata)) - - return module_store, course_items + + +def validate_category_hierarcy(module_store, course_id, parent_category, expected_child_category): + err_cnt = 0 + + parents = [] + # get all modules of parent_category + for module in module_store.modules[course_id].itervalues(): + if module.location.category == parent_category: + parents.append(module) + + for parent in parents: + for child_loc in [Location(child) for child in parent.definition.get('children', [])]: + if child_loc.category != expected_child_category: + err_cnt += 1 + print 'ERROR: child {0} of parent {1} was expected to be category of {2} but was {3}'.format( + child_loc, parent.location, expected_child_category, child_loc.category) + + return err_cnt + +def validate_module_structure(module_store): + err_cnt = 0 + warn_cnt = 0 + for course_id in module_store.modules.keys(): + # constrain that courses only have 'chapter' children + err_cnt += validate_category_hierarcy(module_store, course_id, "course", "chapter") + # constrain that chapters only have 'sequentials' + err_cnt += validate_category_hierarcy(module_store, course_id, "chapter", "sequential") + # constrain that sequentials only have 'verticals' + err_cnt += validate_category_hierarcy(module_store, course_id, "sequential", "vertical") + + print "SUMMARY: {0} Errors {1} Warnings".format(err_cnt, warn_cnt) + + diff --git a/rakefile b/rakefile index 4f1c15321f..beb787c8c3 100644 --- a/rakefile +++ b/rakefile @@ -364,6 +364,18 @@ namespace :cms do end end +namespace :cms do + desc "Import course data within the given DATA_DIR variable" + task :xlint do + if ENV['DATA_DIR'] + sh(django_admin(:cms, :dev, :xlint, ENV['DATA_DIR'])) + else + raise "Please specify a DATA_DIR variable that point to your data directory.\n" + + "Example: \`rake cms:import DATA_DIR=../data\`" + end + end +end + desc "Build a properties file used to trigger autodeploy builds" task :autodeploy_properties do File.open("autodeploy.properties", "w") do |file|