Commit 37d594ce by cahrens

Get rid of non-editable scope.

parent 0a1902e7
...@@ -17,7 +17,7 @@ from xmodule.x_module import XModule ...@@ -17,7 +17,7 @@ from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError from xmodule.exceptions import NotFoundError, ProcessingError
from xblock.core import Scope, String, Boolean, Object from xblock.core import Scope, String, Boolean, Object
from .fields import Timedelta, Date, StringyInteger, StringyFloat, NON_EDITABLE_SETTINGS_SCOPE from .fields import Timedelta, Date, StringyInteger, StringyFloat
from xmodule.util.date_utils import time_to_datetime from xmodule.util.date_utils import time_to_datetime
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -63,13 +63,13 @@ class ComplexEncoder(json.JSONEncoder): ...@@ -63,13 +63,13 @@ class ComplexEncoder(json.JSONEncoder):
class CapaFields(object): class CapaFields(object):
attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.user_state) attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0, scope=Scope.user_state)
max_attempts = StringyInteger(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 = Date(help="Date that this problem is due by", scope=NON_EDITABLE_SETTINGS_SCOPE) due = Date(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", graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted",
scope=NON_EDITABLE_SETTINGS_SCOPE) 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",
values=["answered", "always", "attempted", "closed", "never"]) values=["answered", "always", "attempted", "closed", "never"])
force_save_button = Boolean(help="Whether to force the save button to appear on the page", force_save_button = Boolean(help="Whether to force the save button to appear on the page",
scope=NON_EDITABLE_SETTINGS_SCOPE, default=False) scope=Scope.settings, default=False)
rerandomize = Randomization(help="When to rerandomize the problem", default="always", scope=Scope.settings) rerandomize = Randomization(help="When to rerandomize the problem", default="always", scope=Scope.settings)
data = String(help="XML data for the problem", scope=Scope.content) data = String(help="XML data for the problem", scope=Scope.content)
correct_map = Object(help="Dictionary with the correctness of current student answers", scope=Scope.user_state, default={}) correct_map = Object(help="Dictionary with the correctness of current student answers", scope=Scope.user_state, default={})
...@@ -78,7 +78,7 @@ class CapaFields(object): ...@@ -78,7 +78,7 @@ class CapaFields(object):
done = Boolean(help="Whether the student has answered the problem", scope=Scope.user_state) done = Boolean(help="Whether the student has answered the problem", scope=Scope.user_state)
seed = StringyInteger(help="Random seed for this student", scope=Scope.user_state) seed = StringyInteger(help="Random seed for this student", scope=Scope.user_state)
weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings) weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings)
markdown = String(help="Markdown source of this module", scope=NON_EDITABLE_SETTINGS_SCOPE) markdown = String(help="Markdown source of this module", scope=Scope.settings)
source_code = String(help="Source code for LaTeX and Word problems. This feature is not well-supported.", scope=Scope.settings) source_code = String(help="Source code for LaTeX and Word problems. This feature is not well-supported.", scope=Scope.settings)
...@@ -894,3 +894,10 @@ class CapaDescriptor(CapaFields, RawDescriptor): ...@@ -894,3 +894,10 @@ class CapaDescriptor(CapaFields, RawDescriptor):
'problems/' + path[8:], 'problems/' + path[8:],
path[8:], path[8:],
] ]
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(CapaDescriptor, self).non_editable_metadata_fields
non_editable_fields.extend([CapaDescriptor.due, CapaDescriptor.graceperiod,
CapaDescriptor.force_save_button, CapaDescriptor.markdown])
return non_editable_fields
\ No newline at end of file
from pkg_resources import resource_string from pkg_resources import resource_string
from .fields import NON_EDITABLE_SETTINGS_SCOPE
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.editing_module import MetadataOnlyEditingDescriptor from xmodule.editing_module import MetadataOnlyEditingDescriptor
...@@ -8,11 +7,10 @@ from xblock.core import String, Scope ...@@ -8,11 +7,10 @@ from xblock.core import String, Scope
class DiscussionFields(object): class DiscussionFields(object):
discussion_id = String(scope=NON_EDITABLE_SETTINGS_SCOPE) discussion_id = String(scope=Scope.settings)
discussion_category = String(scope=Scope.settings) discussion_category = String(scope=Scope.settings)
discussion_target = String(scope=Scope.settings) discussion_target = String(scope=Scope.settings)
# We may choose to enable this in the future, but while Kevin is investigating.... sort_key = String(scope=Scope.settings)
sort_key = String(scope=NON_EDITABLE_SETTINGS_SCOPE)
class DiscussionModule(DiscussionFields, XModule): class DiscussionModule(DiscussionFields, XModule):
...@@ -39,3 +37,10 @@ class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawD ...@@ -39,3 +37,10 @@ class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawD
metadata_translations = dict(RawDescriptor.metadata_translations) metadata_translations = dict(RawDescriptor.metadata_translations)
metadata_translations['id'] = 'discussion_id' metadata_translations['id'] = 'discussion_id'
metadata_translations['for'] = 'discussion_target' metadata_translations['for'] = 'discussion_target'
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(DiscussionDescriptor, self).non_editable_metadata_fields
# We may choose to enable sort_keys in the future, but while Kevin is investigating....
non_editable_fields.extend([DiscussionDescriptor.discussion_id, DiscussionDescriptor.sort_key])
return non_editable_fields
\ No newline at end of file
...@@ -7,18 +7,11 @@ from xblock.core import ModelType ...@@ -7,18 +7,11 @@ from xblock.core import ModelType
import datetime import datetime
import dateutil.parser import dateutil.parser
from xblock.core import Integer, Float, Boolean, Scope from xblock.core import Integer, Float, Boolean
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class NonEditableSettingsScope(Scope):
pass
# Same scope as Settings.scope, but not intended to be edited by users (in Studio).
NON_EDITABLE_SETTINGS_SCOPE = NonEditableSettingsScope(user=Scope.settings.user, block=Scope.settings.block)
class Date(ModelType): class Date(ModelType):
''' '''
Date fields know how to parse and produce json (iso) compatible formats. Date fields know how to parse and produce json (iso) compatible formats.
......
from .x_module import XModuleDescriptor, DescriptorSystem from .x_module import XModuleDescriptor, DescriptorSystem
from .fields import NonEditableSettingsScope
from xblock.core import Scope
from xblock.core import XBlock
class MakoDescriptorSystem(DescriptorSystem): class MakoDescriptorSystem(DescriptorSystem):
...@@ -43,33 +40,3 @@ class MakoModuleDescriptor(XModuleDescriptor): ...@@ -43,33 +40,3 @@ class MakoModuleDescriptor(XModuleDescriptor):
return self.system.render_template( return self.system.render_template(
self.mako_template, self.get_context()) self.mako_template, self.get_context())
@property
def editable_metadata_fields(self):
inherited_metadata = getattr(self, '_inherited_metadata', {})
metadata = {}
for field in self.fields:
if field.scope != Scope.settings or isinstance(field.scope, NonEditableSettingsScope):
continue
# We are not allowing editing of xblock tag and name fields at this time (for any component).
if field == XBlock.tags or field == XBlock.name:
continue
inherited = False
default = False
value = getattr(self, field.name)
if field.name in self._model_data:
default = False
if field.name in inherited_metadata and self._model_data.get(field.name) == inherited_metadata.get(
field.name):
inherited = True
else:
default = True
metadata[field.name] = {'field' : field,
'value': value,
'is_inherited': inherited,
'is_default': default }
return metadata
from xmodule.x_module import XModuleFields from xmodule.x_module import XModuleFields
from xblock.core import Scope, String, Object from xblock.core import Scope, String, Object
from xmodule.fields import Date, StringyInteger, NON_EDITABLE_SETTINGS_SCOPE from xmodule.fields import Date, StringyInteger
from xmodule.mako_module import MakoModuleDescriptor from xmodule.xml_module import XmlDescriptor
import unittest import unittest
from . import test_system from . import test_system
from mock import Mock from mock import Mock
class TestFields(object): class TestFields(object):
# Will be returned by editable_metadata_fields because Scope.settings. # Will be returned by editable_metadata_fields.
max_attempts = StringyInteger(scope=Scope.settings) max_attempts = StringyInteger(scope=Scope.settings)
# Will not be returned by editable_metadata_fields because declared as non-editable Scope.settings. # Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields.
due = Date(scope=NON_EDITABLE_SETTINGS_SCOPE) due = Date(scope=Scope.settings)
# Will not be returned by editable_metadata_fields because is not Scope.settings. # Will not be returned by editable_metadata_fields because is not Scope.settings.
student_answers = Object(scope=Scope.user_state) student_answers = Object(scope=Scope.user_state)
# Will be returned, and can override the inherited value from XModule. # Will be returned, and can override the inherited value from XModule.
...@@ -21,14 +21,15 @@ class TestFields(object): ...@@ -21,14 +21,15 @@ class TestFields(object):
class EditableMetadataFieldsTest(unittest.TestCase): class EditableMetadataFieldsTest(unittest.TestCase):
def test_display_name_field(self): def test_display_name_field(self):
editable_fields = self.get_mako_editable_fields({}) editable_fields = self.get_xml_editable_fields({})
# Tests that the xblock fields (currently tags and name) get filtered out. # Tests that the xblock fields (currently tags and name) get filtered out.
self.assertEqual(1, len(editable_fields), "Expected only 1 editable field for mako descriptor.") # Also tests that xml_attributes is filtered out of XmlDescriptor.
self.assertEqual(1, len(editable_fields), "Expected only 1 editable field for xml descriptor.")
self.assert_display_name_default(editable_fields) self.assert_display_name_default(editable_fields)
def test_override_default(self): def test_override_default(self):
# Tests that is_default is correct when a value overrides the default. # Tests that is_default is correct when a value overrides the default.
editable_fields = self.get_mako_editable_fields({'display_name': 'foo'}) editable_fields = self.get_xml_editable_fields({'display_name': 'foo'})
display_name = editable_fields['display_name'] display_name = editable_fields['display_name']
self.assertFalse(display_name['is_default']) self.assertFalse(display_name['is_default'])
self.assertEqual('foo', display_name['value']) self.assertEqual('foo', display_name['value'])
...@@ -47,14 +48,19 @@ class EditableMetadataFieldsTest(unittest.TestCase): ...@@ -47,14 +48,19 @@ class EditableMetadataFieldsTest(unittest.TestCase):
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name, False, True, 'inherited') self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name, False, True, 'inherited')
# Start of helper methods # Start of helper methods
def get_mako_editable_fields(self, model_data): def get_xml_editable_fields(self, model_data):
system = test_system() system = test_system()
system.render_template = Mock(return_value="<div>Test Template HTML</div>") system.render_template = Mock(return_value="<div>Test Template HTML</div>")
return MakoModuleDescriptor(system=system, location=None, model_data=model_data).editable_metadata_fields return XmlDescriptor(system=system, location=None, model_data=model_data).editable_metadata_fields
def get_module_editable_fields(self, model_data): def get_module_editable_fields(self, model_data):
class TestModuleDescriptor(TestFields, MakoModuleDescriptor): class TestModuleDescriptor(TestFields, XmlDescriptor):
pass
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(TestModuleDescriptor, self).non_editable_metadata_fields
non_editable_fields.append(TestModuleDescriptor.due)
return non_editable_fields
system = test_system() system = test_system()
system.render_template = Mock(return_value="<div>Test Template HTML</div>") system.render_template = Mock(return_value="<div>Test Template HTML</div>")
......
...@@ -606,6 +606,48 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock): ...@@ -606,6 +606,48 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
model_data=self._model_data, model_data=self._model_data,
)) ))
@property
def non_editable_metadata_fields(self):
"""
Return the list of fields that should not be editable in Studio.
When overriding, be sure to append to the superclasses' list.
"""
# We are not allowing editing of xblock tag and name fields at this time (for any component).
return [XBlock.tags, XBlock.name]
@property
def editable_metadata_fields(self):
"""
Returns the metadata fields to be edited in Studio. These are fields with scope `Scope.settings`.
Can be limited by extending `non_editable_metadata_fields`.
"""
inherited_metadata = getattr(self, '_inherited_metadata', {})
metadata = {}
for field in self.fields:
if field.scope != Scope.settings or field in self.non_editable_metadata_fields:
continue
inherited = False
default = False
value = getattr(self, field.name)
if field.name in self._model_data:
default = False
if field.name in inherited_metadata:
if self._model_data.get(field.name) == inherited_metadata.get(field.name):
inherited = True
else:
default = True
metadata[field.name] = {'field': field,
'value': value,
'is_inherited': inherited,
'is_default': default}
return metadata
class DescriptorSystem(object): class DescriptorSystem(object):
def __init__(self, load_item, resources_fs, error_tracker, **kwargs): def __init__(self, load_item, resources_fs, error_tracker, **kwargs):
......
...@@ -6,11 +6,10 @@ import sys ...@@ -6,11 +6,10 @@ import sys
from collections import namedtuple from collections import namedtuple
from lxml import etree from lxml import etree
from xblock.core import Object from xblock.core import Object, Scope
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 .fields import NON_EDITABLE_SETTINGS_SCOPE
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -86,7 +85,7 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -86,7 +85,7 @@ class XmlDescriptor(XModuleDescriptor):
""" """
xml_attributes = Object(help="Map of unhandled xml attributes, used only for storage between import and export", xml_attributes = Object(help="Map of unhandled xml attributes, used only for storage between import and export",
default={}, scope=NON_EDITABLE_SETTINGS_SCOPE) default={}, scope=Scope.settings)
# Extension to append to filename paths # Extension to append to filename paths
filename_extension = 'xml' filename_extension = 'xml'
...@@ -420,3 +419,9 @@ class XmlDescriptor(XModuleDescriptor): ...@@ -420,3 +419,9 @@ class XmlDescriptor(XModuleDescriptor):
""" """
raise NotImplementedError( raise NotImplementedError(
"%s does not implement definition_to_xml" % self.__class__.__name__) "%s does not implement definition_to_xml" % self.__class__.__name__)
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(XmlDescriptor, self).non_editable_metadata_fields
non_editable_fields.append(XmlDescriptor.xml_attributes)
return non_editable_fields
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