Commit 213e3cdc by Calen Pennington

Merge pull request #418 from MITx/feature/victor/strip-metadata

Feature/victor/strip metadata
parents e524f3bf a4d67bab
import json
import logging
import os
import re
......@@ -149,7 +150,7 @@ class XMLModuleStore(ModuleStoreBase):
for course_dir in course_dirs:
self.try_load_course(course_dir)
def try_load_course(self,course_dir):
def try_load_course(self, course_dir):
'''
Load a course, keeping track of errors as we go along.
'''
......@@ -170,7 +171,27 @@ class XMLModuleStore(ModuleStoreBase):
'''
String representation - for debugging
'''
return '<XMLModuleStore>data_dir=%s, %d courses, %d modules' % (self.data_dir,len(self.courses),len(self.modules))
return '<XMLModuleStore>data_dir=%s, %d courses, %d modules' % (
self.data_dir, len(self.courses), len(self.modules))
def load_policy(self, policy_path, tracker):
"""
Attempt to read a course policy from policy_path. If the file
exists, but is invalid, log an error and return {}.
If the policy loads correctly, returns the deserialized version.
"""
if not os.path.exists(policy_path):
return {}
try:
with open(policy_path) as f:
return json.load(f)
except (IOError, ValueError) as err:
msg = "Error loading course policy from {}".format(policy_path)
tracker(msg)
log.warning(msg + " " + str(err))
return {}
def load_course(self, course_dir, tracker):
"""
......@@ -214,6 +235,11 @@ class XMLModuleStore(ModuleStoreBase):
system = ImportSystem(self, org, course, course_dir, tracker)
course_descriptor = system.process_xml(etree.tostring(course_data))
policy_path = self.data_dir / course_dir / 'policy.json'
policy = self.load_policy(policy_path, tracker)
XModuleDescriptor.apply_policy(course_descriptor, policy)
# NOTE: The descriptors end up loading somewhat bottom up, which
# breaks metadata inheritance via get_children(). Instead
# (actually, in addition to, for now), we do a final inheritance pass
......
......@@ -298,6 +298,14 @@ class XModule(HTMLSnippet):
return ""
def policy_key(location):
"""
Get the key for a location in a policy file. (Since the policy file is
specific to a course, it doesn't need the full location url).
"""
return '{cat}/{name}'.format(cat=location.category, name=location.name)
class XModuleDescriptor(Plugin, HTMLSnippet):
"""
An XModuleDescriptor is a specification for an element of a course. This
......@@ -416,6 +424,24 @@ class XModuleDescriptor(Plugin, HTMLSnippet):
return dict((k,v) for k,v in self.metadata.items()
if k not in self._inherited_metadata)
@staticmethod
def apply_policy(node, policy):
"""
Given a descriptor, traverse all its descendants and update its metadata
with the policy.
Notes:
- this does not propagate inherited metadata. The caller should
call compute_inherited_metadata after applying the policy.
- metadata specified in the policy overrides metadata in the xml
"""
k = policy_key(node.location)
if k in policy:
node.metadata.update(policy[k])
for c in node.get_children():
XModuleDescriptor.apply_policy(c, policy)
@staticmethod
def compute_inherited_metadata(node):
"""Given a descriptor, traverse all of its descendants and do metadata
......
......@@ -166,7 +166,7 @@ class XmlDescriptor(XModuleDescriptor):
Subclasses should not need to override this except in special
cases (e.g. html module)'''
# VS[compat] -- the filename tag should go away once everything is
# VS[compat] -- the filename attr should go away once everything is
# converted. (note: make sure html files still work once this goes away)
filename = xml_object.get('filename')
if filename is None:
......
"""
A script to walk a course xml tree, generate a dictionary of all the metadata,
and print it out as a json dict.
"""
import os
import sys
import json
from collections import OrderedDict
from path import path
from django.core.management.base import BaseCommand
from xmodule.modulestore.xml import XMLModuleStore
from xmodule.x_module import policy_key
def import_course(course_dir, verbose=True):
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,
eager=True,
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:
sys.stderr.write('ERROR: Expect exactly 1 course. Loaded {n}: {lst}\n'.format(
n=n, lst=courses))
return None
course = courses[0]
errors = modulestore.get_item_errors(course.location)
if len(errors) != 0:
sys.stderr.write('ERRORs during import: {}\n'.format('\n'.join(map(str_of_err, errors))))
return course
def node_metadata(node):
# make a copy
to_export = ('format', 'display_name',
'graceperiod', 'showanswer', 'rerandomize',
'start', 'due', 'graded', 'hide_from_toc',
'ispublic', 'xqa_key')
orig = node.own_metadata
d = {k: orig[k] for k in to_export if k in orig}
return d
def get_metadata(course):
d = OrderedDict({})
queue = [course]
while len(queue) > 0:
node = queue.pop()
d[policy_key(node.location)] = node_metadata(node)
# want to print first children first, so put them at the end
# (we're popping from the end)
queue.extend(reversed(node.get_children()))
return d
def print_metadata(course_dir, output):
course = import_course(course_dir)
if course:
meta = get_metadata(course)
result = json.dumps(meta, indent=4)
if output:
with file(output, 'w') as f:
f.write(result)
else:
print result
class Command(BaseCommand):
help = """Imports specified course.xml and prints its
metadata as a json dict.
Usage: metadata_to_json PATH-TO-COURSE-DIR OUTPUT-PATH
if OUTPUT-PATH isn't given, print to stdout.
"""
def handle(self, *args, **options):
n = len(args)
if n < 1 or n > 2:
print Command.help
return
output_path = args[1] if n > 1 else None
print_metadata(args[0], output_path)
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