diff --git a/cms/djangoapps/contentstore/module_info_model.py b/cms/djangoapps/contentstore/module_info_model.py
index 91f722a699..f7d1bbd8fe 100644
--- a/cms/djangoapps/contentstore/module_info_model.py
+++ b/cms/djangoapps/contentstore/module_info_model.py
@@ -75,11 +75,7 @@ def set_module_info(store, location, post_data):
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
for metadata_key, value in posted_metadata.items():
- # let's strip out any metadata fields from the postback which have been identified as system metadata
- # and therefore should not be user-editable, so we should accept them back from the client
- if metadata_key in module.system_metadata_fields:
- del posted_metadata[metadata_key]
- elif posted_metadata[metadata_key] is None:
+ if posted_metadata[metadata_key] is None:
# remove both from passed in collection as well as the collection read in from the modulestore
if metadata_key in module._model_data:
del module._model_data[metadata_key]
diff --git a/cms/djangoapps/contentstore/views.py b/cms/djangoapps/contentstore/views.py
index 603010f5b4..6ce17d04aa 100644
--- a/cms/djangoapps/contentstore/views.py
+++ b/cms/djangoapps/contentstore/views.py
@@ -687,11 +687,7 @@ def save_item(request):
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
for metadata_key, value in posted_metadata.items():
- # let's strip out any metadata fields from the postback which have been identified as system metadata
- # and therefore should not be user-editable, so we should accept them back from the client
- if metadata_key in existing_item.system_metadata_fields:
- del posted_metadata[metadata_key]
- elif posted_metadata[metadata_key] is None:
+ if posted_metadata[metadata_key] is None:
# remove both from passed in collection as well as the collection read in from the modulestore
if metadata_key in existing_item._model_data:
del existing_item._model_data[metadata_key]
diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py
index 4429e35692..708e79f0a3 100644
--- a/cms/djangoapps/models/settings/course_metadata.py
+++ b/cms/djangoapps/models/settings/course_metadata.py
@@ -14,13 +14,14 @@ class CourseMetadata(object):
The objects have no predefined attrs but instead are obj encodings of the
editable metadata.
'''
- FILTERED_LIST = XModuleDescriptor.system_metadata_fields + ['start',
- 'end',
- 'enrollment_start',
- 'enrollment_end',
- 'tabs',
- 'graceperiod',
- 'checklists']
+ FILTERED_LIST = ['xml_attributes',
+ 'start',
+ 'end',
+ 'enrollment_start',
+ 'enrollment_end',
+ 'tabs',
+ 'graceperiod',
+ 'checklists']
@classmethod
def fetch(cls, course_location):
diff --git a/cms/templates/widgets/metadata-edit.html b/cms/templates/widgets/metadata-edit.html
index 51fe400f88..b351b5c344 100644
--- a/cms/templates/widgets/metadata-edit.html
+++ b/cms/templates/widgets/metadata-edit.html
@@ -7,10 +7,24 @@
% for field_name, field_value in editable_metadata_fields.items():
% if field_name == 'source_code':
- Edit High Level Source
+ % if field_value['is_default'] is False:
+ Edit High Level Source
+ % endif
% else:
-
-
+
+
+ % if False:
+
+
+
+
+ % if field_value['field'].values:
+
+ % for value in field_value['field'].values:
+
+ % endfor
+ % endif
+ % endif
% endif
% endfor
diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py
index 4143345196..bc5014d75b 100644
--- a/common/lib/xmodule/xmodule/capa_module.py
+++ b/common/lib/xmodule/xmodule/capa_module.py
@@ -13,7 +13,7 @@ from capa.responsetypes import StudentInputError,\
ResponseError, LoncapaProblemError
from capa.util import convert_files_to_filenames
from .progress import Progress
-from xmodule.x_module import XModule
+from xmodule.x_module import XModule, XModuleFields
from xmodule.raw_module import RawDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError
from xblock.core import Scope, String, Boolean, Object
@@ -62,20 +62,26 @@ 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=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")
- force_save_button = Boolean(help="Whether to force the save button to appear on the page", scope=Scope.settings, default=False)
- rerandomize = Randomization(help="When to rerandomize the problem", default="always", scope=Scope.settings)
+ max_attempts = StringyInteger(display_name="Maximum Allowed Attempts",
+ help="Maximum number of attempts that a student is allowed", scope=Scope.settings)
+ due = Date(help="Date that this problem is due by", scope=XModuleFields.nonEditableSettingsScope)
+ graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted",
+ scope=XModuleFields.nonEditableSettingsScope)
+ showanswer = String(display_name="Show Answer",
+ 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=XModuleFields.nonEditableSettingsScope, default=False)
+ rerandomize = Randomization(display_name="Rerandomize", 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={})
input_state = Object(help="Dictionary for maintaining the state of inputtypes", scope=Scope.user_state)
student_answers = Object(help="Dictionary with the current student responses", 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)
- weight = StringyFloat(help="How much to weight this problem by", scope=Scope.settings)
- markdown = String(help="Markdown source of this module", scope=Scope.settings)
+ weight = StringyFloat(display_name="Problem Weight", help="How much to weight this problem by", scope=Scope.settings)
+ markdown = String(help="Markdown source of this module", scope=XModuleFields.nonEditableSettingsScope)
source_code = String(help="Source code for LaTeX and Word problems. This feature is not well-supported.", scope=Scope.settings)
@@ -882,16 +888,6 @@ class CapaDescriptor(CapaFields, RawDescriptor):
'enable_markdown': self.markdown is not None})
return _context
- @property
- def editable_metadata_fields(self):
- """Remove metadata from the editable fields since it has its own editor"""
- subset = super(CapaDescriptor, self).editable_metadata_fields
- if 'markdown' in subset:
- del subset['markdown']
- if 'empty' in subset:
- del subset['empty']
- return subset
-
# VS[compat]
# TODO (cpennington): Delete this method once all fall 2012 course are being
# edited in the cms
diff --git a/common/lib/xmodule/xmodule/discussion_module.py b/common/lib/xmodule/xmodule/discussion_module.py
index 8968e221b2..9c2681ef2d 100644
--- a/common/lib/xmodule/xmodule/discussion_module.py
+++ b/common/lib/xmodule/xmodule/discussion_module.py
@@ -1,16 +1,17 @@
from pkg_resources import resource_string
-from xmodule.x_module import XModule
+from xmodule.x_module import XModule, XModuleFields
from xmodule.raw_module import RawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xblock.core import String, Scope
class DiscussionFields(object):
- discussion_id = String(scope=Scope.settings)
- discussion_category = String(scope=Scope.settings)
- discussion_target = String(scope=Scope.settings)
- sort_key = String(scope=Scope.settings)
+ discussion_id = String(scope=XModuleFields.nonEditableSettingsScope)
+ discussion_category = String(display_name="Category Name", scope=Scope.settings)
+ discussion_target = String(display_name="Subcategory Name", scope=Scope.settings)
+ # We may choose to enable this in the future, but while Kevin is investigating....
+ sort_key = String(scope=XModuleFields.nonEditableSettingsScope)
class DiscussionModule(DiscussionFields, XModule):
diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py
index d901fc5fbe..0c147ecf75 100644
--- a/common/lib/xmodule/xmodule/html_module.py
+++ b/common/lib/xmodule/xmodule/html_module.py
@@ -166,16 +166,6 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor):
elt.set("filename", relname)
return elt
- @property
- def editable_metadata_fields(self):
- """Remove any metadata from the editable fields which have their own editor or shouldn't be edited by user."""
- subset = super(HtmlDescriptor, self).editable_metadata_fields
-
- if 'empty' in subset:
- del subset['empty']
-
- return subset
-
class AboutDescriptor(HtmlDescriptor):
"""
diff --git a/common/lib/xmodule/xmodule/mako_module.py b/common/lib/xmodule/xmodule/mako_module.py
index 84db6ad779..e01a03e309 100644
--- a/common/lib/xmodule/xmodule/mako_module.py
+++ b/common/lib/xmodule/xmodule/mako_module.py
@@ -1,5 +1,6 @@
-from .x_module import XModuleDescriptor, DescriptorSystem
-from .modulestore.inheritance import own_metadata
+from .x_module import XModuleDescriptor, DescriptorSystem, NonEditableSettingsScope
+from xblock.core import Scope
+from xblock.core import XBlock
class MakoDescriptorSystem(DescriptorSystem):
@@ -34,20 +35,40 @@ class MakoModuleDescriptor(XModuleDescriptor):
"""
return {
'module': self,
- 'editable_metadata_fields': self.editable_metadata_fields,
+ 'editable_metadata_fields': self.editable_metadata_fields
}
def get_html(self):
return self.system.render_template(
self.mako_template, self.get_context())
- # cdodge: encapsulate a means to expose "editable" metadata fields (i.e. not internal system metadata)
@property
def editable_metadata_fields(self):
- fields = {}
- for field, value in own_metadata(self).items():
- if field in self.system_metadata_fields:
+ inherited_metadata = getattr(self, '_inherited_metadata', {})
+ metadata = {}
+ for field in self.fields:
+
+ if field.scope != Scope.settings or isinstance(field.scope, NonEditableSettingsScope):
continue
- fields[field] = value
- return fields
+ # 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
diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py
index 1fd0b8e138..4a16548c6f 100644
--- a/common/lib/xmodule/xmodule/x_module.py
+++ b/common/lib/xmodule/xmodule/x_module.py
@@ -78,12 +78,18 @@ class HTMLSnippet(object):
.format(self.__class__))
+class NonEditableSettingsScope(Scope):
+ pass
+
+
class XModuleFields(object):
display_name = String(
+ display_name="Display Name",
help="Display name for this module",
scope=Scope.settings,
- default=None,
+ default=None
)
+ nonEditableSettingsScope = NonEditableSettingsScope(user=Scope.settings.user, block=Scope.settings.block)
class XModule(XModuleFields, HTMLSnippet, XBlock):
@@ -334,12 +340,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
# (like a practice problem).
has_score = False
- # cdodge: this is a list of metadata names which are 'system' metadata
- # and should not be edited by an end-user
-
- system_metadata_fields = ['data_dir', 'published_date', 'published_by', 'is_draft',
- 'discussion_id', 'xml_attributes']
-
# A list of descriptor attributes that must be equal for the descriptors to
# be equal
equality_attributes = ('_model_data', 'location')
diff --git a/common/lib/xmodule/xmodule/xml_module.py b/common/lib/xmodule/xmodule/xml_module.py
index f9de929c05..bfbf12635a 100644
--- a/common/lib/xmodule/xmodule/xml_module.py
+++ b/common/lib/xmodule/xmodule/xml_module.py
@@ -6,10 +6,11 @@ import sys
from collections import namedtuple
from lxml import etree
-from xblock.core import Object, Scope
+from xblock.core import Object
from xmodule.x_module import (XModuleDescriptor, policy_key)
from xmodule.modulestore import Location
from xmodule.modulestore.inheritance import own_metadata
+from xmodule.x_module import XModuleFields
log = logging.getLogger(__name__)
@@ -84,7 +85,8 @@ class XmlDescriptor(XModuleDescriptor):
Mixin class for standardized parsing of from xml
"""
- xml_attributes = Object(help="Map of unhandled xml attributes, used only for storage between import and export", default={}, scope=Scope.settings)
+ xml_attributes = Object(help="Map of unhandled xml attributes, used only for storage between import and export",
+ default={}, scope=XModuleFields.nonEditableSettingsScope)
# Extension to append to filename paths
filename_extension = 'xml'
diff --git a/local-requirements.txt b/local-requirements.txt
index 6d2a6270b7..5d04fe8f0b 100644
--- a/local-requirements.txt
+++ b/local-requirements.txt
@@ -6,4 +6,4 @@
# XBlock:
# Might change frequently, so put it in local-requirements.txt,
# but conceptually is an external package, so it is in a separate repo.
--e git+https://github.com/edx/XBlock.git@2e0770ff#egg=XBlock
+-e git+https://github.com/edx/XBlock.git@49181a1b#egg=XBlock