From cbfc7b201af3742c52b5527eae3132d9f6b9aaa7 Mon Sep 17 00:00:00 2001
From: Calen Pennington <calen.pennington@gmail.com>
Date: Mon, 10 Dec 2012 15:40:23 -0500
Subject: [PATCH] WIP more changes to model definitions. Next Up: actually
 wiring model data into the rdbms

---
 cms/djangoapps/contentstore/views.py          |   4 +-
 cms/templates/edit_subsection.html            |   8 +-
 cms/templates/new_item.html                   |   2 +-
 cms/templates/overview.html                   |   6 +-
 cms/templates/unit.html                       |   8 +-
 cms/templates/widgets/header.html             |   2 +-
 cms/templates/widgets/navigation.html         |   2 +-
 cms/templates/widgets/sequence-edit.html      |   2 +-
 cms/templates/widgets/units.html              |   2 +-
 common/djangoapps/student/views.py            |   2 +-
 common/djangoapps/xmodule_modifiers.py        |   2 +-
 common/lib/xmodule/setup.py                   |   2 +-
 common/lib/xmodule/xmodule/capa_module.py     |  59 +++++---
 common/lib/xmodule/xmodule/course_module.py   |  12 +-
 .../lib/xmodule/xmodule/discussion_module.py  |  14 +-
 common/lib/xmodule/xmodule/html_module.py     |  11 +-
 .../xmodule/js/src/video/display.coffee       |   1 -
 common/lib/xmodule/xmodule/model.py           | 104 +++++++++++---
 .../lib/xmodule/xmodule/modulestore/mongo.py  |   3 +-
 common/lib/xmodule/xmodule/modulestore/xml.py |   8 +-
 common/lib/xmodule/xmodule/plugin.py          |  64 +++++++++
 common/lib/xmodule/xmodule/seq_module.py      |  38 ++---
 common/lib/xmodule/xmodule/vertical_module.py |   8 +-
 common/lib/xmodule/xmodule/video_module.py    |  23 ++-
 common/lib/xmodule/xmodule/x_module.py        | 132 +++++++-----------
 lms/djangoapps/courseware/grades.py           |   6 +-
 lms/djangoapps/courseware/module_render.py    |  21 ++-
 lms/setup.py                                  |  18 +++
 lms/templates/courseware/welcome-back.html    |   4 +-
 lms/templates/video.html                      |   2 +-
 lms/xmodule_namespace.py                      |  23 +++
 local-requirements.txt                        |   1 +
 32 files changed, 375 insertions(+), 219 deletions(-)
 create mode 100644 common/lib/xmodule/xmodule/plugin.py
 create mode 100644 lms/setup.py
 create mode 100644 lms/xmodule_namespace.py

diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 833662b2185..bf706f29964 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -115,7 +115,7 @@ def index(request):
 
     return render_to_response('index.html', {
         'new_course_template' : Location('i4x', 'edx', 'templates', 'course', 'Empty'),
-        'courses': [(course.metadata.get('display_name'),
+        'courses': [(course.title,
                     reverse('course_index', args=[
                         course.location.org,
                         course.location.course,
@@ -269,7 +269,7 @@ def edit_unit(request, location):
     for template in templates:
         if template.location.category in COMPONENT_TYPES:
             component_templates[template.location.category].append((
-                template.display_name,
+                template.lms.display_name,
                 template.location.url(),
             ))
 
diff --git a/cms/templates/edit_subsection.html b/cms/templates/edit_subsection.html
index d3b6f73f139..53567c73e19 100644
--- a/cms/templates/edit_subsection.html
+++ b/cms/templates/edit_subsection.html
@@ -21,7 +21,7 @@
       <article class="subsection-body window" data-id="${subsection.location}">
         <div class="subsection-name-input">
           <label>Display Name:</label>
-          <input type="text" value="${subsection.metadata['display_name']}" class="subsection-display-name-input" data-metadata-name="display_name"/>
+          <input type="text" value="${subsection.lms.display_name}" class="subsection-display-name-input" data-metadata-name="display_name"/>
         </div>
         <div>
           <label>Format:</label>
@@ -62,11 +62,11 @@
         </div>
         % if subsection.start != parent_item.start and subsection.start:
             % if parent_start_date is None:
-            <p class="notice">The date above differs from the release date of ${parent_item.display_name}, which is unset.
+            <p class="notice">The date above differs from the release date of ${parent_item.lms.display_name}, which is unset.
             % else:
-            <p class="notice">The date above differs from the release date of ${parent_item.display_name} – ${parent_start_date.strftime('%m/%d/%Y')} at ${parent_start_date.strftime('%H:%M')}.
+            <p class="notice">The date above differs from the release date of ${parent_item.lms.display_name} – ${parent_start_date.strftime('%m/%d/%Y')} at ${parent_start_date.strftime('%H:%M')}.
             % endif
-            <a href="#" class="sync-date no-spinner">Sync to ${parent_item.display_name}.</a></p>
+            <a href="#" class="sync-date no-spinner">Sync to ${parent_item.lms.display_name}.</a></p>
         % endif
       </div>
     
diff --git a/cms/templates/new_item.html b/cms/templates/new_item.html
index 60da39fd2ad..3c81b81c7e4 100644
--- a/cms/templates/new_item.html
+++ b/cms/templates/new_item.html
@@ -8,7 +8,7 @@
       <div>${module_type}</div>
       <div>
         % for template in module_templates:
-        <a class="save" data-template-id="${template.location.url()}">${template.display_name}</a>
+        <a class="save" data-template-id="${template.location.url()}">${template.lms.display_name}</a>
         % endfor
       </div>
     </div>
diff --git a/cms/templates/overview.html b/cms/templates/overview.html
index 2a46908c558..fad28dbf07e 100644
--- a/cms/templates/overview.html
+++ b/cms/templates/overview.html
@@ -132,9 +132,9 @@
 
             <div class="item-details" data-id="${section.location}">
               <h3 class="section-name">
-                <span data-tooltip="Edit this section's name" class="section-name-span">${section.display_name}</span>
+                <span data-tooltip="Edit this section's name" class="section-name-span">${section.lms.display_name}</span>
                 <form class="section-name-edit" style="display:none">
-                  <input type="text" value="${section.display_name}" class="edit-section-name" autocomplete="off"/>
+                  <input type="text" value="${section.lms.display_name}" class="edit-section-name" autocomplete="off"/>
                   <input type="submit" class="save-button edit-section-name-save" value="Save" />
                   <input type="button" class="cancel-button edit-section-name-cancel" value="Cancel" />
                 </form>
@@ -174,7 +174,7 @@
                     <a href="#" data-tooltip="Expand/collapse this subsection" class="expand-collapse-icon expand"></a>
                     <a href="${reverse('edit_subsection', args=[subsection.location])}">
                       <span class="folder-icon"></span>
-                      <span class="subsection-name"><span class="subsection-name-value">${subsection.display_name}</span></span>
+                      <span class="subsection-name"><span class="subsection-name-value">${subsection.lms.display_name}</span></span>
                     </a>
                   </div>
                   
diff --git a/cms/templates/unit.html b/cms/templates/unit.html
index 0599411a677..efd81238909 100644
--- a/cms/templates/unit.html
+++ b/cms/templates/unit.html
@@ -27,7 +27,7 @@
       </div>
       <div class="main-column">
         <article class="unit-body window">
-          <p class="unit-name-input"><label>Display Name:</label><input type="text" value="${unit.display_name}" class="unit-display-name-input" /></p>
+          <p class="unit-name-input"><label>Display Name:</label><input type="text" value="${unit.lms.display_name}" class="unit-display-name-input" /></p>
           <ol class="components">
             % for id in components:
             <li class="component" data-id="${id}"/>
@@ -85,7 +85,7 @@
               % if release_date is not None:
                   on <strong>${release_date}</strong>
               % endif 
-              with the subsection <a href="${reverse('edit_subsection', kwargs={'location': subsection.location})}">"${subsection.display_name}"</a></p>
+              with the subsection <a href="${reverse('edit_subsection', kwargs={'location': subsection.location})}">"${subsection.lms.display_name}"</a></p>
             </div>
             <div class="row unit-actions">
               <a href="#" class="delete-draft delete-button">Delete Draft</a>
@@ -100,12 +100,12 @@
             <div><input type="text" class="url" value="/courseware/${section.url_name}/${subsection.url_name}" disabled /></div>
             <ol>
               <li>
-                <a href="#" class="section-item">${section.display_name}</a>
+                <a href="#" class="section-item">${section.lms.display_name}</a>
                 <ol>
                   <li>
                     <a href="${reverse('edit_subsection', args=[subsection.location])}" class="section-item">
                       <span class="folder-icon"></span>
-                      <span class="subsection-name"><span class="subsection-name-value">${subsection.display_name}</span></span>
+                      <span class="subsection-name"><span class="subsection-name-value">${subsection.lms.display_name}</span></span>
                     </a>
                     ${units.enum_units(subsection, actions=False, selected=unit.location)}
                   </li>
diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html
index 5f414523396..76a259296d8 100644
--- a/cms/templates/widgets/header.html
+++ b/cms/templates/widgets/header.html
@@ -8,7 +8,7 @@
         % if context_course:
           <% ctx_loc = context_course.location %>
           <a href="/" class="home"><span class="small-home-icon"></span></a> › 
-          <a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" class="class-name">${context_course.display_name}</a> ›
+          <a href="${reverse('course_index', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}" class="class-name">${context_course.lms.display_name}</a> ›
         % endif
       </div>
 
diff --git a/cms/templates/widgets/navigation.html b/cms/templates/widgets/navigation.html
index f7e79bceb33..00b147675d3 100644
--- a/cms/templates/widgets/navigation.html
+++ b/cms/templates/widgets/navigation.html
@@ -60,7 +60,7 @@
             data-type="${module.js_module_name}"
             data-preview-type="${module.module_class.js_module_name}">
 
-            <a href="#" class="module-edit">${module.display_name}</a>
+            <a href="#" class="module-edit">${module.lms.display_name}</a>
         </li>
         % endfor
         <%include file="module-dropdown.html"/>
diff --git a/cms/templates/widgets/sequence-edit.html b/cms/templates/widgets/sequence-edit.html
index e9d796784d3..daee13fc018 100644
--- a/cms/templates/widgets/sequence-edit.html
+++ b/cms/templates/widgets/sequence-edit.html
@@ -40,7 +40,7 @@
               <a href="#" class="module-edit"
                  data-id="${child.location.url()}"
                  data-type="${child.js_module_name}"
-                 data-preview-type="${child.module_class.js_module_name}">${child.display_name}</a>
+                 data-preview-type="${child.module_class.js_module_name}">${child.lms.display_name}</a>
               <a href="#" class="draggable">handle</a>
             </li>
             %endfor
diff --git a/cms/templates/widgets/units.html b/cms/templates/widgets/units.html
index 8e23b05bf8f..f7cad233554 100644
--- a/cms/templates/widgets/units.html
+++ b/cms/templates/widgets/units.html
@@ -22,7 +22,7 @@ This def will enumerate through a passed in subsection and list all of the units
     <div class="section-item ${selected_class}">
       <a href="${reverse('edit_unit', args=[unit.location])}" class="${unit_state}-item">
         <span class="${unit.category}-icon"></span>
-        <span class="unit-name">${unit.display_name}</span>
+        <span class="unit-name">${unit.lms.display_name}</span>
       </a>
       % if actions:
       <div class="item-actions">
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index e7562f83d0a..bfde1b1419b 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -231,7 +231,7 @@ def change_enrollment(request):
         if not has_access(user, course, 'enroll'):
             return {'success': False,
                     'error': 'enrollment in {} not allowed at this time'
-                    .format(course.display_name)}
+                    .format(course.lms.display_name)}
 
         org, course_num, run=course_id.split("/")
         statsd.increment("common.student.enrollment",
diff --git a/common/djangoapps/xmodule_modifiers.py b/common/djangoapps/xmodule_modifiers.py
index 5c19a2f1d77..81f0205c3e3 100644
--- a/common/djangoapps/xmodule_modifiers.py
+++ b/common/djangoapps/xmodule_modifiers.py
@@ -32,7 +32,7 @@ def wrap_xmodule(get_html, module, template, context=None):
     def _get_html():
         context.update({
             'content': get_html(),
-            'display_name' : module.metadata.get('display_name') if module.metadata is not None else None,
+            'display_name': module.lms.display_name,
             'class_': module.__class__.__name__,
             'module_name': module.js_module_name
         })
diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py
index 24cddd20474..b3a8dabe1bf 100644
--- a/common/lib/xmodule/setup.py
+++ b/common/lib/xmodule/setup.py
@@ -40,6 +40,6 @@ setup(
             "static_tab = xmodule.html_module:StaticTabDescriptor",
             "custom_tag_template = xmodule.raw_module:RawDescriptor",
             "about = xmodule.html_module:AboutDescriptor"
-        ]
+        ],
     }
 )
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 6ad6de2be67..acb4b63e1c0 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -21,8 +21,6 @@ from xmodule.raw_module import RawDescriptor
 from xmodule.exceptions import NotFoundError
 from .model import Int, Scope, ModuleScope, ModelType, String, Boolean, Object, Float
 
-Date = Timedelta = ModelType
-
 log = logging.getLogger("mitx.courseware")
 
 #-----------------------------------------------------------------------------
@@ -45,25 +43,34 @@ def only_one(lst, default="", process=lambda x: x):
         raise Exception('Malformed XML: expected at most one element in list.')
 
 
-def parse_timedelta(time_str):
-    """
-    time_str: A string with the following components:
-        <D> day[s] (optional)
-        <H> hour[s] (optional)
-        <M> minute[s] (optional)
-        <S> second[s] (optional)
+class Timedelta(ModelType):
+    def from_json(self, time_str):
+        """
+        time_str: A string with the following components:
+            <D> day[s] (optional)
+            <H> hour[s] (optional)
+            <M> minute[s] (optional)
+            <S> second[s] (optional)
 
-    Returns a datetime.timedelta parsed from the string
-    """
-    parts = TIMEDELTA_REGEX.match(time_str)
-    if not parts:
-        return
-    parts = parts.groupdict()
-    time_params = {}
-    for (name, param) in parts.iteritems():
-        if param:
-            time_params[name] = int(param)
-    return timedelta(**time_params)
+        Returns a datetime.timedelta parsed from the string
+        """
+        parts = TIMEDELTA_REGEX.match(time_str)
+        if not parts:
+            return
+        parts = parts.groupdict()
+        time_params = {}
+        for (name, param) in parts.iteritems():
+            if param:
+                time_params[name] = int(param)
+        return timedelta(**time_params)
+
+    def to_json(self, value):
+        values = []
+        for attr in ('days', 'hours', 'minutes', 'seconds'):
+            cur_value = getattr(value, attr, 0)
+            if cur_value > 0:
+                values.append("%d %s" % (cur_value, attr))
+        return ' '.join(values)
 
 
 class ComplexEncoder(json.JSONEncoder):
@@ -82,7 +89,7 @@ class CapaModule(XModule):
 
     attempts = Int(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state)
     max_attempts = Int(help="Maximum number of attempts that a student is allowed", scope=Scope.settings)
-    due = Date(help="Date that this problem is due by", scope=Scope.settings)
+    due = String(help="Date that this problem is due by", scope=Scope.settings)
     graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted", scope=Scope.settings)
     show_answer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed")
     force_save_button = Boolean(help="Whether to force the save button to appear on the page", scope=Scope.settings)
@@ -90,6 +97,7 @@ class CapaModule(XModule):
     data = String(help="XML data for the problem", scope=Scope.content)
     correct_map = Object(help="Dictionary with the correctness of current student answers", scope=Scope.student_state)
     done = Boolean(help="Whether the student has answered the problem", scope=Scope.student_state)
+    display_name = String(help="Display name for this module", scope=Scope.settings)
 
     js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'),
                      resource_string(__name__, 'js/src/collapsible.coffee'),
@@ -104,8 +112,13 @@ class CapaModule(XModule):
     def __init__(self, system, location, descriptor, model_data):
         XModule.__init__(self, system, location, descriptor, model_data)
 
-        if self.graceperiod is not None and self.due:
-            self.close_date = self.due + self.graceperiod
+        if self.due:
+            due_date = dateutil.parser.parse(self.due)
+        else:
+            due_date = None
+
+        if self.graceperiod is not None and due_date:
+            self.close_date = due_date + self.graceperiod
             #log.debug("Then parsed " + grace_period_string +
             #          " to closing date" + str(self.close_date))
         else:
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index 019a79e7abd..c4dc63c1b4f 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -13,8 +13,7 @@ import time
 import copy
 
 from .model import Scope, ModelType, List, String, Object, Boolean
-
-Date = ModelType
+from .x_module import Date
 
 log = logging.getLogger(__name__)
 
@@ -31,6 +30,10 @@ class CourseDescriptor(SequenceDescriptor):
     end = Date(help="Date that this class ends", scope=Scope.settings)
     advertised_start = Date(help="Date that this course is advertised to start", scope=Scope.settings)
     grading_policy = Object(help="Grading policy definition for this class", scope=Scope.content)
+    show_calculator = Boolean(help="Whether to show the calculator in this course", default=False, scope=Scope.settings)
+    start = Date(help="Start time when this module is visible", scope=Scope.settings)
+    display_name = String(help="Display name for this module", scope=Scope.settings)
+    has_children = True
 
     info_sidebar_name = String(scope=Scope.settings, default='Course Handouts')
     
@@ -342,10 +345,6 @@ class CourseDescriptor(SequenceDescriptor):
     def tabs(self, value):
         self.metadata['tabs'] = value
 
-    @property
-    def show_calculator(self):
-        return self.metadata.get("show_calculator", None) == "Yes"
-
     @lazyproperty
     def grading_context(self):
         """
@@ -433,6 +432,7 @@ class CourseDescriptor(SequenceDescriptor):
 
     @property
     def start_date_text(self):
+        print self.advertised_start, self.start
         return time.strftime("%b %d, %Y", self.advertised_start or self.start)
 
     @property
diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py
index 1deceac5d04..a1936042785 100644
--- a/common/lib/xmodule/xmodule/discussion_module.py
+++ b/common/lib/xmodule/xmodule/discussion_module.py
@@ -3,8 +3,7 @@ from pkg_resources import resource_string, resource_listdir
 
 from xmodule.x_module import XModule
 from xmodule.raw_module import RawDescriptor
-
-import json
+from .model import String, Scope
 
 class DiscussionModule(XModule):
     js = {'coffee':
@@ -12,18 +11,19 @@ class DiscussionModule(XModule):
             resource_string(__name__, 'js/src/discussion/display.coffee')]
         }
     js_module_name = "InlineDiscussion"
+
+    data = String(help="XML definition of inline discussion", scope=Scope.content)
+
     def get_html(self):
         context = {
             'discussion_id': self.discussion_id,
         }
         return self.system.render_template('discussion/_discussion_module.html', context)
 
-    def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs):
-        XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
+    def __init__(self, *args, **kwargs):
+        XModule.__init__(self, *args, **kwargs)
 
-        if isinstance(instance_state, str):
-            instance_state = json.loads(instance_state)
-        xml_data = etree.fromstring(definition['data'])
+        xml_data = etree.fromstring(self.data)
         self.discussion_id = xml_data.attrib['id']
         self.title = xml_data.attrib['for']
         self.discussion_category = xml_data.attrib['discussion_category']
diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py
index 709f86bf455..6d86fb90a8b 100644
--- a/common/lib/xmodule/xmodule/html_module.py
+++ b/common/lib/xmodule/xmodule/html_module.py
@@ -15,6 +15,7 @@ from .html_checker import check_html
 from xmodule.modulestore import Location
 
 from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
+from .model import Scope, String
 
 log = logging.getLogger("mitx.courseware")
 
@@ -26,15 +27,11 @@ class HtmlModule(XModule):
                     ]
          }
     js_module_name = "HTMLModule"
+
+    data = String(help="Html contents to display for this module", scope=Scope.content)
     
     def get_html(self):
-        return self.html
-
-    def __init__(self, system, location, definition, descriptor,
-                 instance_state=None, shared_state=None, **kwargs):
-        XModule.__init__(self, system, location, definition, descriptor,
-                         instance_state, shared_state, **kwargs)
-        self.html = self.definition['data']
+        return self.data
 
 
 
diff --git a/common/lib/xmodule/xmodule/js/src/video/display.coffee b/common/lib/xmodule/xmodule/js/src/video/display.coffee
index bb87679d372..dbcab4a6702 100644
--- a/common/lib/xmodule/xmodule/js/src/video/display.coffee
+++ b/common/lib/xmodule/xmodule/js/src/video/display.coffee
@@ -2,7 +2,6 @@ class @Video
   constructor: (element) ->
     @el = $(element).find('.video')
     @id = @el.attr('id').replace(/video_/, '')
-    @caption_data_dir = @el.data('caption-data-dir')
     @caption_asset_path = @el.data('caption-asset-path')
     @show_captions = @el.data('show-captions') == "true"
     window.player = null
diff --git a/common/lib/xmodule/xmodule/model.py b/common/lib/xmodule/xmodule/model.py
index b58ffa267ab..edd94f14a98 100644
--- a/common/lib/xmodule/xmodule/model.py
+++ b/common/lib/xmodule/xmodule/model.py
@@ -1,4 +1,6 @@
 from collections import namedtuple
+from .plugin import Plugin
+
 
 class ModuleScope(object):
     USAGE, DEFINITION, TYPE, ALL = xrange(4)
@@ -15,6 +17,13 @@ Scope.student_info = Scope(student=True, module=ModuleScope.ALL)
 
 
 class ModelType(object):
+    """
+    A field class that can be used as a class attribute to define what data the class will want
+    to refer to.
+
+    When the class is instantiated, it will be available as an instance attribute of the same
+    name, by proxying through to self._model_data on the containing object.
+    """
     sequence = 0
 
     def __init__(self, help=None, default=None, scope=Scope.content):
@@ -33,10 +42,13 @@ class ModelType(object):
         if instance is None:
             return self
 
-        return instance._model_data.get(self.name, self.default)
+        if self.name not in instance._model_data:
+            return self.default
+
+        return self.from_json(instance._model_data[self.name])
 
     def __set__(self, instance, value):
-        instance._model_data[self.name] = value
+        instance._model_data[self.name] = self.to_json(value)
 
     def __delete__(self, instance):
         del instance._model_data[self.name]
@@ -47,27 +59,27 @@ class ModelType(object):
     def __lt__(self, other):
         return self._seq < other._seq
 
+    def to_json(self, value):
+        return value
+
+    def from_json(self, value):
+        return value
+
 Int = Float = Boolean = Object = List = String = Any = ModelType
 
 
 class ModelMetaclass(type):
-    def __new__(cls, name, bases, attrs):
-        # Find registered methods
-        reg_methods = {}
-        for value in attrs.itervalues():
-            for reg_type, names in getattr(value, "_method_registrations", {}).iteritems():
-                for n in names:
-                    reg_methods[reg_type + n] = value
-        attrs['registered_methods'] = reg_methods
-
-        if attrs.get('has_children', False):
-            attrs['children'] = ModelType(help='The children of this XModule', default=[], scope=None)
+    """
+    A metaclass to be used for classes that want to use ModelTypes as class attributes
+    to define data access.
 
-            @property
-            def child_map(self):
-                return dict((child.name, child) for child in self.children)
-            attrs['child_map'] = child_map
+    All class attributes that are ModelTypes will be added to the 'fields' attribute on
+    the instance.
 
+    Additionally, any namespaces registered in the `xmodule.namespace` will be added to
+    the instance
+    """
+    def __new__(cls, name, bases, attrs):
         fields = []
         for n, v in attrs.items():
             if isinstance(v, ModelType):
@@ -77,3 +89,61 @@ class ModelMetaclass(type):
         attrs['fields'] = fields
 
         return super(ModelMetaclass, cls).__new__(cls, name, bases, attrs)
+
+
+class NamespacesMetaclass(type):
+    """
+    A metaclass to be used for classes that want to include namespaced fields in their
+    instances.
+
+    Any namespaces registered in the `xmodule.namespace` will be added to
+    the instance
+    """
+    def __new__(cls, name, bases, attrs):
+        for ns_name, namespace in Namespace.load_classes():
+            if issubclass(namespace, Namespace):
+                attrs[ns_name] = NamespaceDescriptor(namespace)
+
+        return super(NamespacesMetaclass, cls).__new__(cls, name, bases, attrs)
+
+
+class ParentModelMetaclass(type):
+    """
+    A ModelMetaclass that transforms the attribute `has_children = True`
+    into a List field with an empty scope.
+    """
+    def __new__(cls, name, bases, attrs):
+        if attrs.get('has_children', False):
+            attrs['children'] = List(help='The children of this XModule', default=[], scope=None)
+        else:
+            attrs['has_children'] = False
+
+        return super(ParentModelMetaclass, cls).__new__(cls, name, bases, attrs)
+
+
+class NamespaceDescriptor(object):
+    def __init__(self, namespace):
+        self._namespace = namespace
+
+    def __get__(self, instance, owner):
+        if owner is None:
+            return self
+        return self._namespace(instance)
+
+
+class Namespace(Plugin):
+    """
+    A baseclass that sets up machinery for ModelType fields that proxies the contained fields
+    requests for _model_data to self._container._model_data.
+    """
+    __metaclass__ = ModelMetaclass
+    __slots__ = ['container']
+
+    entry_point = 'xmodule.namespace'
+
+    def __init__(self, container):
+        self._container = container
+
+    @property
+    def _model_data(self):
+        return self._container._model_data
diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py
index c65c031c9a3..c0ba73423c6 100644
--- a/common/lib/xmodule/xmodule/modulestore/mongo.py
+++ b/common/lib/xmodule/xmodule/modulestore/mongo.py
@@ -1,6 +1,5 @@
 import pymongo
 import sys
-import logging
 
 from bson.son import SON
 from fs.osfs import OSFS
@@ -9,8 +8,8 @@ from path import path
 
 from importlib import import_module
 from xmodule.errortracker import null_error_tracker, exc_info_to_str
-from xmodule.x_module import XModuleDescriptor
 from xmodule.mako_module import MakoDescriptorSystem
+from xmodule.x_module import XModuleDescriptor
 from xmodule.error_module import ErrorDescriptor
 
 from . import ModuleStoreBase, Location
diff --git a/common/lib/xmodule/xmodule/modulestore/xml.py b/common/lib/xmodule/xmodule/modulestore/xml.py
index 7c6887696e9..8463f945a88 100644
--- a/common/lib/xmodule/xmodule/modulestore/xml.py
+++ b/common/lib/xmodule/xmodule/modulestore/xml.py
@@ -192,7 +192,7 @@ class ImportSystem(XMLParsingSystem, MakoDescriptorSystem):
             xmlstore.modules[course_id][descriptor.location] = descriptor
 
             if hasattr(descriptor, 'children'):
-                for child in descriptor.children:
+                for child in descriptor.get_children():
                     parent_tracker.add_parent(child.location, descriptor.location)
             return descriptor
 
@@ -318,8 +318,6 @@ class XMLModuleStore(ModuleStoreBase):
             # Didn't load course.  Instead, save the errors elsewhere.
             self.errored_courses[course_dir] = errorlog
 
-
-
     def __unicode__(self):
         '''
         String representation - for debugging
@@ -345,8 +343,6 @@ class XMLModuleStore(ModuleStoreBase):
             log.warning(msg + " " + str(err))
         return {}
 
-
-
     def load_course(self, course_dir, tracker):
         """
         Load a course into this module store
@@ -363,7 +359,7 @@ class XMLModuleStore(ModuleStoreBase):
             # been imported into the cms from xml
             course_file = StringIO(clean_out_mako_templating(course_file.read()))
 
-            course_data = etree.parse(course_file,parser=edx_xml_parser).getroot()
+            course_data = etree.parse(course_file, parser=edx_xml_parser).getroot()
 
             org = course_data.get('org')
 
diff --git a/common/lib/xmodule/xmodule/plugin.py b/common/lib/xmodule/xmodule/plugin.py
new file mode 100644
index 00000000000..5cf9c647aa1
--- /dev/null
+++ b/common/lib/xmodule/xmodule/plugin.py
@@ -0,0 +1,64 @@
+import pkg_resources
+import logging
+
+log = logging.getLogger(__name__)
+
+class PluginNotFoundError(Exception):
+    pass
+
+
+class Plugin(object):
+    """
+    Base class for a system that uses entry_points to load plugins.
+
+    Implementing classes are expected to have the following attributes:
+
+        entry_point: The name of the entry point to load plugins from
+    """
+
+    _plugin_cache = None
+
+    @classmethod
+    def load_class(cls, identifier, default=None):
+        """
+        Loads a single class instance specified by identifier. If identifier
+        specifies more than a single class, then logs a warning and returns the
+        first class identified.
+
+        If default is not None, will return default if no entry_point matching
+        identifier is found. Otherwise, will raise a ModuleMissingError
+        """
+        if cls._plugin_cache is None:
+            cls._plugin_cache = {}
+
+        if identifier not in cls._plugin_cache:
+            identifier = identifier.lower()
+            classes = list(pkg_resources.iter_entry_points(
+                    cls.entry_point, name=identifier))
+
+            if len(classes) > 1:
+                log.warning("Found multiple classes for {entry_point} with "
+                            "identifier {id}: {classes}. "
+                            "Returning the first one.".format(
+                    entry_point=cls.entry_point,
+                    id=identifier,
+                    classes=", ".join(
+                            class_.module_name for class_ in classes)))
+
+            if len(classes) == 0:
+                if default is not None:
+                    return default
+                raise PluginNotFoundError(identifier)
+
+            cls._plugin_cache[identifier] = classes[0].load()
+        return cls._plugin_cache[identifier]
+
+    @classmethod
+    def load_classes(cls):
+        """
+        Returns a list of containing the identifiers and their corresponding classes for all
+        of the available instances of this plugin
+        """
+        return [(class_.name, class_.load())
+                for class_
+                in pkg_resources.iter_entry_points(cls.entry_point)]
diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py
index 155ad99480a..6e80d1cf618 100644
--- a/common/lib/xmodule/xmodule/seq_module.py
+++ b/common/lib/xmodule/xmodule/seq_module.py
@@ -8,6 +8,7 @@ from xmodule.xml_module import XmlDescriptor
 from xmodule.x_module import XModule
 from xmodule.progress import Progress
 from xmodule.exceptions import NotFoundError
+from .model import Int, Scope
 from pkg_resources import resource_string
 
 log = logging.getLogger("mitx.common.lib.seq_module")
@@ -16,6 +17,12 @@ log = logging.getLogger("mitx.common.lib.seq_module")
 # OBSOLETE: This obsoletes 'type'
 class_priority = ['video', 'problem']
 
+def display_name(module):
+    if hasattr(module, 'display_name'):
+        return module.display_name
+
+    if hasattr(module, 'lms'):
+        return module.lms.display_name
 
 class SequenceModule(XModule):
     ''' Layout module which lays out content in a temporal sequence
@@ -26,22 +33,18 @@ class SequenceModule(XModule):
     css = {'scss': [resource_string(__name__, 'css/sequence/display.scss')]}
     js_module_name = "Sequence"
 
-    def __init__(self, system, location, definition, descriptor, instance_state=None,
-                 shared_state=None, **kwargs):
-        XModule.__init__(self, system, location, definition, descriptor,
-                         instance_state, shared_state, **kwargs)
-        # NOTE: Position is 1-indexed.  This is silly, but there are now student
-        # positions saved on prod, so it's not easy to fix.
-        self.position = 1
+    has_children = True
+
+    # NOTE: Position is 1-indexed.  This is silly, but there are now student
+    # positions saved on prod, so it's not easy to fix.
+    position = Int(help="Last tab viewed in this sequence", default=1, scope=Scope.student_state)
 
-        if instance_state is not None:
-            state = json.loads(instance_state)
-            if 'position' in state:
-                self.position = int(state['position'])
+    def __init__(self, *args, **kwargs):
+        XModule.__init__(self, *args, **kwargs)
 
         # if position is specified in system, then use that instead
-        if system.get('position'):
-            self.position = int(system.get('position'))
+        if self.system.get('position'):
+            self.position = int(self.system.get('position'))
 
         self.rendered = False
 
@@ -79,9 +82,9 @@ class SequenceModule(XModule):
             childinfo = {
                 'content': child.get_html(),
                 'title': "\n".join(
-                    grand_child.display_name.strip()
+                    display_name(grand_child)
                     for grand_child in child.get_children()
-                    if 'display_name'  in grand_child.metadata
+                    if display_name(grand_child) is not None
                 ),
                 'progress_status': Progress.to_js_status_str(progress),
                 'progress_detail': Progress.to_js_detail_str(progress),
@@ -89,7 +92,7 @@ class SequenceModule(XModule):
                 'id': child.id,
             }
             if childinfo['title']=='':
-                childinfo['title'] = child.metadata.get('display_name','')
+                childinfo['title'] = display_name(child)
             contents.append(childinfo)
 
         params = {'items': contents,
@@ -116,7 +119,8 @@ class SequenceDescriptor(MakoModuleDescriptor, XmlDescriptor):
     mako_template = 'widgets/sequence-edit.html'
     module_class = SequenceModule
 
-    stores_state = True # For remembering where in the sequence the student is
+    has_children = True
+    stores_state = True  # For remembering where in the sequence the student is
 
     js = {'coffee': [resource_string(__name__, 'js/src/sequence/edit.coffee')]}
     js_module_name = "SequenceDescriptor"
diff --git a/common/lib/xmodule/xmodule/vertical_module.py b/common/lib/xmodule/xmodule/vertical_module.py
index 397bd3e1365..a4cd7792859 100644
--- a/common/lib/xmodule/xmodule/vertical_module.py
+++ b/common/lib/xmodule/xmodule/vertical_module.py
@@ -11,8 +11,10 @@ class_priority = ['video', 'problem']
 class VerticalModule(XModule):
     ''' Layout module for laying out submodules vertically.'''
 
-    def __init__(self, system, location, definition, descriptor, instance_state=None, shared_state=None, **kwargs):
-        XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs)
+    has_children = True
+
+    def __init__(self, *args, **kwargs):
+        XModule.__init__(self, *args, **kwargs)
         self.contents = None
 
     def get_html(self):
@@ -45,6 +47,8 @@ class VerticalModule(XModule):
 class VerticalDescriptor(SequenceDescriptor):
     module_class = VerticalModule
 
+    has_children = True
+    
     js = {'coffee': [resource_string(__name__, 'js/src/vertical/edit.coffee')]}
     js_module_name = "VerticalDescriptor"
         
diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py
index 8a9e0aadd76..34ce353afdb 100644
--- a/common/lib/xmodule/xmodule/video_module.py
+++ b/common/lib/xmodule/xmodule/video_module.py
@@ -9,6 +9,7 @@ from xmodule.raw_module import RawDescriptor
 from xmodule.modulestore.mongo import MongoModuleStore
 from xmodule.modulestore.django import modulestore
 from xmodule.contentstore.content import StaticContent
+from .model import Int, Scope, String
 
 log = logging.getLogger(__name__)
 
@@ -27,22 +28,20 @@ class VideoModule(XModule):
     css = {'scss': [resource_string(__name__, 'css/video/display.scss')]}
     js_module_name = "Video"
 
-    def __init__(self, system, location, definition, descriptor,
-                 instance_state=None, shared_state=None, **kwargs):
-        XModule.__init__(self, system, location, definition, descriptor,
-                         instance_state, shared_state, **kwargs)
-        xmltree = etree.fromstring(self.definition['data'])
+    data = String(help="XML data for the problem", scope=Scope.content)
+    position = Int(help="Current position in the video", scope=Scope.student_state)
+    display_name = String(help="Display name for this module", scope=Scope.settings)
+
+    def __init__(self, *args, **kwargs):
+        XModule.__init__(self, *args, **kwargs)
+
+        xmltree = etree.fromstring(self.data)
         self.youtube = xmltree.get('youtube')
         self.position = 0
         self.show_captions = xmltree.get('show_captions', 'true')
         self.source = self._get_source(xmltree)
         self.track = self._get_track(xmltree)
 
-        if instance_state is not None:
-            state = json.loads(instance_state)
-            if 'position' in state:
-                self.position = int(float(state['position']))
-
     def _get_source(self, xmltree):
         # find the first valid source
         return self._get_first_external(xmltree, 'source')
@@ -102,7 +101,7 @@ class VideoModule(XModule):
         else:
             # VS[compat]
             # cdodge: filesystem static content support.
-            caption_asset_path = "/static/{0}/subs/".format(self.metadata['data_dir'])
+            caption_asset_path = "/static/{0}/subs/".format(self.descriptor.data_dir)
 
         return self.system.render_template('video.html', {
             'streams': self.video_list(),
@@ -111,8 +110,6 @@ class VideoModule(XModule):
             'source': self.source,
             'track' : self.track,
             'display_name': self.display_name,
-            # TODO (cpennington): This won't work when we move to data that isn't on the filesystem
-            'data_dir': self.metadata['data_dir'],
             'caption_asset_path': caption_asset_path,
             'show_captions': self.show_captions
         })
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index 9cf45652cad..9cad93ad5b7 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -1,5 +1,4 @@
 import logging
-import pkg_resources
 import yaml
 import os
 import time
@@ -10,9 +9,9 @@ from collections import namedtuple
 from pkg_resources import resource_listdir, resource_string, resource_isdir
 
 from xmodule.modulestore import Location
-from .model import ModelMetaclass, String, Scope, ModuleScope, ModelType
 
-Date = ModelType
+from .model import ModelMetaclass, ParentModelMetaclass, NamespacesMetaclass, ModelType
+from .plugin import Plugin
 
 
 class Date(ModelType):
@@ -38,72 +37,15 @@ class Date(ModelType):
         """
         return time.strftime(self.time_format, value)
 
-log = logging.getLogger('mitx.' + __name__)
-
-
-def dummy_track(event_type, event):
-    pass
 
-
-class ModuleMissingError(Exception):
+class XModuleMetaclass(ParentModelMetaclass, NamespacesMetaclass, ModelMetaclass):
     pass
 
+log = logging.getLogger('mitx.' + __name__)
 
-class Plugin(object):
-    """
-    Base class for a system that uses entry_points to load plugins.
-
-    Implementing classes are expected to have the following attributes:
-
-        entry_point: The name of the entry point to load plugins from
-    """
-
-    _plugin_cache = None
-
-    @classmethod
-    def load_class(cls, identifier, default=None):
-        """
-        Loads a single class instance specified by identifier. If identifier
-        specifies more than a single class, then logs a warning and returns the
-        first class identified.
-
-        If default is not None, will return default if no entry_point matching
-        identifier is found. Otherwise, will raise a ModuleMissingError
-        """
-        if cls._plugin_cache is None:
-            cls._plugin_cache = {}
-
-        if identifier not in cls._plugin_cache:
-            identifier = identifier.lower()
-            classes = list(pkg_resources.iter_entry_points(
-                    cls.entry_point, name=identifier))
-
-            if len(classes) > 1:
-                log.warning("Found multiple classes for {entry_point} with "
-                            "identifier {id}: {classes}. "
-                            "Returning the first one.".format(
-                    entry_point=cls.entry_point,
-                    id=identifier,
-                    classes=", ".join(
-                            class_.module_name for class_ in classes)))
-
-            if len(classes) == 0:
-                if default is not None:
-                    return default
-                raise ModuleMissingError(identifier)
-
-            cls._plugin_cache[identifier] = classes[0].load()
-        return cls._plugin_cache[identifier]
 
-    @classmethod
-    def load_classes(cls):
-        """
-        Returns a list of containing the identifiers and their corresponding classes for all
-        of the available instances of this plugin
-        """
-        return [(class_.name, class_.load())
-                for class_
-                in pkg_resources.iter_entry_points(cls.entry_point)]
+def dummy_track(event_type, event):
+    pass
 
 
 class HTMLSnippet(object):
@@ -179,9 +121,7 @@ class XModule(HTMLSnippet):
         See the HTML module for a simple example.
     '''
 
-    __metaclass__ = ModelMetaclass
-
-    display_name = String(help="Display name for this module", scope=Scope(student=False, module=ModuleScope.USAGE))
+    __metaclass__ = XModuleMetaclass
 
     # The default implementation of get_icon_class returns the icon_class
     # attribute of the class
@@ -244,9 +184,22 @@ class XModule(HTMLSnippet):
         self.url_name = self.location.name
         self.category = self.location.category
         self._model_data = model_data
+        self._loaded_children = None
 
-        if self.display_name is None:
-            self.display_name = self.url_name.replace('_', ' ')
+    def get_children(self):
+        '''
+        Return module instances for all the children of this module.
+        '''
+        if not self.has_children:
+            return []
+
+        if self._loaded_children is None:
+            children = [self.system.get_module(loc) for loc in self.children]
+            # get_module returns None if the current user doesn't have access
+            # to the location.
+            self._loaded_children = [c for c in children if c is not None]
+
+        return self._loaded_children
 
     def __unicode__(self):
         return '<x_module(id={0})>'.format(self.id)
@@ -257,7 +210,7 @@ class XModule(HTMLSnippet):
         immediately inside this module.
         '''
         items = []
-        for child in self.children():
+        for child in self.get_children():
             items.extend(child.displayable_items())
 
         return items
@@ -366,10 +319,8 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
     """
     entry_point = "xmodule.v1"
     module_class = XModule
-    __metaclass__ = ModelMetaclass
+    __metaclass__ = XModuleMetaclass
 
-    display_name = String(help="Display name for this module", scope=Scope(student=False, module=ModuleScope.USAGE))
-    start = Date(help="Start time when this module is visible", scope=Scope(student=False, module=ModuleScope.USAGE))
     # Attributes for inspection of the descriptor
     stores_state = False  # Indicates whether the xmodule state should be
     # stored in a database (independent of shared state)
@@ -430,8 +381,6 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
             metadata: A dictionary containing the following optional keys:
                 goals: A list of strings of learning goals associated with this
                     module
-                display_name: The name to use for displaying this module to the
-                    user
                 url_name: The name to use for this module in urls and other places
                     where a unique name is needed.
                 format: The format of this module ('Homework', 'Lab', etc)
@@ -452,12 +401,36 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
 
         self._child_instances = None
         self._inherited_metadata = set()
+        self._child_instances = None
+
+    def get_children(self):
+        """Returns a list of XModuleDescriptor instances for the children of
+        this module"""
+        if not self.has_children:
+            return []
+
+        if self._child_instances is None:
+            self._child_instances = []
+            for child_loc in self.children:
+                try:
+                    child = self.system.load_item(child_loc)
+                except ItemNotFoundError:
+                    log.exception('Unable to load item {loc}, skipping'.format(loc=child_loc))
+                    continue
+                # TODO (vshnayder): this should go away once we have
+                # proper inheritance support in mongo.  The xml
+                # datastore does all inheritance on course load.
+                #child.inherit_metadata(self.metadata)
+                self._child_instances.append(child)
+
+        return self._child_instances
+
 
     def get_child_by_url_name(self, url_name):
         """
         Return a child XModuleDescriptor with the specified url_name, if it exists, and None otherwise.
         """
-        for c in self.children:
+        for c in self.get_children():
             if c.url_name == url_name:
                 return c
         return None
@@ -472,7 +445,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
             system,
             self.location,
             self,
-            system.xmodule_model_data(self.model_data),
+            system.xmodule_model_data(self._model_data),
         )
     
     def has_dynamic_children(self):
@@ -686,6 +659,7 @@ class ModuleSystem(object):
                  get_module,
                  render_template,
                  replace_urls,
+                 xmodule_model_data,
                  user=None,
                  filestore=None,
                  debug=False,
@@ -739,6 +713,7 @@ class ModuleSystem(object):
         self.node_path = node_path
         self.anonymous_student_id = anonymous_student_id
         self.user_is_staff = user is not None and user.is_staff
+        self.xmodule_model_data = xmodule_model_data
 
     def get(self, attr):
         '''	provide uniform access to attributes (like etree).'''
@@ -748,9 +723,6 @@ class ModuleSystem(object):
         '''provide uniform access to attributes (like etree)'''
         self.__dict__[attr] = val
 
-    def xmodule_module_data(self, module_data):
-        return module_data
-
     def __repr__(self):
         return repr(self.__dict__)
 
diff --git a/lms/djangoapps/courseware/grades.py b/lms/djangoapps/courseware/grades.py
index 36932f9e42c..1283844ade5 100644
--- a/lms/djangoapps/courseware/grades.py
+++ b/lms/djangoapps/courseware/grades.py
@@ -148,7 +148,7 @@ def grade(student, request, course, student_module_cache=None, keep_raw_scores=F
         format_scores = []
         for section in sections:
             section_descriptor = section['section_descriptor']
-            section_name = section_descriptor.metadata.get('display_name')
+            section_name = section_descriptor.display_name
 
             should_grade_section = False
             # If we haven't seen a single problem in the section, we don't have to grade it at all! We can assume 0%  
@@ -276,14 +276,14 @@ def progress_summary(student, request, course, student_module_cache):
     # Don't include chapters that aren't displayable (e.g. due to error)
     for chapter_module in course_module.get_display_items():
         # Skip if the chapter is hidden
-        hidden = chapter_module.metadata.get('hide_from_toc','false')
+        hidden = chapter_module._model_data.get('hide_from_toc','false')
         if hidden.lower() == 'true':
             continue
         
         sections = []
         for section_module in chapter_module.get_display_items():
             # Skip if the section is hidden
-            hidden = section_module.metadata.get('hide_from_toc','false')
+            hidden = section_module._model_data.get('hide_from_toc','false')
             if hidden.lower() == 'true':
                 continue
             
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py
index 9c40767e99f..57a125adba4 100644
--- a/lms/djangoapps/courseware/module_render.py
+++ b/lms/djangoapps/courseware/module_render.py
@@ -88,8 +88,7 @@ def toc_for_course(user, request, course, active_chapter, active_section):
 
     chapters = list()
     for chapter in course_module.get_display_items():
-        hide_from_toc = chapter.metadata.get('hide_from_toc','false').lower() == 'true'
-        if hide_from_toc:
+        if chapter.lms.hide_from_toc:
             continue
 
         sections = list()
@@ -97,18 +96,17 @@ def toc_for_course(user, request, course, active_chapter, active_section):
 
             active = (chapter.url_name == active_chapter and
                       section.url_name == active_section)
-            hide_from_toc = section.metadata.get('hide_from_toc', 'false').lower() == 'true'
 
-            if not hide_from_toc:
-                sections.append({'display_name': section.display_name,
+            if not section.lms.hide_from_toc:
+                sections.append({'display_name': section.lms.display_name,
                                  'url_name': section.url_name,
-                                 'format': section.metadata.get('format', ''),
-                                 'due': section.metadata.get('due', ''),
+                                 'format': section.lms.format,
+                                 'due': section.lms.due,
                                  'active': active,
-                                 'graded': section.metadata.get('graded', False),
+                                 'graded': section.lms.graded,
                                  })
 
-        chapters.append({'display_name': chapter.display_name,
+        chapters.append({'display_name': chapter.lms.display_name,
                          'url_name': chapter.url_name,
                          'sections': sections,
                          'active': chapter.url_name == active_chapter})
@@ -146,7 +144,8 @@ def get_module(user, request, location, student_module_cache, course_id, positio
         log.exception("Error in get_module")
         return None
 
-def _get_module(user, request, location, student_module_cache, course_id, position=None, wrap_xmodule_display = True):
+
+def _get_module(user, request, location, student_module_cache, course_id, position=None, wrap_xmodule_display=True):
     """
     Actually implement get_module.  See docstring there for details.
     """
@@ -268,7 +267,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
 
     module.get_html = replace_static_urls(
         _get_html,
-        module.metadata['data_dir'] if 'data_dir' in module.metadata else '', 
+        getattr(module, 'data_dir', ''),
         course_namespace = module.location._replace(category=None, name=None))
 
     # Allow URLs of the form '/course/' refer to the root of multicourse directory
diff --git a/lms/setup.py b/lms/setup.py
new file mode 100644
index 00000000000..1755b7d37e9
--- /dev/null
+++ b/lms/setup.py
@@ -0,0 +1,18 @@
+from setuptools import setup, find_packages
+
+setup(
+    name="edX LMS",
+    version="0.1",
+    install_requires=['distribute'],
+    requires=[
+        'xmodule',
+    ],
+
+    # See http://guide.python-distribute.org/creation.html#entry-points
+    # for a description of entry_points
+    entry_points={
+        'xmodule.namespace': [
+            'lms = lms.xmodule_namespace:LmsNamespace'
+        ],
+    }
+)
\ No newline at end of file
diff --git a/lms/templates/courseware/welcome-back.html b/lms/templates/courseware/welcome-back.html
index 5d4e0fe1e34..389a54aa534 100644
--- a/lms/templates/courseware/welcome-back.html
+++ b/lms/templates/courseware/welcome-back.html
@@ -1,3 +1,3 @@
-<h2>${chapter_module.display_name}</h2>
+<h2>${chapter_module.lms.display_name}</h2>
 
-<p>You were most recently in <a href="${prev_section_url}">${prev_section.display_name}</a>.  If you're done with that, choose another section on the left.</p> 
+<p>You were most recently in <a href="${prev_section_url}">${prev_section.lms.display_name}</a>.  If you're done with that, choose another section on the left.</p> 
diff --git a/lms/templates/video.html b/lms/templates/video.html
index 18c1bcbced6..1698ae256cb 100644
--- a/lms/templates/video.html
+++ b/lms/templates/video.html
@@ -2,7 +2,7 @@
   <h2> ${display_name} </h2>
 % endif
 
-<div id="video_${id}" class="video" data-streams="${streams}" data-caption-data-dir="${data_dir}" data-caption-asset-path="${caption_asset_path}" data-show-captions="${show_captions}">
+<div id="video_${id}" class="video" data-streams="${streams}" data-caption-asset-path="${caption_asset_path}" data-show-captions="${show_captions}">
   <div class="tc-wrapper">
     <article class="video-wrapper">
       <section class="video-player">
diff --git a/lms/xmodule_namespace.py b/lms/xmodule_namespace.py
new file mode 100644
index 00000000000..3673de73dff
--- /dev/null
+++ b/lms/xmodule_namespace.py
@@ -0,0 +1,23 @@
+from xmodule.model import Namespace, Boolean, Scope, String
+from xmodule.x_module import Date
+
+class LmsNamespace(Namespace):
+    hide_from_toc = Boolean(
+        help="Whether to display this module in the table of contents",
+        default=False,
+        scope=Scope.settings
+    )
+    graded = Boolean(
+        help="Whether this module contributes to the final course grade",
+        default=False,
+        scope=Scope.settings
+    )
+    format = String(
+        help="What format this module is in (used for deciding which "
+             "grader to apply, and what to show in the TOC)",
+        scope=Scope.settings
+    )
+
+    display_name = String(help="Display name for this module", scope=Scope.settings)
+    start = Date(help="Start time when this module is visible", scope=Scope.settings)
+    due = String(help="Date that this problem is due by", scope=Scope.settings, default='')
diff --git a/local-requirements.txt b/local-requirements.txt
index a4d153dd36b..2e951a180c9 100644
--- a/local-requirements.txt
+++ b/local-requirements.txt
@@ -1,3 +1,4 @@
 # Python libraries to install that are local to the mitx repo
 -e common/lib/capa
 -e common/lib/xmodule
+-e lms
-- 
GitLab