Commit 789ac3fc by Calen Pennington

Use the XBlock library as the base for XModule, so that we can incrementally…

Use the XBlock library as the base for XModule, so that we can incrementally rely on more and more of the XBlock api
parent 8874d9d7
...@@ -29,8 +29,8 @@ from django.conf import settings ...@@ -29,8 +29,8 @@ from django.conf import settings
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
from xmodule.model import Scope from xblock.core import Scope
from xmodule.runtime import KeyValueStore, DbModel, InvalidScopeError from xblock.runtime import KeyValueStore, DbModel, InvalidScopeError
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
from xmodule.errortracker import exc_info_to_str from xmodule.errortracker import exc_info_to_str
...@@ -472,7 +472,7 @@ def preview_module_system(request, preview_id, descriptor): ...@@ -472,7 +472,7 @@ def preview_module_system(request, preview_id, descriptor):
debug=True, debug=True,
replace_urls=replace_urls, replace_urls=replace_urls,
user=request.user, user=request.user,
xmodule_model_data=preview_model_data, xblock_model_data=preview_model_data,
) )
......
import datetime import datetime
from xmodule.model import Namespace, Boolean, Scope, ModelType, String from xblock.core import Namespace, Boolean, Scope, ModelType, String
class DateTuple(ModelType): class DateTuple(ModelType):
......
...@@ -6,7 +6,7 @@ from xmodule.x_module import XModule ...@@ -6,7 +6,7 @@ from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.xml_module import XmlDescriptor from xmodule.xml_module import XmlDescriptor
from xmodule.exceptions import InvalidDefinitionError from xmodule.exceptions import InvalidDefinitionError
from .model import String, Scope, Object, ModuleScope from xblock.core import String, Scope, Object, BlockScope
DEFAULT = "_DEFAULT_GROUP" DEFAULT = "_DEFAULT_GROUP"
......
...@@ -17,13 +17,13 @@ from progress import Progress ...@@ -17,13 +17,13 @@ from progress import Progress
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
from .model import Int, Scope, ModuleScope, ModelType, String, Boolean, Object, Float from xblock.core import Integer, Scope, BlockScope, ModelType, String, Boolean, Object, Float
from .fields import Timedelta from .fields import Timedelta
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
class StringyInt(Int): class StringyInteger(Integer):
""" """
A model type that converts from strings to integers when reading from json A model type that converts from strings to integers when reading from json
""" """
...@@ -57,8 +57,8 @@ class CapaModule(XModule): ...@@ -57,8 +57,8 @@ class CapaModule(XModule):
''' '''
icon_class = 'problem' icon_class = 'problem'
attempts = Int(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state) attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state)
max_attempts = StringyInt(help="Maximum number of attempts that a student is allowed", scope=Scope.settings) max_attempts = StringyInteger(help="Maximum number of attempts that a student is allowed", scope=Scope.settings)
due = String(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) graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted", scope=Scope.settings)
showanswer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed") showanswer = String(help="When to show the problem answer to the student", scope=Scope.settings, default="closed")
...@@ -69,7 +69,7 @@ class CapaModule(XModule): ...@@ -69,7 +69,7 @@ class CapaModule(XModule):
student_answers = Object(help="Dictionary with the current student responses", scope=Scope.student_state) student_answers = Object(help="Dictionary with the current student responses", scope=Scope.student_state)
done = Boolean(help="Whether the student has answered the problem", 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) display_name = String(help="Display name for this module", scope=Scope.settings)
seed = Int(help="Random seed for this student", scope=Scope.student_state) seed = Integer(help="Random seed for this student", scope=Scope.student_state)
js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'), js = {'coffee': [resource_string(__name__, 'js/src/capa/display.coffee'),
resource_string(__name__, 'js/src/collapsible.coffee'), resource_string(__name__, 'js/src/collapsible.coffee'),
......
...@@ -12,7 +12,7 @@ import requests ...@@ -12,7 +12,7 @@ import requests
import time import time
import copy import copy
from .model import Scope, ModelType, List, String, Object, Boolean from xblock.core import Scope, ModelType, List, String, Object, Boolean
from .fields import Date from .fields import Date
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
...@@ -3,7 +3,7 @@ from pkg_resources import resource_string, resource_listdir ...@@ -3,7 +3,7 @@ from pkg_resources import resource_string, resource_listdir
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from .model import String, Scope from xblock.core import String, Scope
class DiscussionModule(XModule): class DiscussionModule(XModule):
js = {'coffee': js = {'coffee':
......
from pkg_resources import resource_string from pkg_resources import resource_string
from xmodule.mako_module import MakoModuleDescriptor from xmodule.mako_module import MakoModuleDescriptor
from xmodule.model import Scope, String from xblock.core import Scope, String
import logging import logging
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
...@@ -8,7 +8,7 @@ from xmodule.x_module import XModule ...@@ -8,7 +8,7 @@ from xmodule.x_module import XModule
from xmodule.editing_module import JSONEditingDescriptor from xmodule.editing_module import JSONEditingDescriptor
from xmodule.errortracker import exc_info_to_str from xmodule.errortracker import exc_info_to_str
from xmodule.modulestore import Location from xmodule.modulestore import Location
from .model import String, Scope from xblock.core import String, Scope
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
...@@ -3,7 +3,7 @@ import logging ...@@ -3,7 +3,7 @@ import logging
import re import re
from datetime import timedelta from datetime import timedelta
from .model import ModelType from xblock.core import ModelType
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
...@@ -15,7 +15,7 @@ from .html_checker import check_html ...@@ -15,7 +15,7 @@ from .html_checker import check_html
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent from xmodule.contentstore.content import XASSET_SRCREF_PREFIX, StaticContent
from .model import Scope, String from xblock.core import Scope, String
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
......
from .x_module import XModuleDescriptor, DescriptorSystem from .x_module import XModuleDescriptor, DescriptorSystem
from .model import Scope from xblock.core import Scope
import logging import logging
......
from collections import namedtuple
from .plugin import Plugin
class ModuleScope(object):
USAGE, DEFINITION, TYPE, ALL = xrange(4)
class Scope(namedtuple('ScopeBase', 'student module')):
pass
Scope.content = Scope(student=False, module=ModuleScope.DEFINITION)
Scope.settings = Scope(student=False, module=ModuleScope.USAGE)
Scope.student_state = Scope(student=True, module=ModuleScope.USAGE)
Scope.student_preferences = Scope(student=True, module=ModuleScope.TYPE)
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, computed_default=None):
self._seq = self.sequence
self._name = "unknown"
self.help = help
self.default = default
self.computed_default = computed_default
self.scope = scope
ModelType.sequence += 1
@property
def name(self):
return self._name
def __get__(self, instance, owner):
if instance is None:
return self
try:
return self.from_json(instance._model_data[self.name])
except KeyError:
if self.default is None and self.computed_default is not None:
return self.computed_default(instance)
return self.default
def __set__(self, instance, value):
instance._model_data[self.name] = self.to_json(value)
def __delete__(self, instance):
del instance._model_data[self.name]
def __repr__(self):
return "<{0.__class__.__name__} {0._name}>".format(self)
def __lt__(self, other):
return self._seq < other._seq
def to_json(self, value):
"""
Return value in the form of nested lists and dictionaries (suitable
for passing to json.dumps).
This is called during field writes to convert the native python
type to the value stored in the database
"""
return value
def from_json(self, value):
"""
Return value as a native full featured python type (the inverse of to_json)
Called during field reads to convert the stored value into a full featured python
object
"""
return value
def read_from(self, model):
"""
Retrieve the value for this field from the specified model object
"""
return self.__get__(model, model.__class__)
def write_to(self, model, value):
"""
Set the value for this field to value on the supplied model object
"""
self.__set__(model, value)
def delete_from(self, model):
"""
Delete the value for this field from the supplied model object
"""
self.__delete__(model)
Int = Float = Boolean = Object = List = String = Any = ModelType
class ModelMetaclass(type):
"""
A metaclass to be used for classes that want to use ModelTypes as class attributes
to define data access.
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):
v._name = n
fields.append(v)
fields.sort()
attrs['fields'] = sum([
base.fields
for base
in bases
if hasattr(base, '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):
namespaces = []
for ns_name, namespace in Namespace.load_classes():
if issubclass(namespace, Namespace):
attrs[ns_name] = NamespaceDescriptor(namespace)
namespaces.append(ns_name)
attrs['namespaces'] = namespaces
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):
return self._namespace(instance)
class Namespace(Plugin):
"""
A baseclass that sets up machinery for ModelType fields that makes those fields be called
with the container as the field instance
"""
__metaclass__ = ModelMetaclass
entry_point = 'xmodule.namespace'
def __init__(self, container):
self._container = container
def __getattribute__(self, name):
container = super(Namespace, self).__getattribute__('_container')
namespace_attr = getattr(type(self), name, None)
if namespace_attr is None or not isinstance(namespace_attr, ModelType):
return super(Namespace, self).__getattribute__(name)
return namespace_attr.__get__(container, type(container))
def __setattr__(self, name, value):
try:
container = super(Namespace, self).__getattribute__('_container')
except AttributeError:
super(Namespace, self).__setattr__(name, value)
return
namespace_attr = getattr(type(self), name, None)
if namespace_attr is None or not isinstance(namespace_attr, ModelType):
return super(Namespace, self).__setattr__(name, value)
return namespace_attr.__set__(container, value)
def __delattr__(self, name):
container = super(Namespace, self).__getattribute__('_container')
namespace_attr = getattr(type(self), name, None)
if namespace_attr is None or not isinstance(namespace_attr, ModelType):
return super(Namespace, self).__detattr__(name)
return namespace_attr.__delete__(container)
from xmodule.model import Scope from xblock.core import Scope
# A list of metadata that this module can inherit from its parent module # A list of metadata that this module can inherit from its parent module
INHERITABLE_METADATA = ( INHERITABLE_METADATA = (
......
...@@ -13,8 +13,8 @@ from xmodule.errortracker import null_error_tracker, exc_info_to_str ...@@ -13,8 +13,8 @@ from xmodule.errortracker import null_error_tracker, exc_info_to_str
from xmodule.mako_module import MakoDescriptorSystem from xmodule.mako_module import MakoDescriptorSystem
from xmodule.x_module import XModuleDescriptor from xmodule.x_module import XModuleDescriptor
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
from xmodule.runtime import DbModel, KeyValueStore, InvalidScopeError from xblock.runtime import DbModel, KeyValueStore, InvalidScopeError
from xmodule.model import Scope from xblock.core import Scope
from . import ModuleStoreBase, Location from . import ModuleStoreBase, Location
from .draft import DraftModuleStore from .draft import DraftModuleStore
...@@ -41,8 +41,10 @@ class MongoKeyValueStore(KeyValueStore): ...@@ -41,8 +41,10 @@ class MongoKeyValueStore(KeyValueStore):
self._metadata = metadata self._metadata = metadata
def get(self, key): def get(self, key):
if key.field_name == 'children': if key.scope == Scope.children:
return self._children return self._children
elif key.scope == Scope.parent:
return None
elif key.scope == Scope.settings: elif key.scope == Scope.settings:
return self._metadata[key.field_name] return self._metadata[key.field_name]
elif key.scope == Scope.content: elif key.scope == Scope.content:
...@@ -54,7 +56,7 @@ class MongoKeyValueStore(KeyValueStore): ...@@ -54,7 +56,7 @@ class MongoKeyValueStore(KeyValueStore):
raise InvalidScopeError(key.scope) raise InvalidScopeError(key.scope)
def set(self, key, value): def set(self, key, value):
if key.field_name == 'children': if key.scope == Scope.children:
self._children = value self._children = value
elif key.scope == Scope.settings: elif key.scope == Scope.settings:
self._metadata[key.field_name] = value self._metadata[key.field_name] = value
...@@ -67,7 +69,7 @@ class MongoKeyValueStore(KeyValueStore): ...@@ -67,7 +69,7 @@ class MongoKeyValueStore(KeyValueStore):
raise InvalidScopeError(key.scope) raise InvalidScopeError(key.scope)
def delete(self, key): def delete(self, key):
if key.field_name == 'children': if key.scope == Scope.children:
self._children = [] self._children = []
elif key.scope == Scope.settings: elif key.scope == Scope.settings:
if key.field_name in self._metadata: if key.field_name in self._metadata:
......
...@@ -6,7 +6,7 @@ from pkg_resources import resource_string, resource_listdir ...@@ -6,7 +6,7 @@ from pkg_resources import resource_string, resource_listdir
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from .model import Int, Scope, Boolean from xblock.core import Integer, Scope, Boolean
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -16,8 +16,8 @@ class PollModule(XModule): ...@@ -16,8 +16,8 @@ class PollModule(XModule):
js = {'coffee': [resource_string(__name__, 'js/src/poll/display.coffee')]} js = {'coffee': [resource_string(__name__, 'js/src/poll/display.coffee')]}
js_module_name = "PollModule" js_module_name = "PollModule"
upvotes = Int(help="Number of upvotes this poll has recieved", scope=Scope.content, default=0) upvotes = Integer(help="Number of upvotes this poll has recieved", scope=Scope.content, default=0)
downvotes = Int(help="Number of downvotes this poll has recieved", scope=Scope.content, default=0) downvotes = Integer(help="Number of downvotes this poll has recieved", scope=Scope.content, default=0)
voted = Boolean(help="Whether this student has voted on the poll", scope=Scope.student_state, default=False) voted = Boolean(help="Whether this student has voted on the poll", scope=Scope.student_state, default=False)
def handle_ajax(self, dispatch, get): def handle_ajax(self, dispatch, get):
......
...@@ -3,7 +3,7 @@ from xmodule.editing_module import XMLEditingDescriptor ...@@ -3,7 +3,7 @@ from xmodule.editing_module import XMLEditingDescriptor
from xmodule.xml_module import XmlDescriptor from xmodule.xml_module import XmlDescriptor
import logging import logging
import sys import sys
from .model import String, Scope from xblock.core import String, Scope
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
......
from collections import MutableMapping, namedtuple
from .model import ModuleScope, ModelType
class InvalidScopeError(Exception):
"""
Raised to indicated that operating on the supplied scope isn't allowed by a KeyValueStore
"""
pass
class KeyValueStore(object):
"""The abstract interface for Key Value Stores."""
# Keys are structured to retain information about the scope of the data.
# Stores can use this information however they like to store and retrieve
# data.
Key = namedtuple("Key", "scope, student_id, module_scope_id, field_name")
def get(self, key):
pass
def set(self, key, value):
pass
def delete(self, key):
pass
class DbModel(MutableMapping):
"""A dictionary-like interface to the fields on a module."""
def __init__(self, kvs, module_cls, student_id, usage):
self._kvs = kvs
self._student_id = student_id
self._module_cls = module_cls
self._usage = usage
def __repr__(self):
return "<{0.__class__.__name__} {0._module_cls!r}>".format(self)
def _getfield(self, name):
# First, get the field from the class, if defined
module_field = getattr(self._module_cls, name, None)
if module_field is not None and isinstance(module_field, ModelType):
return module_field
# If the class doesn't have the field, and it also
# doesn't have any namespaces, then the the name isn't a field
# so KeyError
if not hasattr(self._module_cls, 'namespaces'):
return KeyError(name)
# Resolve the field name in the first namespace where it's
# available
for namespace_name in self._module_cls.namespaces:
namespace = getattr(self._module_cls, namespace_name)
namespace_field = getattr(type(namespace), name, None)
if namespace_field is not None and isinstance(namespace_field, ModelType):
return namespace_field
# Not in the class or in any of the namespaces, so name
# really doesn't name a field
raise KeyError(name)
def _key(self, name):
field = self._getfield(name)
if field.scope is None:
module_id = None
student_id = None
else:
module = field.scope.module
if module == ModuleScope.ALL:
module_id = None
elif module == ModuleScope.USAGE:
module_id = self._usage.id
elif module == ModuleScope.DEFINITION:
module_id = self._usage.def_id
elif module == ModuleScope.TYPE:
module_id = self._module_cls.__name__
if field.scope.student:
student_id = self._student_id
else:
student_id = None
key = KeyValueStore.Key(
scope=field.scope,
student_id=student_id,
module_scope_id=module_id,
field_name=name
)
return key
def __getitem__(self, name):
return self._kvs.get(self._key(name))
def __setitem__(self, name, value):
self._kvs.set(self._key(name), value)
def __delitem__(self, name):
self._kvs.delete(self._key(name))
def __iter__(self):
return iter(self.keys())
def __len__(self):
return len(self.keys())
def __contains__(self, item):
return item in self.keys()
def keys(self):
fields = [field.name for field in self._module_cls.fields]
for namespace_name in self._module_cls.namespaces:
fields.extend(field.name for field in getattr(self._module_cls, namespace_name).fields)
return fields
...@@ -23,7 +23,7 @@ from .editing_module import EditingDescriptor ...@@ -23,7 +23,7 @@ from .editing_module import EditingDescriptor
from .stringify import stringify_children from .stringify import stringify_children
from .x_module import XModule from .x_module import XModule
from .xml_module import XmlDescriptor from .xml_module import XmlDescriptor
from .model import List, String, Scope, Int from xblock.core import List, String, Scope, Integer
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -67,10 +67,10 @@ class SelfAssessmentModule(XModule): ...@@ -67,10 +67,10 @@ class SelfAssessmentModule(XModule):
# Used for progress / grading. Currently get credit just for # Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect). # completion (doesn't matter if you self-assessed correct/incorrect).
max_score = Int(scope=Scope.settings, default=MAX_SCORE) max_score = Integer(scope=Scope.settings, default=MAX_SCORE)
attempts = Int(scope=Scope.student_state, default=0), Int attempts = Integer(scope=Scope.student_state, default=0)
max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS) max_attempts = Integer(scope=Scope.settings, default=MAX_ATTEMPTS)
rubric = String(scope=Scope.content) rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content) prompt = String(scope=Scope.content)
submitmessage = String(scope=Scope.content) submitmessage = String(scope=Scope.content)
...@@ -392,9 +392,9 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): ...@@ -392,9 +392,9 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
# Used for progress / grading. Currently get credit just for # Used for progress / grading. Currently get credit just for
# completion (doesn't matter if you self-assessed correct/incorrect). # completion (doesn't matter if you self-assessed correct/incorrect).
max_score = Int(scope=Scope.settings, default=MAX_SCORE) max_score = Integer(scope=Scope.settings, default=MAX_SCORE)
max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS) max_attempts = Integer(scope=Scope.settings, default=MAX_ATTEMPTS)
rubric = String(scope=Scope.content) rubric = String(scope=Scope.content)
prompt = String(scope=Scope.content) prompt = String(scope=Scope.content)
submitmessage = String(scope=Scope.content) submitmessage = String(scope=Scope.content)
......
...@@ -8,7 +8,7 @@ from xmodule.xml_module import XmlDescriptor ...@@ -8,7 +8,7 @@ from xmodule.xml_module import XmlDescriptor
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.progress import Progress from xmodule.progress import Progress
from xmodule.exceptions import NotFoundError from xmodule.exceptions import NotFoundError
from .model import Int, Scope from xblock.core import Integer, Scope
from pkg_resources import resource_string from pkg_resources import resource_string
log = logging.getLogger("mitx.common.lib.seq_module") log = logging.getLogger("mitx.common.lib.seq_module")
...@@ -37,7 +37,7 @@ class SequenceModule(XModule): ...@@ -37,7 +37,7 @@ class SequenceModule(XModule):
# NOTE: Position is 1-indexed. This is silly, but there are now student # NOTE: Position is 1-indexed. This is silly, but there are now student
# positions saved on prod, so it's not easy to fix. # positions saved on prod, so it's not easy to fix.
position = Int(help="Last tab viewed in this sequence", scope=Scope.student_state) position = Integer(help="Last tab viewed in this sequence", scope=Scope.student_state)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
XModule.__init__(self, *args, **kwargs) XModule.__init__(self, *args, **kwargs)
......
...@@ -31,7 +31,7 @@ i4xs = ModuleSystem( ...@@ -31,7 +31,7 @@ i4xs = ModuleSystem(
xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10}, xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10},
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
anonymous_student_id = 'student', anonymous_student_id = 'student',
xmodule_model_data = lambda x: x, xblock_model_data = lambda x: x,
) )
......
from mock import patch
from unittest import TestCase
from nose.tools import assert_in, assert_equals, assert_raises
from xmodule.model import *
def test_model_metaclass():
class ModelMetaclassTester(object):
__metaclass__ = ModelMetaclass
field_a = Int(scope=Scope.settings)
field_b = Int(scope=Scope.content)
def __init__(self, model_data):
self._model_data = model_data
class ChildClass(ModelMetaclassTester):
pass
assert hasattr(ModelMetaclassTester, 'field_a')
assert hasattr(ModelMetaclassTester, 'field_b')
assert_in(ModelMetaclassTester.field_a, ModelMetaclassTester.fields)
assert_in(ModelMetaclassTester.field_b, ModelMetaclassTester.fields)
assert hasattr(ChildClass, 'field_a')
assert hasattr(ChildClass, 'field_b')
assert_in(ChildClass.field_a, ChildClass.fields)
assert_in(ChildClass.field_b, ChildClass.fields)
def test_parent_metaclass():
class HasChildren(object):
__metaclass__ = ParentModelMetaclass
has_children = True
class WithoutChildren(object):
__metaclass = ParentModelMetaclass
assert hasattr(HasChildren, 'children')
assert not hasattr(WithoutChildren, 'children')
assert isinstance(HasChildren.children, List)
assert_equals(None, HasChildren.children.scope)
def test_field_access():
class FieldTester(object):
__metaclass__ = ModelMetaclass
field_a = Int(scope=Scope.settings)
field_b = Int(scope=Scope.content, default=10)
field_c = Int(scope=Scope.student_state, computed_default=lambda s: s.field_a + s.field_b)
def __init__(self, model_data):
self._model_data = model_data
field_tester = FieldTester({'field_a': 5, 'field_x': 15})
assert_equals(5, field_tester.field_a)
assert_equals(10, field_tester.field_b)
assert_equals(15, field_tester.field_c)
assert not hasattr(field_tester, 'field_x')
field_tester.field_a = 20
assert_equals(20, field_tester._model_data['field_a'])
assert_equals(10, field_tester.field_b)
assert_equals(30, field_tester.field_c)
del field_tester.field_a
assert_equals(None, field_tester.field_a)
assert hasattr(FieldTester, 'field_a')
class TestNamespace(Namespace):
field_x = List(scope=Scope.content)
field_y = String(scope=Scope.student_state, default="default_value")
@patch('xmodule.model.Namespace.load_classes', return_value=[('test', TestNamespace)])
def test_namespace_metaclass(mock_load_classes):
class TestClass(object):
__metaclass__ = NamespacesMetaclass
assert hasattr(TestClass, 'test')
assert hasattr(TestClass.test, 'field_x')
assert hasattr(TestClass.test, 'field_y')
assert_in(TestNamespace.field_x, TestClass.test.fields)
assert_in(TestNamespace.field_y, TestClass.test.fields)
assert isinstance(TestClass.test, Namespace)
@patch('xmodule.model.Namespace.load_classes', return_value=[('test', TestNamespace)])
def test_namespace_field_access(mock_load_classes):
class Metaclass(ModelMetaclass, NamespacesMetaclass):
pass
class FieldTester(object):
__metaclass__ = Metaclass
field_a = Int(scope=Scope.settings)
field_b = Int(scope=Scope.content, default=10)
field_c = Int(scope=Scope.student_state, computed_default=lambda s: s.field_a + s.field_b)
def __init__(self, model_data):
self._model_data = model_data
field_tester = FieldTester({
'field_a': 5,
'field_x': [1, 2, 3],
})
assert_equals(5, field_tester.field_a)
assert_equals(10, field_tester.field_b)
assert_equals(15, field_tester.field_c)
assert_equals([1, 2, 3], field_tester.test.field_x)
assert_equals('default_value', field_tester.test.field_y)
field_tester.test.field_x = ['a', 'b']
assert_equals(['a', 'b'], field_tester._model_data['field_x'])
del field_tester.test.field_x
assert_equals(None, field_tester.test.field_x)
assert_raises(AttributeError, getattr, field_tester.test, 'field_z')
assert_raises(AttributeError, delattr, field_tester.test, 'field_z')
# Namespaces are created on the fly, so setting a new attribute on one
# has no long-term effect
field_tester.test.field_z = 'foo'
assert_raises(AttributeError, getattr, field_tester.test, 'field_z')
assert 'field_z' not in field_tester._model_data
def test_field_serialization():
class CustomField(ModelType):
def from_json(self, value):
return value['value']
def to_json(self, value):
return {'value': value}
class FieldTester(object):
__metaclass__ = ModelMetaclass
field = CustomField()
def __init__(self, model_data):
self._model_data = model_data
field_tester = FieldTester({
'field': {'value': 4}
})
assert_equals(4, field_tester.field)
field_tester.field = 5
assert_equals({'value': 5}, field_tester._model_data['field'])
from nose.tools import assert_equals
from mock import patch
from xmodule.model import *
from xmodule.runtime import *
class Metaclass(NamespacesMetaclass, ParentModelMetaclass, ModelMetaclass):
pass
class TestNamespace(Namespace):
n_content = String(scope=Scope.content, default='nc')
n_settings = String(scope=Scope.settings, default='ns')
n_student_state = String(scope=Scope.student_state, default='nss')
n_student_preferences = String(scope=Scope.student_preferences, default='nsp')
n_student_info = String(scope=Scope.student_info, default='nsi')
n_by_type = String(scope=Scope(False, ModuleScope.TYPE), default='nbt')
n_for_all = String(scope=Scope(False, ModuleScope.ALL), default='nfa')
n_student_def = String(scope=Scope(True, ModuleScope.DEFINITION), default='nsd')
with patch('xmodule.model.Namespace.load_classes', return_value=[('test', TestNamespace)]):
class TestModel(object):
__metaclass__ = Metaclass
content = String(scope=Scope.content, default='c')
settings = String(scope=Scope.settings, default='s')
student_state = String(scope=Scope.student_state, default='ss')
student_preferences = String(scope=Scope.student_preferences, default='sp')
student_info = String(scope=Scope.student_info, default='si')
by_type = String(scope=Scope(False, ModuleScope.TYPE), default='bt')
for_all = String(scope=Scope(False, ModuleScope.ALL), default='fa')
student_def = String(scope=Scope(True, ModuleScope.DEFINITION), default='sd')
def __init__(self, model_data):
self._model_data = model_data
class DictKeyValueStore(KeyValueStore):
def __init__(self):
self.db = {}
def get(self, key):
return self.db[key]
def set(self, key, value):
self.db[key] = value
def delete(self, key):
del self.db[key]
Usage = namedtuple('Usage', 'id, def_id')
def check_field(collection, field):
print "Getting %s from %r" % (field.name, collection)
assert_equals(field.default, getattr(collection, field.name))
new_value = 'new ' + field.name
print "Setting %s to %s on %r" % (field.name, new_value, collection)
setattr(collection, field.name, new_value)
print "Checking %s on %r" % (field.name, collection)
assert_equals(new_value, getattr(collection, field.name))
print "Deleting %s from %r" % (field.name, collection)
delattr(collection, field.name)
print "Back to defaults for %s in %r" % (field.name, collection)
assert_equals(field.default, getattr(collection, field.name))
def test_namespace_actions():
tester = TestModel(DbModel(DictKeyValueStore(), TestModel, 's0', Usage('u0', 'd0')))
for collection in (tester, tester.test):
for field in collection.fields:
yield check_field, collection, field
def test_db_model_keys():
key_store = DictKeyValueStore()
tester = TestModel(DbModel(key_store, TestModel, 's0', Usage('u0', 'd0')))
for collection in (tester, tester.test):
for field in collection.fields:
new_value = 'new ' + field.name
setattr(collection, field.name, new_value)
print key_store.db
assert_equals('new content', key_store.db[KeyValueStore.Key(Scope.content, None, 'd0', 'content')])
assert_equals('new settings', key_store.db[KeyValueStore.Key(Scope.settings, None, 'u0', 'settings')])
assert_equals('new student_state', key_store.db[KeyValueStore.Key(Scope.student_state, 's0', 'u0', 'student_state')])
assert_equals('new student_preferences', key_store.db[KeyValueStore.Key(Scope.student_preferences, 's0', 'TestModel', 'student_preferences')])
assert_equals('new student_info', key_store.db[KeyValueStore.Key(Scope.student_info, 's0', None, 'student_info')])
assert_equals('new by_type', key_store.db[KeyValueStore.Key(Scope(False, ModuleScope.TYPE), None, 'TestModel', 'by_type')])
assert_equals('new for_all', key_store.db[KeyValueStore.Key(Scope(False, ModuleScope.ALL), None, None, 'for_all')])
assert_equals('new student_def', key_store.db[KeyValueStore.Key(Scope(True, ModuleScope.DEFINITION), 's0', 'd0', 'student_def')])
assert_equals('new n_content', key_store.db[KeyValueStore.Key(Scope.content, None, 'd0', 'n_content')])
assert_equals('new n_settings', key_store.db[KeyValueStore.Key(Scope.settings, None, 'u0', 'n_settings')])
assert_equals('new n_student_state', key_store.db[KeyValueStore.Key(Scope.student_state, 's0', 'u0', 'n_student_state')])
assert_equals('new n_student_preferences', key_store.db[KeyValueStore.Key(Scope.student_preferences, 's0', 'TestModel', 'n_student_preferences')])
assert_equals('new n_student_info', key_store.db[KeyValueStore.Key(Scope.student_info, 's0', None, 'n_student_info')])
assert_equals('new n_by_type', key_store.db[KeyValueStore.Key(Scope(False, ModuleScope.TYPE), None, 'TestModel', 'n_by_type')])
assert_equals('new n_for_all', key_store.db[KeyValueStore.Key(Scope(False, ModuleScope.ALL), None, None, 'n_for_all')])
assert_equals('new n_student_def', key_store.db[KeyValueStore.Key(Scope(True, ModuleScope.DEFINITION), 's0', 'd0', 'n_student_def')])
...@@ -9,7 +9,7 @@ from xmodule.raw_module import RawDescriptor ...@@ -9,7 +9,7 @@ from xmodule.raw_module import RawDescriptor
from xmodule.modulestore.mongo import MongoModuleStore from xmodule.modulestore.mongo import MongoModuleStore
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.contentstore.content import StaticContent from xmodule.contentstore.content import StaticContent
from .model import Int, Scope, String from xblock.core import Integer, Scope, String
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -29,7 +29,7 @@ class VideoModule(XModule): ...@@ -29,7 +29,7 @@ class VideoModule(XModule):
js_module_name = "Video" js_module_name = "Video"
data = String(help="XML data for the problem", scope=Scope.content) data = String(help="XML data for the problem", scope=Scope.content)
position = Int(help="Current position in the video", scope=Scope.student_state) position = Integer(help="Current position in the video", scope=Scope.student_state)
display_name = String(help="Display name for this module", scope=Scope.settings) display_name = String(help="Display name for this module", scope=Scope.settings)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
......
...@@ -10,12 +10,7 @@ from pkg_resources import resource_listdir, resource_string, resource_isdir ...@@ -10,12 +10,7 @@ from pkg_resources import resource_listdir, resource_string, resource_isdir
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
from .model import ModelMetaclass, ParentModelMetaclass, NamespacesMetaclass from xblock.core import XBlock
from .plugin import Plugin
class XModuleMetaclass(ParentModelMetaclass, NamespacesMetaclass, ModelMetaclass):
pass
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -88,7 +83,7 @@ class HTMLSnippet(object): ...@@ -88,7 +83,7 @@ class HTMLSnippet(object):
.format(self.__class__)) .format(self.__class__))
class XModule(HTMLSnippet): class XModule(HTMLSnippet, XBlock):
''' Implements a generic learning module. ''' Implements a generic learning module.
Subclasses must at a minimum provide a definition for get_html in order Subclasses must at a minimum provide a definition for get_html in order
...@@ -97,8 +92,6 @@ class XModule(HTMLSnippet): ...@@ -97,8 +92,6 @@ class XModule(HTMLSnippet):
See the HTML module for a simple example. See the HTML module for a simple example.
''' '''
__metaclass__ = XModuleMetaclass
# The default implementation of get_icon_class returns the icon_class # The default implementation of get_icon_class returns the icon_class
# attribute of the class # attribute of the class
# #
...@@ -266,7 +259,7 @@ class ResourceTemplates(object): ...@@ -266,7 +259,7 @@ class ResourceTemplates(object):
return templates return templates
class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): class XModuleDescriptor(HTMLSnippet, ResourceTemplates, XBlock):
""" """
An XModuleDescriptor is a specification for an element of a course. This An XModuleDescriptor is a specification for an element of a course. This
could be a problem, an organizational element (a group of content), or a could be a problem, an organizational element (a group of content), or a
...@@ -279,7 +272,6 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): ...@@ -279,7 +272,6 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
""" """
entry_point = "xmodule.v1" entry_point = "xmodule.v1"
module_class = XModule module_class = XModule
__metaclass__ = XModuleMetaclass
# Attributes for inspection of the descriptor # Attributes for inspection of the descriptor
...@@ -371,7 +363,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): ...@@ -371,7 +363,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates):
system, system,
self.location, self.location,
self, self,
system.xmodule_model_data(self._model_data), system.xblock_model_data(self._model_data),
) )
def has_dynamic_children(self): def has_dynamic_children(self):
...@@ -608,7 +600,7 @@ class ModuleSystem(object): ...@@ -608,7 +600,7 @@ class ModuleSystem(object):
get_module, get_module,
render_template, render_template,
replace_urls, replace_urls,
xmodule_model_data, xblock_model_data,
user=None, user=None,
filestore=None, filestore=None,
debug=False, debug=False,
...@@ -663,7 +655,7 @@ class ModuleSystem(object): ...@@ -663,7 +655,7 @@ class ModuleSystem(object):
self.node_path = node_path self.node_path = node_path
self.anonymous_student_id = anonymous_student_id self.anonymous_student_id = anonymous_student_id
self.user_is_staff = user is not None and user.is_staff self.user_is_staff = user is not None and user.is_staff
self.xmodule_model_data = xmodule_model_data self.xblock_model_data = xblock_model_data
if publish is None: if publish is None:
publish = lambda e: None publish = lambda e: None
......
from xmodule.x_module import (XModuleDescriptor, policy_key) from xmodule.x_module import (XModuleDescriptor, policy_key)
from xmodule.modulestore import Location from xmodule.modulestore import Location
from xmodule.modulestore.inheritance import own_metadata from xmodule.modulestore.inheritance import own_metadata
from xmodule.model import Object, Scope from xblock.core import Object, Scope
from lxml import etree from lxml import etree
import json import json
import copy import copy
......
...@@ -3,3 +3,4 @@ ...@@ -3,3 +3,4 @@
-e git://github.com/MITx/django-pipeline.git#egg=django-pipeline -e git://github.com/MITx/django-pipeline.git#egg=django-pipeline
-e git://github.com/MITx/django-wiki.git@e2e84558#egg=django-wiki -e git://github.com/MITx/django-wiki.git@e2e84558#egg=django-wiki
-e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev -e git://github.com/dementrock/pystache_custom.git@776973740bdaad83a3b029f96e415a7d1e8bec2f#egg=pystache_custom-dev
-e git+ssh://git@github.com/MITx/xmodule-debugger@ada10f2991cdd61c60ec223b4e0b9b4e06d7cdc3#egg=XBlock
\ No newline at end of file
...@@ -8,8 +8,8 @@ from .models import ( ...@@ -8,8 +8,8 @@ from .models import (
XModuleStudentInfoField XModuleStudentInfoField
) )
from xmodule.runtime import KeyValueStore, InvalidScopeError from xblock.runtime import KeyValueStore, InvalidScopeError
from xmodule.model import Scope from xblock.core import Scope
class InvalidWriteError(Exception): class InvalidWriteError(Exception):
...@@ -45,20 +45,20 @@ class LmsKeyValueStore(KeyValueStore): ...@@ -45,20 +45,20 @@ class LmsKeyValueStore(KeyValueStore):
def _student_module(self, key): def _student_module(self, key):
student_module = self._student_module_cache.lookup( student_module = self._student_module_cache.lookup(
self._course_id, key.module_scope_id.category, key.module_scope_id.url() self._course_id, key.block_scope_id.category, key.block_scope_id.url()
) )
return student_module return student_module
def _field_object(self, key): def _field_object(self, key):
if key.scope == Scope.content: if key.scope == Scope.content:
return XModuleContentField, {'field_name': key.field_name, 'definition_id': key.module_scope_id} return XModuleContentField, {'field_name': key.field_name, 'definition_id': key.block_scope_id}
elif key.scope == Scope.settings: elif key.scope == Scope.settings:
return XModuleSettingsField, { return XModuleSettingsField, {
'field_name': key.field_name, 'field_name': key.field_name,
'usage_id': '%s-%s' % (self._course_id, key.module_scope_id) 'usage_id': '%s-%s' % (self._course_id, key.block_scope_id)
} }
elif key.scope == Scope.student_preferences: elif key.scope == Scope.student_preferences:
return XModuleStudentPrefsField, {'field_name': key.field_name, 'student': self._user, 'module_type': key.module_scope_id} return XModuleStudentPrefsField, {'field_name': key.field_name, 'student': self._user, 'module_type': key.block_scope_id}
elif key.scope == Scope.student_info: elif key.scope == Scope.student_info:
return XModuleStudentInfoField, {'field_name': key.field_name, 'student': self._user} return XModuleStudentInfoField, {'field_name': key.field_name, 'student': self._user}
...@@ -68,6 +68,9 @@ class LmsKeyValueStore(KeyValueStore): ...@@ -68,6 +68,9 @@ class LmsKeyValueStore(KeyValueStore):
if key.field_name in self._descriptor_model_data: if key.field_name in self._descriptor_model_data:
return self._descriptor_model_data[key.field_name] return self._descriptor_model_data[key.field_name]
if key.scope == Scope.parent:
return None
if key.scope == Scope.student_state: if key.scope == Scope.student_state:
student_module = self._student_module(key) student_module = self._student_module(key)
...@@ -92,8 +95,8 @@ class LmsKeyValueStore(KeyValueStore): ...@@ -92,8 +95,8 @@ class LmsKeyValueStore(KeyValueStore):
student_module = StudentModule( student_module = StudentModule(
course_id=self._course_id, course_id=self._course_id,
student=self._user, student=self._user,
module_type=key.module_scope_id.category, module_type=key.block_scope_id.category,
module_state_key=key.module_scope_id.url(), module_state_key=key.block_scope_id.url(),
state=json.dumps({}) state=json.dumps({})
) )
self._student_module_cache.append(student_module) self._student_module_cache.append(student_module)
......
...@@ -26,7 +26,7 @@ from xmodule.modulestore import Location ...@@ -26,7 +26,7 @@ from xmodule.modulestore import Location
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor from xmodule.error_module import ErrorDescriptor, NonStaffErrorDescriptor
from xmodule.runtime import DbModel from xblock.runtime import DbModel
from xmodule_modifiers import replace_course_urls, replace_static_urls, add_histogram, wrap_xmodule from xmodule_modifiers import replace_course_urls, replace_static_urls, add_histogram, wrap_xmodule
from .model_data import LmsKeyValueStore, LmsUsage from .model_data import LmsKeyValueStore, LmsUsage
...@@ -203,7 +203,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi ...@@ -203,7 +203,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
return get_module(user, request, location, return get_module(user, request, location,
student_module_cache, course_id, position) student_module_cache, course_id, position)
def xmodule_model_data(descriptor_model_data): def xblock_model_data(descriptor_model_data):
return DbModel( return DbModel(
LmsKeyValueStore(course_id, user, descriptor_model_data, student_module_cache), LmsKeyValueStore(course_id, user, descriptor_model_data, student_module_cache),
descriptor.module_class, descriptor.module_class,
...@@ -260,7 +260,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi ...@@ -260,7 +260,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
replace_urls=replace_urls, replace_urls=replace_urls,
node_path=settings.NODE_PATH, node_path=settings.NODE_PATH,
anonymous_student_id=anonymous_student_id, anonymous_student_id=anonymous_student_id,
xmodule_model_data=xmodule_model_data, xblock_model_data=xblock_model_data,
publish=publish, publish=publish,
) )
# pass position specified in URL to module through ModuleSystem # pass position specified in URL to module through ModuleSystem
......
...@@ -7,7 +7,7 @@ from functools import partial ...@@ -7,7 +7,7 @@ from functools import partial
from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError
from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField, XModuleStudentInfoField, XModuleStudentPrefsField, StudentModuleCache from courseware.models import StudentModule, XModuleContentField, XModuleSettingsField, XModuleStudentInfoField, XModuleStudentPrefsField, StudentModuleCache
from xmodule.model import Scope, ModuleScope from xblock.core import Scope, BlockScope
from xmodule.modulestore import Location from xmodule.modulestore import Location
from django.test import TestCase from django.test import TestCase
...@@ -112,9 +112,9 @@ class TestInvalidScopes(TestCase): ...@@ -112,9 +112,9 @@ class TestInvalidScopes(TestCase):
self.kvs = LmsKeyValueStore(course_id, UserFactory.build(), self.desc_md, None) self.kvs = LmsKeyValueStore(course_id, UserFactory.build(), self.desc_md, None)
def test_invalid_scopes(self): def test_invalid_scopes(self):
for scope in (Scope(student=True, module=ModuleScope.DEFINITION), for scope in (Scope(student=True, block=BlockScope.DEFINITION),
Scope(student=False, module=ModuleScope.TYPE), Scope(student=False, block=BlockScope.TYPE),
Scope(student=False, module=ModuleScope.ALL)): Scope(student=False, block=BlockScope.ALL)):
self.assertRaises(InvalidScopeError, self.kvs.get, LmsKeyValueStore.Key(scope, None, None, 'field')) self.assertRaises(InvalidScopeError, self.kvs.get, LmsKeyValueStore.Key(scope, None, None, 'field'))
self.assertRaises(InvalidScopeError, self.kvs.set, LmsKeyValueStore.Key(scope, None, None, 'field'), 'value') self.assertRaises(InvalidScopeError, self.kvs.set, LmsKeyValueStore.Key(scope, None, None, 'field'), 'value')
self.assertRaises(InvalidScopeError, self.kvs.delete, LmsKeyValueStore.Key(scope, None, None, 'field')) self.assertRaises(InvalidScopeError, self.kvs.delete, LmsKeyValueStore.Key(scope, None, None, 'field'))
......
from xmodule.model import Namespace, Boolean, Scope, String, List from xblock.core import Namespace, Boolean, Scope, String, List
from xmodule.fields import Date, Timedelta from xmodule.fields import Date, Timedelta
......
...@@ -7,11 +7,11 @@ setup( ...@@ -7,11 +7,11 @@ setup(
requires=[ requires=[
'xmodule', 'xmodule',
], ],
py_modules=['lms.xmodule_namespace'], py_modules=['lms.xmodule_namespace', 'cms.xmodule_namespace'],
# See http://guide.python-distribute.org/creation.html#entry-points # See http://guide.python-distribute.org/creation.html#entry-points
# for a description of entry_points # for a description of entry_points
entry_points={ entry_points={
'xmodule.namespace': [ 'xblock.namespace': [
'lms = lms.xmodule_namespace:LmsNamespace', 'lms = lms.xmodule_namespace:LmsNamespace',
'cms = cms.xmodule_namespace:CmsNamespace', 'cms = cms.xmodule_namespace:CmsNamespace',
], ],
......
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