Get rid of non-editable scope.
This commit is contained in:
@@ -17,7 +17,7 @@ from xmodule.x_module import XModule
|
||||
from xmodule.raw_module import RawDescriptor
|
||||
from xmodule.exceptions import NotFoundError, ProcessingError
|
||||
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
|
||||
|
||||
log = logging.getLogger("mitx.courseware")
|
||||
@@ -63,13 +63,13 @@ class ComplexEncoder(json.JSONEncoder):
|
||||
class CapaFields(object):
|
||||
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)
|
||||
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",
|
||||
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",
|
||||
values=["answered", "always", "attempted", "closed", "never"])
|
||||
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)
|
||||
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={})
|
||||
@@ -78,7 +78,7 @@ class CapaFields(object):
|
||||
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)
|
||||
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)
|
||||
|
||||
|
||||
@@ -894,3 +894,10 @@ class CapaDescriptor(CapaFields, RawDescriptor):
|
||||
'problems/' + 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
|
||||
@@ -1,6 +1,5 @@
|
||||
from pkg_resources import resource_string
|
||||
|
||||
from .fields import NON_EDITABLE_SETTINGS_SCOPE
|
||||
from xmodule.x_module import XModule
|
||||
from xmodule.raw_module import RawDescriptor
|
||||
from xmodule.editing_module import MetadataOnlyEditingDescriptor
|
||||
@@ -8,11 +7,10 @@ from xblock.core import String, Scope
|
||||
|
||||
|
||||
class DiscussionFields(object):
|
||||
discussion_id = String(scope=NON_EDITABLE_SETTINGS_SCOPE)
|
||||
discussion_id = String(scope=Scope.settings)
|
||||
discussion_category = 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=NON_EDITABLE_SETTINGS_SCOPE)
|
||||
sort_key = String(scope=Scope.settings)
|
||||
|
||||
|
||||
class DiscussionModule(DiscussionFields, XModule):
|
||||
@@ -39,3 +37,10 @@ class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawD
|
||||
metadata_translations = dict(RawDescriptor.metadata_translations)
|
||||
metadata_translations['id'] = 'discussion_id'
|
||||
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
|
||||
@@ -7,18 +7,11 @@ from xblock.core import ModelType
|
||||
import datetime
|
||||
import dateutil.parser
|
||||
|
||||
from xblock.core import Integer, Float, Boolean, Scope
|
||||
from xblock.core import Integer, Float, Boolean
|
||||
|
||||
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):
|
||||
'''
|
||||
Date fields know how to parse and produce json (iso) compatible formats.
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
from .x_module import XModuleDescriptor, DescriptorSystem
|
||||
from .fields import NonEditableSettingsScope
|
||||
from xblock.core import Scope
|
||||
from xblock.core import XBlock
|
||||
|
||||
|
||||
class MakoDescriptorSystem(DescriptorSystem):
|
||||
@@ -43,33 +40,3 @@ class MakoModuleDescriptor(XModuleDescriptor):
|
||||
return self.system.render_template(
|
||||
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
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
from xmodule.x_module import XModuleFields
|
||||
from xblock.core import Scope, String, Object
|
||||
from xmodule.fields import Date, StringyInteger, NON_EDITABLE_SETTINGS_SCOPE
|
||||
from xmodule.mako_module import MakoModuleDescriptor
|
||||
from xmodule.fields import Date, StringyInteger
|
||||
from xmodule.xml_module import XmlDescriptor
|
||||
import unittest
|
||||
from . import test_system
|
||||
from mock import Mock
|
||||
|
||||
|
||||
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)
|
||||
# Will not be returned by editable_metadata_fields because declared as non-editable Scope.settings.
|
||||
due = Date(scope=NON_EDITABLE_SETTINGS_SCOPE)
|
||||
# Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields.
|
||||
due = Date(scope=Scope.settings)
|
||||
# Will not be returned by editable_metadata_fields because is not Scope.settings.
|
||||
student_answers = Object(scope=Scope.user_state)
|
||||
# Will be returned, and can override the inherited value from XModule.
|
||||
@@ -21,14 +21,15 @@ class TestFields(object):
|
||||
class EditableMetadataFieldsTest(unittest.TestCase):
|
||||
|
||||
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.
|
||||
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)
|
||||
|
||||
def test_override_default(self):
|
||||
# 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']
|
||||
self.assertFalse(display_name['is_default'])
|
||||
self.assertEqual('foo', display_name['value'])
|
||||
@@ -47,14 +48,19 @@ class EditableMetadataFieldsTest(unittest.TestCase):
|
||||
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name, False, True, 'inherited')
|
||||
|
||||
# Start of helper methods
|
||||
def get_mako_editable_fields(self, model_data):
|
||||
def get_xml_editable_fields(self, model_data):
|
||||
system = test_system()
|
||||
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):
|
||||
class TestModuleDescriptor(TestFields, MakoModuleDescriptor):
|
||||
pass
|
||||
class TestModuleDescriptor(TestFields, XmlDescriptor):
|
||||
|
||||
@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.render_template = Mock(return_value="<div>Test Template HTML</div>")
|
||||
@@ -606,6 +606,48 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
|
||||
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):
|
||||
def __init__(self, load_item, resources_fs, error_tracker, **kwargs):
|
||||
|
||||
@@ -6,11 +6,10 @@ import sys
|
||||
from collections import namedtuple
|
||||
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.modulestore import Location
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
from .fields import NON_EDITABLE_SETTINGS_SCOPE
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -86,7 +85,7 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
"""
|
||||
|
||||
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
|
||||
filename_extension = 'xml'
|
||||
@@ -420,3 +419,9 @@ class XmlDescriptor(XModuleDescriptor):
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"%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
|
||||
|
||||
Reference in New Issue
Block a user