diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 6bcc147c89d9eeccde0cf2c91ea8103b4b1dc879..6f103640facb02c544863fa152cc526a4104f817 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -246,7 +246,21 @@ def shim_xmodule_js(block, fragment): fragment.json_init_args = {'xmodule-type': block.js_module_name} -class XModuleMixin(XBlockMixin): +class XModuleFields(object): + """ + Common fields for XModules. + """ + display_name = String( + display_name="Display Name", + help="This name appears in the horizontal navigation at the top of the page.", + scope=Scope.settings, + # it'd be nice to have a useful default but it screws up other things; so, + # use display_name_with_default for those + default=None + ) + + +class XModuleMixin(XModuleFields, XBlockMixin): """ Fields and methods used by XModules internally. @@ -277,15 +291,6 @@ class XModuleMixin(XBlockMixin): # in the module icon_class = 'other' - display_name = String( - display_name="Display Name", - help="This name appears in the horizontal navigation at the top of the page.", - scope=Scope.settings, - # it'd be nice to have a useful default but it screws up other things; so, - # use display_name_with_default for those - default=None - ) - def __init__(self, *args, **kwargs): self.xmodule_runtime = None self._child_instances = None @@ -570,6 +575,95 @@ class XModuleMixin(XBlockMixin): self.xmodule_runtime = xmodule_runtime self._field_data = field_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`. + """ + metadata_fields = {} + + # Only use the fields from this class, not mixins + fields = getattr(self, 'unmixed_class', self.__class__).fields + + for field in fields.values(): + + if field.scope != Scope.settings or field in self.non_editable_metadata_fields: + continue + + metadata_fields[field.name] = self._create_metadata_editor_info(field) + + return metadata_fields + + def _create_metadata_editor_info(self, field): + """ + Creates the information needed by the metadata editor for a specific field. + """ + def jsonify_value(field, json_choice): + """ + Convert field value to JSON, if needed. + """ + if isinstance(json_choice, dict): + new_json_choice = dict(json_choice) # make a copy so below doesn't change the original + if 'display_name' in json_choice: + new_json_choice['display_name'] = get_text(json_choice['display_name']) + if 'value' in json_choice: + new_json_choice['value'] = field.to_json(json_choice['value']) + else: + new_json_choice = field.to_json(json_choice) + return new_json_choice + + def get_text(value): + """Localize a text value that might be None.""" + if value is None: + return None + else: + return self.runtime.service(self, "i18n").ugettext(value) + + # gets the 'default_value' and 'explicitly_set' attrs + metadata_field_editor_info = self.runtime.get_field_provenance(self, field) + metadata_field_editor_info['field_name'] = field.name + metadata_field_editor_info['display_name'] = get_text(field.display_name) + metadata_field_editor_info['help'] = get_text(field.help) + metadata_field_editor_info['value'] = field.read_json(self) + + # We support the following editors: + # 1. A select editor for fields with a list of possible values (includes Booleans). + # 2. Number editors for integers and floats. + # 3. A generic string editor for anything else (editing JSON representation of the value). + editor_type = "Generic" + values = field.values + if "values_provider" in field.runtime_options: + values = field.runtime_options['values_provider'](self) + if isinstance(values, (tuple, list)) and len(values) > 0: + editor_type = "Select" + values = [jsonify_value(field, json_choice) for json_choice in values] + elif isinstance(field, Integer): + editor_type = "Integer" + elif isinstance(field, Float): + editor_type = "Float" + elif isinstance(field, List): + editor_type = "List" + elif isinstance(field, Dict): + editor_type = "Dict" + elif isinstance(field, RelativeTime): + editor_type = "RelativeTime" + metadata_field_editor_info['type'] = editor_type + metadata_field_editor_info['options'] = [] if values is None else values + + return metadata_field_editor_info + class ProxyAttribute(object): """ @@ -964,92 +1058,6 @@ class XModuleDescriptor(XModuleMixin, HTMLSnippet, ResourceTemplates, XBlock): ")".format(self) ) - @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`. - """ - metadata_fields = {} - - # Only use the fields from this class, not mixins - fields = getattr(self, 'unmixed_class', self.__class__).fields - - for field in fields.values(): - - if field.scope != Scope.settings or field in self.non_editable_metadata_fields: - continue - - metadata_fields[field.name] = self._create_metadata_editor_info(field) - - return metadata_fields - - def _create_metadata_editor_info(self, field): - """ - Creates the information needed by the metadata editor for a specific field. - """ - def jsonify_value(field, json_choice): - if isinstance(json_choice, dict): - json_choice = dict(json_choice) # make a copy so below doesn't change the original - if 'display_name' in json_choice: - json_choice['display_name'] = get_text(json_choice['display_name']) - if 'value' in json_choice: - json_choice['value'] = field.to_json(json_choice['value']) - else: - json_choice = field.to_json(json_choice) - return json_choice - - def get_text(value): - """Localize a text value that might be None.""" - if value is None: - return None - else: - return self.runtime.service(self, "i18n").ugettext(value) - - # gets the 'default_value' and 'explicitly_set' attrs - metadata_field_editor_info = self.runtime.get_field_provenance(self, field) - metadata_field_editor_info['field_name'] = field.name - metadata_field_editor_info['display_name'] = get_text(field.display_name) - metadata_field_editor_info['help'] = get_text(field.help) - metadata_field_editor_info['value'] = field.read_json(self) - - # We support the following editors: - # 1. A select editor for fields with a list of possible values (includes Booleans). - # 2. Number editors for integers and floats. - # 3. A generic string editor for anything else (editing JSON representation of the value). - editor_type = "Generic" - values = field.values - if "values_provider" in field.runtime_options: - values = field.runtime_options['values_provider'](self) - if isinstance(values, (tuple, list)) and len(values) > 0: - editor_type = "Select" - values = [jsonify_value(field, json_choice) for json_choice in values] - elif isinstance(field, Integer): - editor_type = "Integer" - elif isinstance(field, Float): - editor_type = "Float" - elif isinstance(field, List): - editor_type = "List" - elif isinstance(field, Dict): - editor_type = "Dict" - elif isinstance(field, RelativeTime): - editor_type = "RelativeTime" - metadata_field_editor_info['type'] = editor_type - metadata_field_editor_info['options'] = [] if values is None else values - - return metadata_field_editor_info - # ~~~~~~~~~~~~~~~ XModule Indirection ~~~~~~~~~~~~~~~~ @property def _xmodule(self):