Merge pull request #2008 from edx/brian/dump-inherited-metadata
Add options to dump_course_structure to output inherited metadata.
This commit is contained in:
@@ -23,10 +23,11 @@ from textwrap import dedent
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
|
||||
from xmodule.modulestore.inheritance import own_metadata, compute_inherited_metadata
|
||||
from xblock.fields import Scope
|
||||
|
||||
FILTER_LIST = ['xml_attributes', 'checklists']
|
||||
INHERITED_FILTER_LIST = ['children', 'xml_attributes', 'checklists']
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
@@ -41,6 +42,14 @@ class Command(BaseCommand):
|
||||
action='store',
|
||||
default='default',
|
||||
help='Name of the modulestore'),
|
||||
make_option('--inherited',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Whether to include inherited metadata'),
|
||||
make_option('--inherited_defaults',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Whether to include default values of inherited metadata'),
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
@@ -62,14 +71,18 @@ class Command(BaseCommand):
|
||||
if course is None:
|
||||
raise CommandError("Invalid course_id")
|
||||
|
||||
# precompute inherited metadata at the course level, if needed:
|
||||
if options['inherited']:
|
||||
compute_inherited_metadata(course)
|
||||
|
||||
# Convert course data to dictionary and dump it as JSON to stdout
|
||||
|
||||
info = dump_module(course)
|
||||
info = dump_module(course, inherited=options['inherited'], defaults=options['inherited_defaults'])
|
||||
|
||||
return json.dumps(info, indent=2, sort_keys=True)
|
||||
|
||||
|
||||
def dump_module(module, destination=None):
|
||||
def dump_module(module, destination=None, inherited=False, defaults=False):
|
||||
"""
|
||||
Add the module and all its children to the destination dictionary in
|
||||
as a flat structure.
|
||||
@@ -83,10 +96,29 @@ def dump_module(module, destination=None):
|
||||
destination[module.location.url()] = {
|
||||
'category': module.location.category,
|
||||
'children': module.children if hasattr(module, 'children') else [],
|
||||
'metadata': filtered_metadata
|
||||
'metadata': filtered_metadata,
|
||||
}
|
||||
|
||||
if inherited:
|
||||
# when calculating inherited metadata, don't include existing
|
||||
# locally-defined metadata
|
||||
inherited_metadata_filter_list = list(filtered_metadata.keys())
|
||||
inherited_metadata_filter_list.extend(INHERITED_FILTER_LIST)
|
||||
|
||||
def is_inherited(field):
|
||||
if field.name in inherited_metadata_filter_list:
|
||||
return False
|
||||
elif field.scope != Scope.settings:
|
||||
return False
|
||||
elif defaults == True:
|
||||
return True
|
||||
else:
|
||||
return field.values != field.default
|
||||
|
||||
inherited_metadata = {field.name: field.read_json(module) for field in module.fields.values() if is_inherited(field)}
|
||||
destination[module.location.url()]['inherited_metadata'] = inherited_metadata
|
||||
|
||||
for child in module.get_children():
|
||||
dump_module(child, destination)
|
||||
dump_module(child, destination, inherited, defaults)
|
||||
|
||||
return destination
|
||||
|
||||
@@ -10,6 +10,7 @@ from path import path
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test.utils import override_settings
|
||||
from django.test.testcases import TestCase
|
||||
|
||||
from courseware.tests.modulestore_config import TEST_DATA_XML_MODULESTORE
|
||||
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
|
||||
@@ -24,7 +25,7 @@ DATA_DIR = 'common/test/data/'
|
||||
TEST_COURSE_ID = 'edX/simple/2012_Fall'
|
||||
|
||||
|
||||
class CommandsTestBase(object):
|
||||
class CommandsTestBase(TestCase):
|
||||
"""
|
||||
Base class for testing different django commands.
|
||||
|
||||
@@ -66,6 +67,15 @@ class CommandsTestBase(object):
|
||||
|
||||
dump = json.loads(output)
|
||||
|
||||
# check that all elements in the course structure have metadata,
|
||||
# but not inherited metadata:
|
||||
for element_name in dump:
|
||||
element = dump[element_name]
|
||||
self.assertIn('metadata', element)
|
||||
self.assertIn('children', element)
|
||||
self.assertIn('category', element)
|
||||
self.assertNotIn('inherited_metadata', element)
|
||||
|
||||
# Check a few elements in the course dump
|
||||
|
||||
parent_id = 'i4x://edX/simple/chapter/Overview'
|
||||
@@ -81,10 +91,44 @@ class CommandsTestBase(object):
|
||||
self.assertEqual(len(dump[video_id]['metadata']), 4)
|
||||
self.assertIn('youtube_id_1_0', dump[video_id]['metadata'])
|
||||
|
||||
# Check if there is the right number of elements
|
||||
# Check if there are the right number of elements
|
||||
|
||||
self.assertEqual(len(dump), 16)
|
||||
|
||||
def test_dump_inherited_course_structure(self):
|
||||
args = [TEST_COURSE_ID]
|
||||
kwargs = {'modulestore': 'default', 'inherited': True}
|
||||
output = self.call_command('dump_course_structure', *args, **kwargs)
|
||||
dump = json.loads(output)
|
||||
# check that all elements in the course structure have inherited metadata,
|
||||
# and that it contains a particular value as well:
|
||||
for element_name in dump:
|
||||
element = dump[element_name]
|
||||
self.assertIn('metadata', element)
|
||||
self.assertIn('children', element)
|
||||
self.assertIn('category', element)
|
||||
self.assertIn('inherited_metadata', element)
|
||||
self.assertIsNone(element['inherited_metadata']['ispublic'])
|
||||
# ... but does not contain inherited metadata containing a default value:
|
||||
self.assertNotIn('due', element['inherited_metadata'])
|
||||
|
||||
def test_dump_inherited_course_structure_with_defaults(self):
|
||||
args = [TEST_COURSE_ID]
|
||||
kwargs = {'modulestore': 'default', 'inherited': True, 'inherited_defaults': True}
|
||||
output = self.call_command('dump_course_structure', *args, **kwargs)
|
||||
dump = json.loads(output)
|
||||
# check that all elements in the course structure have inherited metadata,
|
||||
# and that it contains a particular value as well:
|
||||
for element_name in dump:
|
||||
element = dump[element_name]
|
||||
self.assertIn('metadata', element)
|
||||
self.assertIn('children', element)
|
||||
self.assertIn('category', element)
|
||||
self.assertIn('inherited_metadata', element)
|
||||
self.assertIsNone(element['inherited_metadata']['ispublic'])
|
||||
# ... and contains inherited metadata containing a default value:
|
||||
self.assertIsNone(element['inherited_metadata']['due'])
|
||||
|
||||
def test_export_course(self):
|
||||
tmp_dir = path(mkdtemp())
|
||||
filename = tmp_dir / 'test.tar.gz'
|
||||
Reference in New Issue
Block a user