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