Files
edx-platform/common/lib/xmodule/xmodule/model.py
2013-01-02 15:06:43 -05:00

213 lines
6.6 KiB
Python

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'] = 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)