From 789ac3fc875aa26380fc7f0865dc5c89a7359473 Mon Sep 17 00:00:00 2001 From: Calen Pennington <calen.pennington@gmail.com> Date: Fri, 4 Jan 2013 16:19:58 -0500 Subject: [PATCH] Use the XBlock library as the base for XModule, so that we can incrementally rely on more and more of the XBlock api --- cms/djangoapps/contentstore/views.py | 6 +- cms/xmodule_namespace.py | 2 +- common/lib/xmodule/xmodule/abtest_module.py | 2 +- common/lib/xmodule/xmodule/capa_module.py | 10 +- common/lib/xmodule/xmodule/course_module.py | 2 +- .../lib/xmodule/xmodule/discussion_module.py | 2 +- common/lib/xmodule/xmodule/editing_module.py | 2 +- common/lib/xmodule/xmodule/error_module.py | 2 +- common/lib/xmodule/xmodule/fields.py | 2 +- common/lib/xmodule/xmodule/html_module.py | 2 +- common/lib/xmodule/xmodule/mako_module.py | 2 +- common/lib/xmodule/xmodule/model.py | 217 ------------------ .../xmodule/modulestore/inheritance.py | 2 +- .../lib/xmodule/xmodule/modulestore/mongo.py | 12 +- common/lib/xmodule/xmodule/poll_module.py | 6 +- common/lib/xmodule/xmodule/raw_module.py | 2 +- common/lib/xmodule/xmodule/runtime.py | 119 ---------- .../xmodule/xmodule/self_assessment_module.py | 12 +- common/lib/xmodule/xmodule/seq_module.py | 4 +- common/lib/xmodule/xmodule/tests/__init__.py | 2 +- .../lib/xmodule/xmodule/tests/test_model.py | 163 ------------- .../lib/xmodule/xmodule/tests/test_runtime.py | 105 --------- common/lib/xmodule/xmodule/video_module.py | 4 +- common/lib/xmodule/xmodule/x_module.py | 20 +- common/lib/xmodule/xmodule/xml_module.py | 2 +- github-requirements.txt | 1 + lms/djangoapps/courseware/model_data.py | 19 +- lms/djangoapps/courseware/module_render.py | 6 +- .../courseware/tests/test_model_data.py | 8 +- lms/xmodule_namespace.py | 2 +- setup.py | 4 +- 31 files changed, 69 insertions(+), 675 deletions(-) delete mode 100644 common/lib/xmodule/xmodule/model.py delete mode 100644 common/lib/xmodule/xmodule/runtime.py delete mode 100644 common/lib/xmodule/xmodule/tests/test_model.py delete mode 100644 common/lib/xmodule/xmodule/tests/test_runtime.py diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py index 2b650a525b6..2fd395cff91 100644 --- a/cms/djangoapps/contentstore/views.py +++ b/cms/djangoapps/contentstore/views.py @@ -29,8 +29,8 @@ from django.conf import settings from xmodule.modulestore import Location from xmodule.modulestore.exceptions import ItemNotFoundError, InvalidLocationError from xmodule.modulestore.inheritance import own_metadata -from xmodule.model import Scope -from xmodule.runtime import KeyValueStore, DbModel, InvalidScopeError +from xblock.core import Scope +from xblock.runtime import KeyValueStore, DbModel, InvalidScopeError from xmodule.x_module import ModuleSystem from xmodule.error_module import ErrorDescriptor from xmodule.errortracker import exc_info_to_str @@ -472,7 +472,7 @@ def preview_module_system(request, preview_id, descriptor): debug=True, replace_urls=replace_urls, user=request.user, - xmodule_model_data=preview_model_data, + xblock_model_data=preview_model_data, ) diff --git a/cms/xmodule_namespace.py b/cms/xmodule_namespace.py index 3ade59532af..9e30105abc8 100644 --- a/cms/xmodule_namespace.py +++ b/cms/xmodule_namespace.py @@ -1,6 +1,6 @@ import datetime -from xmodule.model import Namespace, Boolean, Scope, ModelType, String +from xblock.core import Namespace, Boolean, Scope, ModelType, String class DateTuple(ModelType): diff --git a/common/lib/xmodule/xmodule/abtest_module.py b/common/lib/xmodule/xmodule/abtest_module.py index f33a3db91c7..f5115586939 100644 --- a/common/lib/xmodule/xmodule/abtest_module.py +++ b/common/lib/xmodule/xmodule/abtest_module.py @@ -6,7 +6,7 @@ from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor from xmodule.xml_module import XmlDescriptor from xmodule.exceptions import InvalidDefinitionError -from .model import String, Scope, Object, ModuleScope +from xblock.core import String, Scope, Object, BlockScope DEFAULT = "_DEFAULT_GROUP" diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index a41e634c368..0d04b419a52 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -17,13 +17,13 @@ from progress import Progress from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor 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 log = logging.getLogger("mitx.courseware") -class StringyInt(Int): +class StringyInteger(Integer): """ A model type that converts from strings to integers when reading from json """ @@ -57,8 +57,8 @@ class CapaModule(XModule): ''' icon_class = 'problem' - attempts = Int(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) + attempts = Integer(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.student_state) + 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) 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") @@ -69,7 +69,7 @@ class CapaModule(XModule): 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) 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'), resource_string(__name__, 'js/src/collapsible.coffee'), diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 38cf81f3af9..3b73b6a4d18 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -12,7 +12,7 @@ import requests import time 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 log = logging.getLogger(__name__) diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py index b373f337fbf..6a9edbbd704 100644 --- a/common/lib/xmodule/xmodule/discussion_module.py +++ b/common/lib/xmodule/xmodule/discussion_module.py @@ -3,7 +3,7 @@ from pkg_resources import resource_string, resource_listdir from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor -from .model import String, Scope +from xblock.core import String, Scope class DiscussionModule(XModule): js = {'coffee': diff --git a/common/lib/xmodule/xmodule/editing_module.py b/common/lib/xmodule/xmodule/editing_module.py index 531fd7d8b9f..9ff0124dc62 100644 --- a/common/lib/xmodule/xmodule/editing_module.py +++ b/common/lib/xmodule/xmodule/editing_module.py @@ -1,6 +1,6 @@ from pkg_resources import resource_string from xmodule.mako_module import MakoModuleDescriptor -from xmodule.model import Scope, String +from xblock.core import Scope, String import logging log = logging.getLogger(__name__) diff --git a/common/lib/xmodule/xmodule/error_module.py b/common/lib/xmodule/xmodule/error_module.py index 6a06b3ad3af..d2f67d68a6e 100644 --- a/common/lib/xmodule/xmodule/error_module.py +++ b/common/lib/xmodule/xmodule/error_module.py @@ -8,7 +8,7 @@ from xmodule.x_module import XModule from xmodule.editing_module import JSONEditingDescriptor from xmodule.errortracker import exc_info_to_str from xmodule.modulestore import Location -from .model import String, Scope +from xblock.core import String, Scope log = logging.getLogger(__name__) diff --git a/common/lib/xmodule/xmodule/fields.py b/common/lib/xmodule/xmodule/fields.py index 21c360f9146..fb80752e56f 100644 --- a/common/lib/xmodule/xmodule/fields.py +++ b/common/lib/xmodule/xmodule/fields.py @@ -3,7 +3,7 @@ import logging import re from datetime import timedelta -from .model import ModelType +from xblock.core import ModelType log = logging.getLogger(__name__) diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index 55155810e98..f9221810467 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -15,7 +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 +from xblock.core import Scope, String log = logging.getLogger("mitx.courseware") diff --git a/common/lib/xmodule/xmodule/mako_module.py b/common/lib/xmodule/xmodule/mako_module.py index 66a4b647d38..adfe28387d4 100644 --- a/common/lib/xmodule/xmodule/mako_module.py +++ b/common/lib/xmodule/xmodule/mako_module.py @@ -1,5 +1,5 @@ from .x_module import XModuleDescriptor, DescriptorSystem -from .model import Scope +from xblock.core import Scope import logging diff --git a/common/lib/xmodule/xmodule/model.py b/common/lib/xmodule/xmodule/model.py deleted file mode 100644 index b8228cb417b..00000000000 --- a/common/lib/xmodule/xmodule/model.py +++ /dev/null @@ -1,217 +0,0 @@ -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) diff --git a/common/lib/xmodule/xmodule/modulestore/inheritance.py b/common/lib/xmodule/xmodule/modulestore/inheritance.py index 18cf0b33515..dd2ca7e346b 100644 --- a/common/lib/xmodule/xmodule/modulestore/inheritance.py +++ b/common/lib/xmodule/xmodule/modulestore/inheritance.py @@ -1,4 +1,4 @@ -from xmodule.model import Scope +from xblock.core import Scope # A list of metadata that this module can inherit from its parent module INHERITABLE_METADATA = ( diff --git a/common/lib/xmodule/xmodule/modulestore/mongo.py b/common/lib/xmodule/xmodule/modulestore/mongo.py index 52267d67341..201a2146218 100644 --- a/common/lib/xmodule/xmodule/modulestore/mongo.py +++ b/common/lib/xmodule/xmodule/modulestore/mongo.py @@ -13,8 +13,8 @@ from xmodule.errortracker import null_error_tracker, exc_info_to_str from xmodule.mako_module import MakoDescriptorSystem from xmodule.x_module import XModuleDescriptor from xmodule.error_module import ErrorDescriptor -from xmodule.runtime import DbModel, KeyValueStore, InvalidScopeError -from xmodule.model import Scope +from xblock.runtime import DbModel, KeyValueStore, InvalidScopeError +from xblock.core import Scope from . import ModuleStoreBase, Location from .draft import DraftModuleStore @@ -41,8 +41,10 @@ class MongoKeyValueStore(KeyValueStore): self._metadata = metadata def get(self, key): - if key.field_name == 'children': + if key.scope == Scope.children: return self._children + elif key.scope == Scope.parent: + return None elif key.scope == Scope.settings: return self._metadata[key.field_name] elif key.scope == Scope.content: @@ -54,7 +56,7 @@ class MongoKeyValueStore(KeyValueStore): raise InvalidScopeError(key.scope) def set(self, key, value): - if key.field_name == 'children': + if key.scope == Scope.children: self._children = value elif key.scope == Scope.settings: self._metadata[key.field_name] = value @@ -67,7 +69,7 @@ class MongoKeyValueStore(KeyValueStore): raise InvalidScopeError(key.scope) def delete(self, key): - if key.field_name == 'children': + if key.scope == Scope.children: self._children = [] elif key.scope == Scope.settings: if key.field_name in self._metadata: diff --git a/common/lib/xmodule/xmodule/poll_module.py b/common/lib/xmodule/xmodule/poll_module.py index db0b3fd0c42..eb5bef9e6d2 100644 --- a/common/lib/xmodule/xmodule/poll_module.py +++ b/common/lib/xmodule/xmodule/poll_module.py @@ -6,7 +6,7 @@ from pkg_resources import resource_string, resource_listdir from xmodule.x_module import XModule from xmodule.raw_module import RawDescriptor -from .model import Int, Scope, Boolean +from xblock.core import Integer, Scope, Boolean log = logging.getLogger(__name__) @@ -16,8 +16,8 @@ class PollModule(XModule): js = {'coffee': [resource_string(__name__, 'js/src/poll/display.coffee')]} js_module_name = "PollModule" - upvotes = Int(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) + upvotes = Integer(help="Number of upvotes 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) def handle_ajax(self, dispatch, get): diff --git a/common/lib/xmodule/xmodule/raw_module.py b/common/lib/xmodule/xmodule/raw_module.py index 5e50bdf6a02..c6d2ebf2b32 100644 --- a/common/lib/xmodule/xmodule/raw_module.py +++ b/common/lib/xmodule/xmodule/raw_module.py @@ -3,7 +3,7 @@ from xmodule.editing_module import XMLEditingDescriptor from xmodule.xml_module import XmlDescriptor import logging import sys -from .model import String, Scope +from xblock.core import String, Scope log = logging.getLogger(__name__) diff --git a/common/lib/xmodule/xmodule/runtime.py b/common/lib/xmodule/xmodule/runtime.py deleted file mode 100644 index 8cbfc3ae364..00000000000 --- a/common/lib/xmodule/xmodule/runtime.py +++ /dev/null @@ -1,119 +0,0 @@ -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 diff --git a/common/lib/xmodule/xmodule/self_assessment_module.py b/common/lib/xmodule/xmodule/self_assessment_module.py index ffcd86ba525..12f054eaa6d 100644 --- a/common/lib/xmodule/xmodule/self_assessment_module.py +++ b/common/lib/xmodule/xmodule/self_assessment_module.py @@ -23,7 +23,7 @@ from .editing_module import EditingDescriptor from .stringify import stringify_children from .x_module import XModule 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") @@ -67,10 +67,10 @@ class SelfAssessmentModule(XModule): # Used for progress / grading. Currently get credit just for # 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 - max_attempts = Int(scope=Scope.settings, default=MAX_ATTEMPTS) + attempts = Integer(scope=Scope.student_state, default=0) + max_attempts = Integer(scope=Scope.settings, default=MAX_ATTEMPTS) rubric = String(scope=Scope.content) prompt = String(scope=Scope.content) submitmessage = String(scope=Scope.content) @@ -392,9 +392,9 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor): # Used for progress / grading. Currently get credit just for # 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) prompt = String(scope=Scope.content) submitmessage = String(scope=Scope.content) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 3fc3a5dbaab..b3ceb7a2295 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -8,7 +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 xblock.core import Integer, Scope from pkg_resources import resource_string log = logging.getLogger("mitx.common.lib.seq_module") @@ -37,7 +37,7 @@ class SequenceModule(XModule): # 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", scope=Scope.student_state) + position = Integer(help="Last tab viewed in this sequence", scope=Scope.student_state) def __init__(self, *args, **kwargs): XModule.__init__(self, *args, **kwargs) diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index c16c6d75963..e8b71b53fc2 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -31,7 +31,7 @@ i4xs = ModuleSystem( xqueue={'interface':None, 'callback_url':'/', 'default_queuename': 'testqueue', 'waittime': 10}, node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"), anonymous_student_id = 'student', - xmodule_model_data = lambda x: x, + xblock_model_data = lambda x: x, ) diff --git a/common/lib/xmodule/xmodule/tests/test_model.py b/common/lib/xmodule/xmodule/tests/test_model.py deleted file mode 100644 index c5eaaaefc76..00000000000 --- a/common/lib/xmodule/xmodule/tests/test_model.py +++ /dev/null @@ -1,163 +0,0 @@ -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']) diff --git a/common/lib/xmodule/xmodule/tests/test_runtime.py b/common/lib/xmodule/xmodule/tests/test_runtime.py deleted file mode 100644 index e71c78e4af3..00000000000 --- a/common/lib/xmodule/xmodule/tests/test_runtime.py +++ /dev/null @@ -1,105 +0,0 @@ -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')]) diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py index 34ce353afdb..e284c574d33 100644 --- a/common/lib/xmodule/xmodule/video_module.py +++ b/common/lib/xmodule/xmodule/video_module.py @@ -9,7 +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 +from xblock.core import Integer, Scope, String log = logging.getLogger(__name__) @@ -29,7 +29,7 @@ class VideoModule(XModule): js_module_name = "Video" 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) def __init__(self, *args, **kwargs): diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 86f9f5f189d..5ce74ffc46b 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -10,12 +10,7 @@ from pkg_resources import resource_listdir, resource_string, resource_isdir from xmodule.modulestore import Location from xmodule.modulestore.exceptions import ItemNotFoundError -from .model import ModelMetaclass, ParentModelMetaclass, NamespacesMetaclass -from .plugin import Plugin - - -class XModuleMetaclass(ParentModelMetaclass, NamespacesMetaclass, ModelMetaclass): - pass +from xblock.core import XBlock log = logging.getLogger(__name__) @@ -88,7 +83,7 @@ class HTMLSnippet(object): .format(self.__class__)) -class XModule(HTMLSnippet): +class XModule(HTMLSnippet, XBlock): ''' Implements a generic learning module. Subclasses must at a minimum provide a definition for get_html in order @@ -97,8 +92,6 @@ class XModule(HTMLSnippet): See the HTML module for a simple example. ''' - __metaclass__ = XModuleMetaclass - # The default implementation of get_icon_class returns the icon_class # attribute of the class # @@ -266,7 +259,7 @@ class ResourceTemplates(object): return templates -class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): +class XModuleDescriptor(HTMLSnippet, ResourceTemplates, XBlock): """ 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 @@ -279,7 +272,6 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): """ entry_point = "xmodule.v1" module_class = XModule - __metaclass__ = XModuleMetaclass # Attributes for inspection of the descriptor @@ -371,7 +363,7 @@ class XModuleDescriptor(Plugin, HTMLSnippet, ResourceTemplates): system, self.location, self, - system.xmodule_model_data(self._model_data), + system.xblock_model_data(self._model_data), ) def has_dynamic_children(self): @@ -608,7 +600,7 @@ class ModuleSystem(object): get_module, render_template, replace_urls, - xmodule_model_data, + xblock_model_data, user=None, filestore=None, debug=False, @@ -663,7 +655,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 + self.xblock_model_data = xblock_model_data if publish is None: publish = lambda e: None diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py index e50adeb364f..f510a40cc79 100644 --- a/common/lib/xmodule/xmodule/xml_module.py +++ b/common/lib/xmodule/xmodule/xml_module.py @@ -1,7 +1,7 @@ from xmodule.x_module import (XModuleDescriptor, policy_key) from xmodule.modulestore import Location from xmodule.modulestore.inheritance import own_metadata -from xmodule.model import Object, Scope +from xblock.core import Object, Scope from lxml import etree import json import copy diff --git a/github-requirements.txt b/github-requirements.txt index 468d55ce65d..cf6f097aee8 100644 --- a/github-requirements.txt +++ b/github-requirements.txt @@ -3,3 +3,4 @@ -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/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 diff --git a/lms/djangoapps/courseware/model_data.py b/lms/djangoapps/courseware/model_data.py index b2f2d3ef489..6ae75a2c710 100644 --- a/lms/djangoapps/courseware/model_data.py +++ b/lms/djangoapps/courseware/model_data.py @@ -8,8 +8,8 @@ from .models import ( XModuleStudentInfoField ) -from xmodule.runtime import KeyValueStore, InvalidScopeError -from xmodule.model import Scope +from xblock.runtime import KeyValueStore, InvalidScopeError +from xblock.core import Scope class InvalidWriteError(Exception): @@ -45,20 +45,20 @@ class LmsKeyValueStore(KeyValueStore): def _student_module(self, key): 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 def _field_object(self, key): 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: return XModuleSettingsField, { '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: - 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: return XModuleStudentInfoField, {'field_name': key.field_name, 'student': self._user} @@ -68,6 +68,9 @@ class LmsKeyValueStore(KeyValueStore): if key.field_name in self._descriptor_model_data: return self._descriptor_model_data[key.field_name] + if key.scope == Scope.parent: + return None + if key.scope == Scope.student_state: student_module = self._student_module(key) @@ -92,8 +95,8 @@ class LmsKeyValueStore(KeyValueStore): student_module = StudentModule( course_id=self._course_id, student=self._user, - module_type=key.module_scope_id.category, - module_state_key=key.module_scope_id.url(), + module_type=key.block_scope_id.category, + module_state_key=key.block_scope_id.url(), state=json.dumps({}) ) self._student_module_cache.append(student_module) diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index c2a8db0fabe..4d599d128d7 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -26,7 +26,7 @@ from xmodule.modulestore import Location from xmodule.modulestore.django import modulestore from xmodule.x_module import ModuleSystem 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 .model_data import LmsKeyValueStore, LmsUsage @@ -203,7 +203,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi return get_module(user, request, location, student_module_cache, course_id, position) - def xmodule_model_data(descriptor_model_data): + def xblock_model_data(descriptor_model_data): return DbModel( LmsKeyValueStore(course_id, user, descriptor_model_data, student_module_cache), descriptor.module_class, @@ -260,7 +260,7 @@ def _get_module(user, request, location, student_module_cache, course_id, positi replace_urls=replace_urls, node_path=settings.NODE_PATH, anonymous_student_id=anonymous_student_id, - xmodule_model_data=xmodule_model_data, + xblock_model_data=xblock_model_data, publish=publish, ) # pass position specified in URL to module through ModuleSystem diff --git a/lms/djangoapps/courseware/tests/test_model_data.py b/lms/djangoapps/courseware/tests/test_model_data.py index ab9067ce710..f18e128984b 100644 --- a/lms/djangoapps/courseware/tests/test_model_data.py +++ b/lms/djangoapps/courseware/tests/test_model_data.py @@ -7,7 +7,7 @@ from functools import partial from courseware.model_data import LmsKeyValueStore, InvalidWriteError, InvalidScopeError 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 django.test import TestCase @@ -112,9 +112,9 @@ class TestInvalidScopes(TestCase): self.kvs = LmsKeyValueStore(course_id, UserFactory.build(), self.desc_md, None) def test_invalid_scopes(self): - for scope in (Scope(student=True, module=ModuleScope.DEFINITION), - Scope(student=False, module=ModuleScope.TYPE), - Scope(student=False, module=ModuleScope.ALL)): + for scope in (Scope(student=True, block=BlockScope.DEFINITION), + Scope(student=False, block=BlockScope.TYPE), + Scope(student=False, block=BlockScope.ALL)): 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.delete, LmsKeyValueStore.Key(scope, None, None, 'field')) diff --git a/lms/xmodule_namespace.py b/lms/xmodule_namespace.py index 5fd2c18bb74..8d587dfacb8 100644 --- a/lms/xmodule_namespace.py +++ b/lms/xmodule_namespace.py @@ -1,4 +1,4 @@ -from xmodule.model import Namespace, Boolean, Scope, String, List +from xblock.core import Namespace, Boolean, Scope, String, List from xmodule.fields import Date, Timedelta diff --git a/setup.py b/setup.py index a07f8364133..48572de6def 100644 --- a/setup.py +++ b/setup.py @@ -7,11 +7,11 @@ setup( requires=[ 'xmodule', ], - py_modules=['lms.xmodule_namespace'], + py_modules=['lms.xmodule_namespace', 'cms.xmodule_namespace'], # See http://guide.python-distribute.org/creation.html#entry-points # for a description of entry_points entry_points={ - 'xmodule.namespace': [ + 'xblock.namespace': [ 'lms = lms.xmodule_namespace:LmsNamespace', 'cms = cms.xmodule_namespace:CmsNamespace', ], -- GitLab