dump_course_structure.py 3.98 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
"""
A Django command that dumps the structure of a course as a JSON object.

The resulting JSON object has one entry for each module in the course:

{
  "$module_url": {
    "category": "$module_category",
    "children": [$module_children_urls... ],
    "metadata": {$module_metadata}
  },

  "$module_url": ....
  ...
}

"""

import json
from optparse import make_option
from textwrap import dedent

from django.core.management.base import BaseCommand, CommandError

from xmodule.modulestore.django import modulestore
26 27
from xmodule.modulestore.inheritance import own_metadata, compute_inherited_metadata
from xblock.fields import Scope
28
from opaque_keys import InvalidKeyError
29
from opaque_keys.edx.keys import CourseKey
30 31

FILTER_LIST = ['xml_attributes', 'checklists']
32
INHERITED_FILTER_LIST = ['children', 'xml_attributes', 'checklists']
33 34 35 36 37 38 39 40 41 42 43 44 45 46


class Command(BaseCommand):
    """
    Write out to stdout a structural and metadata information for a
    course as a JSON object
    """
    args = "<course_id>"
    help = dedent(__doc__).strip()
    option_list = BaseCommand.option_list + (
        make_option('--modulestore',
                    action='store',
                    default='default',
                    help='Name of the modulestore'),
47 48 49 50 51 52 53 54
        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'),
55 56 57 58 59 60 61
    )

    def handle(self, *args, **options):
        if len(args) != 1:
            raise CommandError("course_id not specified")

        # Get the modulestore
62

63
        store = modulestore()
64 65 66

        # Get the course data

67
        try:
68
            course_key = CourseKey.from_string(args[0])
69 70 71
        except InvalidKeyError:
            raise CommandError("Invalid course_id")

72
        course = store.get_course(course_key)
73 74 75
        if course is None:
            raise CommandError("Invalid course_id")

76 77
        # Precompute inherited metadata at the course level, if needed:

78 79 80
        if options['inherited']:
            compute_inherited_metadata(course)

81 82
        # Convert course data to dictionary and dump it as JSON to stdout

83
        info = dump_module(course, inherited=options['inherited'], defaults=options['inherited_defaults'])
84

85
        return json.dumps(info, indent=2, sort_keys=True, default=unicode)
86 87


88
def dump_module(module, destination=None, inherited=False, defaults=False):
89 90 91 92 93 94 95
    """
    Add the module and all its children to the destination dictionary in
    as a flat structure.
    """

    destination = destination if destination else {}

96
    items = own_metadata(module)
97

98
    filtered_metadata = {k: v for k, v in items.iteritems() if k not in FILTER_LIST}
99

100
    destination[unicode(module.location)] = {
101
        'category': module.location.category,
102
        'children': [unicode(child) for child in getattr(module, 'children', [])],
103
        'metadata': filtered_metadata,
104 105
    }

106
    if inherited:
107
        # When calculating inherited metadata, don't include existing
108 109 110 111 112 113 114 115 116
        # 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
117
            elif defaults:
118 119 120 121 122
                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)}
123
        destination[unicode(module.location)]['inherited_metadata'] = inherited_metadata
124

125
    for child in module.get_children():
126
        dump_module(child, destination, inherited, defaults)
127 128

    return destination