Commit 4a64c0c9 by Jay Zoldak

Merge in from origin/feature/christina/metadata-ui

parents 6627ca56 5d41e2a9
...@@ -49,8 +49,10 @@ def verify_all_setting_entries(expected_entries): ...@@ -49,8 +49,10 @@ def verify_all_setting_entries(expected_entries):
settings = world.browser.find_by_css('.wrapper-comp-setting') settings = world.browser.find_by_css('.wrapper-comp-setting')
assert_equal(len(expected_entries), len(settings)) assert_equal(len(expected_entries), len(settings))
for (counter, setting) in enumerate(settings): for (counter, setting) in enumerate(settings):
world.verify_setting_entry(setting, expected_entries[counter][0], world.verify_setting_entry(
expected_entries[counter][1], expected_entries[counter][2]) setting, expected_entries[counter][0],
expected_entries[counter][1], expected_entries[counter][2]
)
@world.absorb @world.absorb
......
...@@ -6,12 +6,15 @@ from lettuce import world, step ...@@ -6,12 +6,15 @@ from lettuce import world, step
@step('I have created a Discussion Tag$') @step('I have created a Discussion Tag$')
def i_created_discussion_tag(step): def i_created_discussion_tag(step):
world.create_component_instance(step, '.large-discussion-icon', 'i4x://edx/templates/discussion/Discussion_Tag', world.create_component_instance(
'.xmodule_DiscussionModule') step, '.large-discussion-icon',
'i4x://edx/templates/discussion/Discussion_Tag',
'.xmodule_DiscussionModule'
)
@step('I see three alphabetized settings and their expected values$') @step('I see three alphabetized settings and their expected values$')
def i_see_only_the_display_name(step): def i_see_only_the_settings_and_values(step):
world.verify_all_setting_entries( world.verify_all_setting_entries(
[ [
['Category', "Week 1", True], ['Category', "Week 1", True],
......
...@@ -6,8 +6,10 @@ from lettuce import world, step ...@@ -6,8 +6,10 @@ from lettuce import world, step
@step('I have created a Blank HTML Page$') @step('I have created a Blank HTML Page$')
def i_created_blank_html_page(step): def i_created_blank_html_page(step):
world.create_component_instance(step, '.large-html-icon', 'i4x://edx/templates/html/Blank_HTML_Page', world.create_component_instance(
'.xmodule_HtmlModule') step, '.large-html-icon', 'i4x://edx/templates/html/Blank_HTML_Page',
'.xmodule_HtmlModule'
)
@step('I see only the HTML display name setting$') @step('I see only the HTML display name setting$')
......
...@@ -13,6 +13,12 @@ Feature: Problem Editor ...@@ -13,6 +13,12 @@ Feature: Problem Editor
Then I can modify the display name Then I can modify the display name
And my display name change is persisted on save And my display name change is persisted on save
Scenario: User can specify special characters in String values
Given I have created a Blank Common Problem
And I edit and select Settings
Then I can specify special characters in the display name
And my special characters and persisted on save
Scenario: User can revert display name to unset Scenario: User can revert display name to unset
Given I have created a Blank Common Problem Given I have created a Blank Common Problem
And I edit and select Settings And I edit and select Settings
...@@ -59,4 +65,3 @@ Feature: Problem Editor ...@@ -59,4 +65,3 @@ Feature: Problem Editor
Given I have created a LaTeX Problem Given I have created a LaTeX Problem
And I edit and select Settings And I edit and select Settings
Then Edit High Level Source is visible Then Edit High Level Source is visible
...@@ -14,8 +14,17 @@ SHOW_ANSWER = "Show Answer" ...@@ -14,8 +14,17 @@ SHOW_ANSWER = "Show Answer"
############### ACTIONS #################### ############### ACTIONS ####################
@step('I have created a Blank Common Problem$') @step('I have created a Blank Common Problem$')
def i_created_blank_common_problem(step): def i_created_blank_common_problem(step):
<<<<<<< HEAD
world.create_component_instance(step, '.large-problem-icon', 'i4x://edx/templates/problem/Blank_Common_Problem', world.create_component_instance(step, '.large-problem-icon', 'i4x://edx/templates/problem/Blank_Common_Problem',
'.xmodule_CapaModule') '.xmodule_CapaModule')
=======
world.create_component_instance(
step,
'.large-problem-icon',
'i4x://edx/templates/problem/Blank_Common_Problem',
'.xmodule_CapaModule'
)
>>>>>>> a0efd3a5d7045dd9369869fd15fc2cc4ecdb6cc1
@step('I edit and select Settings$') @step('I edit and select Settings$')
...@@ -47,6 +56,21 @@ def my_display_name_change_is_persisted_on_save(step): ...@@ -47,6 +56,21 @@ def my_display_name_change_is_persisted_on_save(step):
verify_modified_display_name() verify_modified_display_name()
<<<<<<< HEAD
=======
@step('I can specify special characters in the display name')
def i_can_modify_the_display_name_with_special_chars(step):
world.get_setting_entry(DISPLAY_NAME).find_by_css('.setting-input')[0].fill("updated ' \" &")
verify_modified_display_name_with_special_chars()
@step('my special characters and persisted on save')
def special_chars_persisted_on_save(step):
world.save_component_and_reopen(step)
verify_modified_display_name_with_special_chars()
>>>>>>> a0efd3a5d7045dd9369869fd15fc2cc4ecdb6cc1
@step('I can revert the display name to unset') @step('I can revert the display name to unset')
def can_revert_display_name_to_unset(step): def can_revert_display_name_to_unset(step):
world.revert_setting_entry(DISPLAY_NAME) world.revert_setting_entry(DISPLAY_NAME)
...@@ -78,6 +102,7 @@ def i_can_revert_to_default_for_randomization(step): ...@@ -78,6 +102,7 @@ def i_can_revert_to_default_for_randomization(step):
world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Always", False) world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Always", False)
@step('I can set the weight to 3.5') @step('I can set the weight to 3.5')
def i_can_set_weight_to_3_5(step): def i_can_set_weight_to_3_5(step):
world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill('3.5') world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill('3.5')
...@@ -97,6 +122,7 @@ def i_can_revert_to_default_for_randomization(step): ...@@ -97,6 +122,7 @@ def i_can_revert_to_default_for_randomization(step):
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", False) world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", False)
@step('if I set the weight to abc, it remains unset') @step('if I set the weight to abc, it remains unset')
def set_the_weight_to_abc(step): def set_the_weight_to_abc(step):
world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill('abc') world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill('abc')
...@@ -107,6 +133,7 @@ def set_the_weight_to_abc(step): ...@@ -107,6 +133,7 @@ def set_the_weight_to_abc(step):
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", False) world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", False)
@step('if I set the max attempts to 2.34, the max attempts are persisted as 234') @step('if I set the max attempts to 2.34, the max attempts are persisted as 234')
def set_the_weight_to_abc(step): def set_the_weight_to_abc(step):
world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill('2.34') world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill('2.34')
...@@ -115,6 +142,7 @@ def set_the_weight_to_abc(step): ...@@ -115,6 +142,7 @@ def set_the_weight_to_abc(step):
world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "234", True) world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "234", True)
@step('I set the max attempts to -3, the max attempts are persisted as 1') @step('I set the max attempts to -3, the max attempts are persisted as 1')
def set_max_attempts_to_neg_3(step): def set_max_attempts_to_neg_3(step):
world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill('-3') world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill('-3')
...@@ -123,6 +151,7 @@ def set_max_attempts_to_neg_3(step): ...@@ -123,6 +151,7 @@ def set_max_attempts_to_neg_3(step):
world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "1", True) world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "1", True)
@step('Edit High Level Source is not visible') @step('Edit High Level Source is not visible')
def edit_high_level_source_not_visible(step): def edit_high_level_source_not_visible(step):
verify_high_level_source(step, False) verify_high_level_source(step, False)
...@@ -159,13 +188,22 @@ def verify_modified_weight(): ...@@ -159,13 +188,22 @@ def verify_modified_weight():
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "3.5", True) world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "3.5", True)
def verify_modified_randomization(): def verify_modified_randomization():
world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Per Student", True) world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Per Student", True)
def verify_modified_display_name(): def verify_modified_display_name():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, 'modified', True) world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, 'modified', True)
<<<<<<< HEAD
=======
def verify_modified_display_name_with_special_chars():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, "updated ' \" &", True)
>>>>>>> a0efd3a5d7045dd9369869fd15fc2cc4ecdb6cc1
def verify_unset_display_name(): def verify_unset_display_name():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, '', False) world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, '', False)
...@@ -3,10 +3,15 @@ ...@@ -3,10 +3,15 @@
from lettuce import world, step from lettuce import world, step
@step('I have created a Video component$') @step('I have created a Video component$')
def i_created_a_video_component(step): def i_created_a_video_component(step):
world.create_component_instance(step, '.large-video-icon', 'i4x://edx/templates/video/default', world.create_component_instance(
'.xmodule_VideoModule') step, '.large-video-icon',
'i4x://edx/templates/video/default',
'.xmodule_VideoModule'
)
@step('I see only the video display name setting$') @step('I see only the video display name setting$')
def i_see_only_the_video_display_name(step): def i_see_only_the_video_display_name(step):
......
../../../templates/js/metadata-editor.underscore
\ No newline at end of file
../../../templates/js/metadata-number-entry.underscore
\ No newline at end of file
../../../templates/js/metadata-option-entry.underscore
\ No newline at end of file
../../../templates/js/metadata-string-entry.underscore
\ No newline at end of file
...@@ -27,7 +27,7 @@ describe "AJAX Errors", -> ...@@ -27,7 +27,7 @@ describe "AJAX Errors", ->
tpl = readFixtures('system-feedback.underscore') tpl = readFixtures('system-feedback.underscore')
beforeEach -> beforeEach ->
setFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl)) appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
appendSetFixtures(sandbox({id: "page-notification"})) appendSetFixtures(sandbox({id: "page-notification"}))
@requests = requests = [] @requests = requests = []
@xhr = sinon.useFakeXMLHttpRequest() @xhr = sinon.useFakeXMLHttpRequest()
......
tpl = readFixtures('system-feedback.underscore') tpl = readFixtures('system-feedback.underscore')
beforeEach -> beforeEach ->
setFixtures(sandbox({id: "page-alert"})) appendSetFixtures(sandbox({id: "page-alert"}))
appendSetFixtures(sandbox({id: "page-notification"})) appendSetFixtures(sandbox({id: "page-notification"}))
appendSetFixtures(sandbox({id: "page-prompt"})) appendSetFixtures(sandbox({id: "page-prompt"}))
appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl)) appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(tpl))
......
...@@ -4,7 +4,7 @@ describe "CMS.Views.ModuleEdit", -> ...@@ -4,7 +4,7 @@ describe "CMS.Views.ModuleEdit", ->
@stubModule.id = 'stub-id' @stubModule.id = 'stub-id'
setFixtures """ appendSetFixtures """
<li class="component" id="stub-id"> <li class="component" id="stub-id">
<div class="component-editor"> <div class="component-editor">
<div class="module-editor"> <div class="module-editor">
......
...@@ -29,7 +29,7 @@ describe "CMS.Views.SectionEdit", -> ...@@ -29,7 +29,7 @@ describe "CMS.Views.SectionEdit", ->
feedback_tpl = readFixtures('system-feedback.underscore') feedback_tpl = readFixtures('system-feedback.underscore')
beforeEach -> beforeEach ->
setFixtures($("<script>", {id: "section-name-edit-tpl", type: "text/template"}).text(tpl)) appendSetFixtures($("<script>", {id: "section-name-edit-tpl", type: "text/template"}).text(tpl))
appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedback_tpl)) appendSetFixtures($("<script>", {id: "system-feedback-tpl", type: "text/template"}).text(feedback_tpl))
spyOn(CMS.Views.SectionEdit.prototype, "switchToShowView") spyOn(CMS.Views.SectionEdit.prototype, "switchToShowView")
.andCallThrough() .andCallThrough()
......
...@@ -126,7 +126,6 @@ class CMS.Views.ModuleEdit extends Backbone.View ...@@ -126,7 +126,6 @@ class CMS.Views.ModuleEdit extends Backbone.View
hideDataEditor: => hideDataEditor: =>
editorModeButtonParent = @$el.find('#editor-mode') editorModeButtonParent = @$el.find('#editor-mode')
# Can it be enough to just remove active-mode?
editorModeButtonParent.addClass('inactive-mode') editorModeButtonParent.addClass('inactive-mode')
editorModeButtonParent.removeClass('active-mode') editorModeButtonParent.removeClass('active-mode')
@$el.find('.wrapper-comp-settings').addClass('is-active') @$el.find('.wrapper-comp-settings').addClass('is-active')
......
...@@ -825,6 +825,3 @@ function saveSetSectionScheduleDate(e) { ...@@ -825,6 +825,3 @@ function saveSetSectionScheduleDate(e) {
hideModal(); hideModal();
}); });
} }
...@@ -3,7 +3,6 @@ if (!CMS.Views['Metadata']) CMS.Views.Metadata = {}; ...@@ -3,7 +3,6 @@ if (!CMS.Views['Metadata']) CMS.Views.Metadata = {};
CMS.Views.Metadata.Editor = Backbone.View.extend({ CMS.Views.Metadata.Editor = Backbone.View.extend({
// Model is simply a Backbone.Model instance. // Model is simply a Backbone.Model instance.
initialize : function() { initialize : function() {
var tpl = $("#metadata-editor-tpl").text(); var tpl = $("#metadata-editor-tpl").text();
if(!tpl) { if(!tpl) {
...@@ -11,7 +10,7 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({ ...@@ -11,7 +10,7 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
} }
this.template = _.template(tpl); this.template = _.template(tpl);
this.$el.html(this.template({metadata_entries: this.model.attributes})); this.$el.html(this.template({numEntries: this.model.keys().length}));
var counter = 0; var counter = 0;
// Sort entries by display name. // Sort entries by display name.
...@@ -45,6 +44,9 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({ ...@@ -45,6 +44,9 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
}); });
}, },
/**
* Returns the just the modified metadata values, in the format used to persist to the server.
*/
getModifiedMetadataValues: function () { getModifiedMetadataValues: function () {
var modified_values = {}; var modified_values = {};
_.each(this.models, _.each(this.models,
...@@ -57,8 +59,16 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({ ...@@ -57,8 +59,16 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
return modified_values; return modified_values;
}, },
/**
* Returns a display name for the component related to this metadata. This method looks to see
* if there is a metadata entry called 'display_name', and if so, it returns its value. If there
* is no such entry, or if display_name does not have a value set, it returns an empty string.
*/
getDisplayName: function () { getDisplayName: function () {
// It is possible that there is no display name set. In that case, return empty string. // It is possible that there is no display name set. In that case, return empty string.
if (this.model.get('display_name') === undefined) {
return '';
}
var displayNameValue = this.model.get('display_name').value; var displayNameValue = this.model.get('display_name').value;
return displayNameValue ? displayNameValue : ''; return displayNameValue ? displayNameValue : '';
} }
...@@ -67,13 +77,13 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({ ...@@ -67,13 +77,13 @@ CMS.Views.Metadata.Editor = Backbone.View.extend({
CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({ CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({
// Model is CMS.Models.Metadata. // Model is CMS.Models.Metadata.
initialize : function() { initialize : function() {
var self = this; var self = this;
var templateName = this.getTemplateName(); var templateName = _.result(this, 'templateName');
// Backbone model cid is only unique within the collection.
this.uniqueId = _.uniqueId(templateName + "_"); this.uniqueId = _.uniqueId(templateName + "_");
var tpl = $("#"+templateName).text(); var tpl = document.getElementById(templateName).text;
if(!tpl) { if(!tpl) {
console.error("Couldn't load template: " + templateName); console.error("Couldn't load template: " + templateName);
} }
...@@ -82,22 +92,42 @@ CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({ ...@@ -82,22 +92,42 @@ CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({
this.render(); this.render();
}, },
getTemplateName : function () {}, /**
* The ID/name of the template. Subclasses must override this.
*/
templateName: '',
/**
* Returns the value currently displayed in the editor/view. Subclasses should implement this method.
*/
getValueFromEditor : function () {}, getValueFromEditor : function () {},
/**
* Sets the value currently displayed in the editor/view. Subclasses should implement this method.
*/
setValueInEditor : function (value) {}, setValueInEditor : function (value) {},
/**
* Sets the value in the model, using the value currently displayed in the view. Afterward,
* this method re-renders to update the clear button.
*/
updateModel: function () { updateModel: function () {
this.model.setValue(this.getValueFromEditor()); this.model.setValue(this.getValueFromEditor());
this.render(); this.render();
}, },
/**
* Clears the value currently set in the model (reverting to the default). Afterward, this method
* re-renders the view.
*/
clear: function () { clear: function () {
this.model.clear(); this.model.clear();
this.render(); this.render();
}, },
/**
* Shows the clear button, if it is not already showing.
*/
showClearButton: function() { showClearButton: function() {
if (!this.$el.hasClass('is-set')) { if (!this.$el.hasClass('is-set')) {
this.$el.addClass('is-set'); this.$el.addClass('is-set');
...@@ -106,10 +136,17 @@ CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({ ...@@ -106,10 +136,17 @@ CMS.Views.Metadata.AbstractEditor = Backbone.View.extend({
} }
}, },
/**
* Returns the clear button.
*/
getClearButton: function () { getClearButton: function () {
return this.$el.find('.setting-clear'); return this.$el.find('.setting-clear');
}, },
/**
* Renders the editor, updating the value displayed in the view, as well as the state of
* the clear button.
*/
render: function () { render: function () {
if (!this.template) return; if (!this.template) return;
...@@ -134,9 +171,7 @@ CMS.Views.Metadata.String = CMS.Views.Metadata.AbstractEditor.extend({ ...@@ -134,9 +171,7 @@ CMS.Views.Metadata.String = CMS.Views.Metadata.AbstractEditor.extend({
"click .setting-clear" : "clear" "click .setting-clear" : "clear"
}, },
getTemplateName : function () { templateName: "metadata-string-entry",
return "metadata-string-entry";
},
getValueFromEditor : function () { getValueFromEditor : function () {
return this.$el.find('#' + this.uniqueId).val(); return this.$el.find('#' + this.uniqueId).val();
...@@ -172,7 +207,7 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({ ...@@ -172,7 +207,7 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({
} }
if (options.hasOwnProperty(max)) { if (options.hasOwnProperty(max)) {
this.max = Number(options[max]); this.max = Number(options[max]);
this.$el.find('input').attr(max, numToString(this.max.toFixed)); this.$el.find('input').attr(max, numToString(this.max));
} }
var stepValue = undefined; var stepValue = undefined;
if (options.hasOwnProperty(step)) { if (options.hasOwnProperty(step)) {
...@@ -196,9 +231,7 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({ ...@@ -196,9 +231,7 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({
} }
}, },
getTemplateName : function () { templateName: "metadata-number-entry",
return "metadata-number-entry";
},
getValueFromEditor : function () { getValueFromEditor : function () {
return this.$el.find('#' + this.uniqueId).val(); return this.$el.find('#' + this.uniqueId).val();
...@@ -208,6 +241,9 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({ ...@@ -208,6 +241,9 @@ CMS.Views.Metadata.Number = CMS.Views.Metadata.AbstractEditor.extend({
this.$el.find('input').val(value); this.$el.find('input').val(value);
}, },
/**
* Returns true if this view is restricted to integers, as opposed to floating points values.
*/
isIntegerField : function () { isIntegerField : function () {
return this.model.getType() === 'Integer'; return this.model.getType() === 'Integer';
}, },
...@@ -249,9 +285,7 @@ CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({ ...@@ -249,9 +285,7 @@ CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({
"click .setting-clear" : "clear" "click .setting-clear" : "clear"
}, },
getTemplateName : function () { templateName: "metadata-option-entry",
return "metadata-option-entry";
},
getValueFromEditor : function () { getValueFromEditor : function () {
var selectedText = this.$el.find('#' + this.uniqueId).find(":selected").text(); var selectedText = this.$el.find('#' + this.uniqueId).find(":selected").text();
...@@ -275,7 +309,7 @@ CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({ ...@@ -275,7 +309,7 @@ CMS.Views.Metadata.Option = CMS.Views.Metadata.AbstractEditor.extend({
value = modelValue['display_name']; value = modelValue['display_name'];
} }
}); });
$('#' + this.uniqueId + " option").filter(function() { this.$el.find('#' + this.uniqueId + " option").filter(function() {
return $(this).text() === value; return $(this).text() === value;
}).prop('selected', true); }).prop('selected', true);
} }
......
<ul class="list-input settings-list"> <ul class="list-input settings-list">
<% _.each(metadata_entries, function(entry) { %> <% _.each(_.range(numEntries), function() { %>
<li class="field comp-setting-entry metadata_entry" id="settings-listing"> <li class="field comp-setting-entry metadata_entry" id="settings-listing">
</li> </li>
<% }) %> <% }) %>
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
</script> </script>
<% showHighLevelSource='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] %> <% showHighLevelSource='source_code' in editable_metadata_fields and editable_metadata_fields['source_code']['explicitly_set'] %>
<% metadata_field_copy = copy.deepcopy(editable_metadata_fields) %> <% metadata_field_copy = copy.copy(editable_metadata_fields) %>
## Delete 'source_code' field (if it exists) so metadata editor view does not attempt to render it. ## Delete 'source_code' field (if it exists) so metadata editor view does not attempt to render it.
% if 'source_code' in editable_metadata_fields: % if 'source_code' in editable_metadata_fields:
## source-edit.html needs access to the 'source_code' value, so delete from a copy. ## source-edit.html needs access to the 'source_code' value, so delete from a copy.
...@@ -40,4 +40,4 @@ ...@@ -40,4 +40,4 @@
<%include file="source-edit.html" /> <%include file="source-edit.html" />
% endif % endif
<div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(metadata_field_copy)}'/> <div class="wrapper-comp-settings metadata_edit" id="settings-tab" data-metadata='${json.dumps(metadata_field_copy) | h}'/>
\ No newline at end of file \ No newline at end of file
<%! from django.utils.translation import ugettext as _ %>
<div class="wrapper-comp-editor" id="editor-tab"> <div class="wrapper-comp-editor" id="editor-tab">
<section class="problem-editor editor"> <section class="problem-editor editor">
<div class="row"> <div class="row">
%if enable_markdown: %if enable_markdown:
<div class="editor-bar"> <div class="editor-bar">
<ul class="format-buttons"> <ul class="format-buttons">
<li><a href="#" class="header-button" data-tooltip="Heading 1"><span <li><a href="#" class="header-button" data-tooltip='${_("Heading 1")}'><span
class="problem-editor-icon heading1"></span></a></li> class="problem-editor-icon heading1"></span></a></li>
<li><a href="#" class="multiple-choice-button" data-tooltip="Multiple Choice"><span <li><a href="#" class="multiple-choice-button" data-tooltip='${_("Multiple Choice")}'><span
class="problem-editor-icon multiple-choice"></span></a></li> class="problem-editor-icon multiple-choice"></span></a></li>
<li><a href="#" class="checks-button" data-tooltip="Checkboxes"><span <li><a href="#" class="checks-button" data-tooltip='${_("Checkboxes")}'><span
class="problem-editor-icon checks"></span></a></li> class="problem-editor-icon checks"></span></a></li>
<li><a href="#" class="string-button" data-tooltip="Text Input"><span <li><a href="#" class="string-button" data-tooltip='${_("Text Input")}'><span
class="problem-editor-icon string"></span></a></li> class="problem-editor-icon string"></span></a></li>
<li><a href="#" class="number-button" data-tooltip="Numerical Input"><span <li><a href="#" class="number-button" data-tooltip='${_("Numerical Input")}'><span
class="problem-editor-icon number"></span></a></li> class="problem-editor-icon number"></span></a></li>
<li><a href="#" class="dropdown-button" data-tooltip="Dropdown"><span <li><a href="#" class="dropdown-button" data-tooltip='${_("Dropdown")}'><span
class="problem-editor-icon dropdown"></span></a></li> class="problem-editor-icon dropdown"></span></a></li>
<li><a href="#" class="explanation-button" data-tooltip="Explanation"><span <li><a href="#" class="explanation-button" data-tooltip='${_("Explanation")}'><span
class="problem-editor-icon explanation"></span></a></li> class="problem-editor-icon explanation"></span></a></li>
</ul> </ul>
<ul class="editor-tabs"> <ul class="editor-tabs">
<li><a href="#" class="xml-tab advanced-toggle" data-tab="xml">Advanced Editor</a></li> <li><a href="#" class="xml-tab advanced-toggle" data-tab="xml">${_("Advanced Editor")}</a></li>
<li><a href="#" class="cheatsheet-toggle" data-tooltip="Toggle Cheatsheet">?</a></li> <li><a href="#" class="cheatsheet-toggle" data-tooltip='${_("Toggle Cheatsheet")}'>?</a></li>
</ul> </ul>
</div> </div>
<textarea class="markdown-box">${markdown | h}</textarea> <textarea class="markdown-box">${markdown | h}</textarea>
...@@ -34,7 +36,7 @@ ...@@ -34,7 +36,7 @@
<article class="simple-editor-cheatsheet"> <article class="simple-editor-cheatsheet">
<div class="cheatsheet-wrapper"> <div class="cheatsheet-wrapper">
<div class="row"> <div class="row">
<h6>Heading 1</h6> <h6>${_("Heading 1")}</h6>
<div class="col sample heading-1"> <div class="col sample heading-1">
<img src="/static/img/header-example.png" /> <img src="/static/img/header-example.png" />
</div> </div>
...@@ -45,7 +47,7 @@ ...@@ -45,7 +47,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Multiple Choice</h6> <h6>${_("Multiple Choice")}</h6>
<div class="col sample multiple-choice"> <div class="col sample multiple-choice">
<img src="/static/img/choice-example.png" /> <img src="/static/img/choice-example.png" />
</div> </div>
...@@ -56,7 +58,7 @@ ...@@ -56,7 +58,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Checkboxes</h6> <h6>${_("Checkboxes")}</h6>
<div class="col sample check-multiple"> <div class="col sample check-multiple">
<img src="/static/img/multi-example.png" /> <img src="/static/img/multi-example.png" />
</div> </div>
...@@ -67,7 +69,7 @@ ...@@ -67,7 +69,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Text Input</h6> <h6>${_("Text Input")}</h6>
<div class="col sample string-response"> <div class="col sample string-response">
<img src="/static/img/string-example.png" /> <img src="/static/img/string-example.png" />
</div> </div>
...@@ -76,7 +78,7 @@ ...@@ -76,7 +78,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Numerical Input</h6> <h6>${_("Numerical Input")}</h6>
<div class="col sample numerical-response"> <div class="col sample numerical-response">
<img src="/static/img/number-example.png" /> <img src="/static/img/number-example.png" />
</div> </div>
...@@ -85,7 +87,7 @@ ...@@ -85,7 +87,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Dropdown</h6> <h6>${_("Dropdown")}</h6>
<div class="col sample option-reponse"> <div class="col sample option-reponse">
<img src="/static/img/select-example.png" /> <img src="/static/img/select-example.png" />
</div> </div>
...@@ -94,7 +96,7 @@ ...@@ -94,7 +96,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<h6>Explanation</h6> <h6>${_("Explanation")}</h6>
<div class="col sample explanation"> <div class="col sample explanation">
<img src="/static/img/explanation-example.png" /> <img src="/static/img/explanation-example.png" />
</div> </div>
......
...@@ -66,13 +66,16 @@ class ComplexEncoder(json.JSONEncoder): ...@@ -66,13 +66,16 @@ 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(display_name="Maximum Attempts", max_attempts = StringyInteger(
help="This specifies the number of times the student can try to answer this problem. If unset, infinite attempts are allowed.", display_name="Maximum Attempts",
values = {"min" : 1 }, scope=Scope.settings) help="Defines the number of times a student can try to answer this problem. If the value is not set, infinite attempts are allowed.",
values = {"min" : 1 }, scope=Scope.settings
)
due = Date(help="Date that this problem is due by", 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) graceperiod = Timedelta(help="Amount of time after the due date that submissions will be accepted", scope=Scope.settings)
showanswer = String(display_name="Show Answer", showanswer = String(
help="Specifies when to show the answer to this problem. A default value can be set course-wide in Advanced Settings.", display_name="Show Answer",
help="Defines when to show the answer to the problem. A default value can be set in Advanced Settings.",
scope=Scope.settings, default="closed", scope=Scope.settings, default="closed",
values=[ values=[
{"display_name": "Always", "value": "always"}, {"display_name": "Always", "value": "always"},
...@@ -81,26 +84,33 @@ class CapaFields(object): ...@@ -81,26 +84,33 @@ class CapaFields(object):
{"display_name": "Closed", "value": "closed"}, {"display_name": "Closed", "value": "closed"},
{"display_name": "Finished", "value": "finished"}, {"display_name": "Finished", "value": "finished"},
{"display_name": "Past Due", "value": "past_due"}, {"display_name": "Past Due", "value": "past_due"},
{"display_name": "Never", "value": "never"}]) {"display_name": "Never", "value": "never"}]
)
force_save_button = Boolean(help="Whether to force the save button to appear on the page", scope=Scope.settings, default=False) force_save_button = Boolean(help="Whether to force the save button to appear on the page", scope=Scope.settings, default=False)
rerandomize = Randomization(display_name="Randomization", help="Specifies whether variable inputs for this problem are randomized each time a student loads the problem. This only applies to problems that have randomly generated numeric variables. A default value can be set course-wide in Advanced Settings.", rerandomize = Randomization(
display_name="Randomization", help="Defines how often inputs are randomized when a student loads the problem. This setting only applies to problems that can have randomly generated numeric values. A default value can be set in Advanced Settings.",
default="always", scope=Scope.settings, values=[{"display_name": "Always", "value": "always"}, default="always", scope=Scope.settings, values=[{"display_name": "Always", "value": "always"},
{"display_name": "On Reset", "value": "onreset"}, {"display_name": "On Reset", "value": "onreset"},
{"display_name": "Never", "value": "never"}, {"display_name": "Never", "value": "never"},
{"display_name": "Per Student", "value": "per_student"}]) {"display_name": "Per Student", "value": "per_student"}]
)
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={})
input_state = Object(help="Dictionary for maintaining the state of inputtypes", scope=Scope.user_state) 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) 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) 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(display_name="Problem Weight", weight = StringyFloat(
help="Specifies the number of points the problem is worth. If unset, each response field in the problem is worth one point.", display_name="Problem Weight",
help="Defines the number of points each problem is worth. If the value is not set, each response field in the problem is worth one point.",
values = {"min" : 0 , "step": .1}, values = {"min" : 0 , "step": .1},
scope=Scope.settings) scope=Scope.settings
)
markdown = String(help="Markdown source of this module", scope=Scope.settings) 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.", source_code = String(
scope=Scope.settings) help="Source code for LaTeX and Word problems. This feature is not well-supported.",
scope=Scope.settings
)
class CapaModule(CapaFields, XModule): class CapaModule(CapaFields, XModule):
......
...@@ -48,33 +48,49 @@ class VersionInteger(Integer): ...@@ -48,33 +48,49 @@ class VersionInteger(Integer):
class CombinedOpenEndedFields(object): class CombinedOpenEndedFields(object):
display_name = String(help="Display name for this module", default="Open Ended Grading", scope=Scope.settings) display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
default="Open Ended Grading", scope=Scope.settings
)
current_task_number = StringyInteger(help="Current task that the student is on.", default=0, scope=Scope.user_state) current_task_number = StringyInteger(help="Current task that the student is on.", default=0, scope=Scope.user_state)
task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state) task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state)
state = String(help="Which step within the current task that the student is on.", default="initial", state = String(help="Which step within the current task that the student is on.", default="initial",
scope=Scope.user_state) scope=Scope.user_state)
student_attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0, student_attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0,
scope=Scope.user_state) scope=Scope.user_state)
ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False, ready_to_reset = StringyBoolean(
scope=Scope.user_state) help="If the problem is ready to be reset or not.", default=False,
attempts = StringyInteger(display_name="Maximum Attempts", scope=Scope.user_state
)
attempts = StringyInteger(
display_name="Maximum Attempts",
help="The number of times the student can try to answer this problem.", default=1, help="The number of times the student can try to answer this problem.", default=1,
scope=Scope.settings, values = {"min" : 1 }) scope=Scope.settings, values = {"min" : 1 }
)
is_graded = StringyBoolean(display_name="Graded", help="Whether or not the problem is graded.", default=False, scope=Scope.settings) is_graded = StringyBoolean(display_name="Graded", help="Whether or not the problem is graded.", default=False, scope=Scope.settings)
accept_file_upload = StringyBoolean(display_name="Allow File Uploads", accept_file_upload = StringyBoolean(
help="Whether or not the student can submit files as a response.", default=False, scope=Scope.settings) display_name="Allow File Uploads",
skip_spelling_checks = StringyBoolean(display_name="Disable Quality Filter", help="Whether or not the student can submit files as a response.", default=False, scope=Scope.settings
# TODO: passing of text failed with "won't". Need to make our code more robust. )
help="If False, submissions with poor spelling, short length, or poor grammar will not be peer reviewed.", skip_spelling_checks = StringyBoolean(
default=False, scope=Scope.settings) display_name="Disable Quality Filter",
help="If False, the Quality Filter is enabled and submissions with poor spelling, short length, or poor grammar will not be peer reviewed.",
default=False, scope=Scope.settings
)
due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings) due = Date(help="Date that this problem is due by", default=None, scope=Scope.settings)
graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None, graceperiod = String(
scope=Scope.settings) help="Amount of time after the due date that submissions will be accepted",
default=None,
scope=Scope.settings
)
version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings) version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, 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)
weight = StringyFloat(display_name="Problem Weight", weight = StringyFloat(
help="The number of points the problem is worth. By default, each problem is worth one point.", display_name="Problem Weight",
scope=Scope.settings, values = {"min" : 0 , "step": ".1"}) help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
scope=Scope.settings, values = {"min" : 0 , "step": ".1"}
)
markdown = String(help="Markdown source of this module", scope=Scope.settings) markdown = String(help="Markdown source of this module", scope=Scope.settings)
......
...@@ -8,12 +8,16 @@ from xblock.core import String, Scope ...@@ -8,12 +8,16 @@ from xblock.core import String, Scope
class DiscussionFields(object): class DiscussionFields(object):
discussion_id = String(scope=Scope.settings) discussion_id = String(scope=Scope.settings)
discussion_category = String(display_name="Category", discussion_category = String(
help="Specifies a category name for this discussion. This name appears in the left pane of the discussion forum for your course.", display_name="Category",
scope=Scope.settings) help="A category name for the discussion. This name appears in the left pane of the discussion forum for the course.",
discussion_target = String(display_name="Subcategory", scope=Scope.settings
help="Specifies a subcategory name for this discussion. This name appears in the left pane of the discussion forum for your course.", )
scope=Scope.settings) discussion_target = String(
display_name="Subcategory",
help="A subcategory name for the discussion. This name appears in the left pane of the discussion forum for the course.",
scope=Scope.settings
)
sort_key = String(scope=Scope.settings) sort_key = String(scope=Scope.settings)
......
...@@ -28,25 +28,37 @@ EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please ...@@ -28,25 +28,37 @@ EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please
class PeerGradingFields(object): class PeerGradingFields(object):
use_for_single_location = StringyBoolean(display_name="Show Single Problem", use_for_single_location = StringyBoolean(
display_name="Show Single Problem",
help='When True, only the single problem specified by "Link to Problem Location" is shown. ' help='When True, only the single problem specified by "Link to Problem Location" is shown. '
'When False, a panel is displayed with all problems available for peer grading.', 'When False, a panel is displayed with all problems available for peer grading.',
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings) default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings
link_to_location = String(display_name="Link to Problem Location", )
link_to_location = String(
display_name="Link to Problem Location",
help='The location of the problem being graded. Only used when "Show Single Problem" is True.', help='The location of the problem being graded. Only used when "Show Single Problem" is True.',
default=LINK_TO_LOCATION, scope=Scope.settings) default=LINK_TO_LOCATION, scope=Scope.settings
is_graded = StringyBoolean(display_name="Graded", )
help='Whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.', is_graded = StringyBoolean(
default=IS_GRADED, scope=Scope.settings) display_name="Graded",
help='Defines whether the student gets credit for grading this problem. Only used when "Show Single Problem" is True.',
default=IS_GRADED, scope=Scope.settings
)
due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings) due_date = Date(help="Due date that should be displayed.", default=None, scope=Scope.settings)
grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings) grace_period_string = String(help="Amount of grace to give on the due date.", default=None, scope=Scope.settings)
max_grade = StringyInteger(help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE, max_grade = StringyInteger(
scope=Scope.settings, values={"min" : 0 }) help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE,
student_data_for_location = Object(help="Student data for a given peer grading problem.", scope=Scope.settings, values={"min": 0}
scope=Scope.user_state) )
weight = StringyFloat(display_name="Problem Weight", student_data_for_location = Object(
help="Specifies the number of points the problem is worth. By default, each problem is worth one point.", help="Student data for a given peer grading problem.",
scope=Scope.settings, values = {"min" : 0 , "step": ".1"}) scope=Scope.user_state
)
weight = StringyFloat(
display_name="Problem Weight",
help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
scope=Scope.settings, values={"min": 0, "step": ".1"}
)
class PeerGradingModule(PeerGradingFields, XModule): class PeerGradingModule(PeerGradingFields, XModule):
......
...@@ -34,5 +34,4 @@ data: | ...@@ -34,5 +34,4 @@ data: |
</task> </task>
</combinedopenended> </combinedopenended>
children: [] children: []
...@@ -3,8 +3,6 @@ metadata: ...@@ -3,8 +3,6 @@ metadata:
display_name: Multiple Choice display_name: Multiple Choice
rerandomize: never rerandomize: never
showanswer: finished showanswer: finished
weight: ""
attempts: ""
markdown: markdown:
"A multiple choice problem presents radio buttons for student input. Students can only select a single "A multiple choice problem presents radio buttons for student input. Students can only select a single
option presented. Multiple Choice questions have been the subject of many areas of research due to the early option presented. Multiple Choice questions have been the subject of many areas of research due to the early
......
...@@ -460,8 +460,8 @@ class ImportTestCase(BaseCourseTestCase): ...@@ -460,8 +460,8 @@ class ImportTestCase(BaseCourseTestCase):
) )
module = modulestore.get_instance(course.id, location) module = modulestore.get_instance(course.id, location)
self.assertEqual(len(module.get_children()), 0) self.assertEqual(len(module.get_children()), 0)
self.assertEqual(module.num_inputs, '5') self.assertEqual(module.num_inputs, 5)
self.assertEqual(module.num_top_words, '250') self.assertEqual(module.num_top_words, 250)
def test_cohort_config(self): def test_cohort_config(self):
""" """
......
#pylint: disable=C0111
#pylint: disable=W0621
from xmodule.x_module import XModuleFields from xmodule.x_module import XModuleFields
from xblock.core import Scope, String, Object, Boolean from xblock.core import Scope, String, Object, Boolean
from xmodule.fields import Date, StringyInteger, StringyFloat from xmodule.fields import Date, StringyInteger, StringyFloat
from xmodule.xml_module import XmlDescriptor 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 CrazyJsonString(String): class CrazyJsonString(String):
def to_json(self, value): def to_json(self, value):
return value + " JSON" return value + " JSON"
class TestFields(object): class TestFields(object):
# Will be returned by editable_metadata_fields. # Will be returned by editable_metadata_fields.
max_attempts = StringyInteger(scope=Scope.settings, default=1000, values={'min': 1 , 'max' : 10}) max_attempts = StringyInteger(scope=Scope.settings, default=1000, values={'min': 1, 'max': 10})
# Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields. # Will not be returned by editable_metadata_fields because filtered out by non_editable_metadata_fields.
due = Date(scope=Scope.settings) 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.
display_name = String(scope=Scope.settings, default='local default', display_name = 'Local Display Name', display_name = String(scope=Scope.settings, default='local default', display_name='Local Display Name',
help='local help') help='local help')
# Used for testing select type, effect of to_json method # Used for testing select type, effect of to_json method
string_select = CrazyJsonString(scope=Scope.settings, default='default value', string_select = CrazyJsonString(
values=[{'display_name' : 'first', 'value' : 'value a'}, scope=Scope.settings,
{'display_name' : 'second','value' : 'value b'}]) default='default value',
values=[{'display_name': 'first', 'value': 'value a'},
{'display_name': 'second', 'value': 'value b'}]
)
# Used for testing select type # Used for testing select type
float_select = StringyFloat(scope=Scope.settings, default=.999, values=[1.23, 0.98]) float_select = StringyFloat(scope=Scope.settings, default=.999, values=[1.23, 0.98])
# Used for testing float type # Used for testing float type
float_non_select = StringyFloat(scope=Scope.settings, default=.999, values={'min': 0 , 'step' : .3}) float_non_select = StringyFloat(scope=Scope.settings, default=.999, values={'min': 0, 'step': .3})
# Used for testing that Booleans get mapped to select type # Used for testing that Booleans get mapped to select type
boolean_select = Boolean(scope=Scope.settings) boolean_select = Boolean(scope=Scope.settings)
class EditableMetadataFieldsTest(unittest.TestCase): class EditableMetadataFieldsTest(unittest.TestCase):
def test_display_name_field(self): def test_display_name_field(self):
editable_fields = self.get_xml_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.
# Also tests that xml_attributes is filtered out of XmlDescriptor. # 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.assertEqual(1, len(editable_fields), "Expected only 1 editable field for xml descriptor.")
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name, self.assert_field_values(
explicitly_set=False, inheritable=False, value=None, default_value=None) editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=False, inheritable=False, value=None, default_value=None
)
def test_override_default(self): def test_override_default(self):
# Tests that explicitly_set is correct when a value overrides the default (not inheritable). # Tests that explicitly_set is correct when a value overrides the default (not inheritable).
editable_fields = self.get_xml_editable_fields({'display_name': 'foo'}) editable_fields = self.get_xml_editable_fields({'display_name': 'foo'})
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name, self.assert_field_values(
explicitly_set=True, inheritable=False, value='foo', default_value=None) editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=True, inheritable=False, value='foo', default_value=None
)
def test_integer_field(self): def test_integer_field(self):
descriptor = self.get_descriptor({'max_attempts' : '7'}) descriptor = self.get_descriptor({'max_attempts': '7'})
editable_fields = descriptor.editable_metadata_fields editable_fields = descriptor.editable_metadata_fields
self.assertEqual(6, len(editable_fields)) self.assertEqual(6, len(editable_fields))
self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts, self.assert_field_values(
editable_fields, 'max_attempts', TestFields.max_attempts,
explicitly_set=True, inheritable=False, value=7, default_value=1000, type='Integer', explicitly_set=True, inheritable=False, value=7, default_value=1000, type='Integer',
options=TestFields.max_attempts.values) options=TestFields.max_attempts.values
self.assert_field_values(editable_fields, 'display_name', TestFields.display_name, )
explicitly_set=False, inheritable=False, value='local default', default_value='local default') self.assert_field_values(
editable_fields, 'display_name', TestFields.display_name,
explicitly_set=False, inheritable=False, value='local default', default_value='local default'
)
editable_fields = self.get_descriptor({}).editable_metadata_fields editable_fields = self.get_descriptor({}).editable_metadata_fields
self.assert_field_values(editable_fields, 'max_attempts', TestFields.max_attempts, self.assert_field_values(
editable_fields, 'max_attempts', TestFields.max_attempts,
explicitly_set=False, inheritable=False, value=1000, default_value=1000, type='Integer', explicitly_set=False, inheritable=False, value=1000, default_value=1000, type='Integer',
options=TestFields.max_attempts.values) options=TestFields.max_attempts.values
)
def test_inherited_field(self): def test_inherited_field(self):
model_val = {'display_name' : 'inherited'} model_val = {'display_name': 'inherited'}
descriptor = self.get_descriptor(model_val) descriptor = self.get_descriptor(model_val)
# Mimic an inherited value for display_name (inherited and inheritable are the same in this case). # Mimic an inherited value for display_name (inherited and inheritable are the same in this case).
descriptor._inherited_metadata = model_val descriptor._inherited_metadata = model_val
descriptor._inheritable_metadata = model_val descriptor._inheritable_metadata = model_val
editable_fields = descriptor.editable_metadata_fields editable_fields = descriptor.editable_metadata_fields
self.assert_field_values(editable_fields, 'display_name', TestFields.display_name, self.assert_field_values(
explicitly_set=False, inheritable=True, value='inherited', default_value='inherited') editable_fields, 'display_name', TestFields.display_name,
explicitly_set=False, inheritable=True, value='inherited', default_value='inherited'
)
descriptor = self.get_descriptor({'display_name' : 'explicit'}) descriptor = self.get_descriptor({'display_name': 'explicit'})
# Mimic the case where display_name WOULD have been inherited, except we explicitly set it. # Mimic the case where display_name WOULD have been inherited, except we explicitly set it.
descriptor._inheritable_metadata = {'display_name' : 'inheritable value'} descriptor._inheritable_metadata = {'display_name': 'inheritable value'}
descriptor._inherited_metadata = {} descriptor._inherited_metadata = {}
editable_fields = descriptor.editable_metadata_fields editable_fields = descriptor.editable_metadata_fields
self.assert_field_values(editable_fields, 'display_name', TestFields.display_name, self.assert_field_values(
explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value') editable_fields, 'display_name', TestFields.display_name,
explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value'
)
def test_type_and_options(self): def test_type_and_options(self):
# test_display_name_field verifies that a String field is of type "Generic". # test_display_name_field verifies that a String field is of type "Generic".
...@@ -92,23 +110,31 @@ class EditableMetadataFieldsTest(unittest.TestCase): ...@@ -92,23 +110,31 @@ class EditableMetadataFieldsTest(unittest.TestCase):
editable_fields = descriptor.editable_metadata_fields editable_fields = descriptor.editable_metadata_fields
# Tests for select # Tests for select
self.assert_field_values(editable_fields, 'string_select', TestFields.string_select, self.assert_field_values(
editable_fields, 'string_select', TestFields.string_select,
explicitly_set=False, inheritable=False, value='default value', default_value='default value', explicitly_set=False, inheritable=False, value='default value', default_value='default value',
type='Select', options=[{'display_name' : 'first', 'value' : 'value a JSON'}, type='Select', options=[{'display_name': 'first', 'value': 'value a JSON'},
{'display_name' : 'second','value' : 'value b JSON'}]) {'display_name': 'second', 'value': 'value b JSON'}]
)
self.assert_field_values(editable_fields, 'float_select', TestFields.float_select, self.assert_field_values(
editable_fields, 'float_select', TestFields.float_select,
explicitly_set=False, inheritable=False, value=.999, default_value=.999, explicitly_set=False, inheritable=False, value=.999, default_value=.999,
type='Select', options=[1.23, 0.98]) type='Select', options=[1.23, 0.98]
)
self.assert_field_values(editable_fields, 'boolean_select', TestFields.boolean_select, self.assert_field_values(
editable_fields, 'boolean_select', TestFields.boolean_select,
explicitly_set=False, inheritable=False, value=None, default_value=None, explicitly_set=False, inheritable=False, value=None, default_value=None,
type='Select', options=[{'display_name': "True", "value": True}, {'display_name': "False", "value": False}]) type='Select', options=[{'display_name': "True", "value": True}, {'display_name': "False", "value": False}]
)
# Test for float # Test for float
self.assert_field_values(editable_fields, 'float_non_select', TestFields.float_non_select, self.assert_field_values(
editable_fields, 'float_non_select', TestFields.float_non_select,
explicitly_set=False, inheritable=False, value=.999, default_value=.999, explicitly_set=False, inheritable=False, value=.999, default_value=.999,
type='Float', options={'min': 0 , 'step' : .3}) type='Float', options={'min': 0, 'step': .3}
)
# Start of helper methods # Start of helper methods
...@@ -119,7 +145,6 @@ class EditableMetadataFieldsTest(unittest.TestCase): ...@@ -119,7 +145,6 @@ class EditableMetadataFieldsTest(unittest.TestCase):
def get_descriptor(self, model_data): def get_descriptor(self, model_data):
class TestModuleDescriptor(TestFields, XmlDescriptor): class TestModuleDescriptor(TestFields, XmlDescriptor):
@property @property
def non_editable_metadata_fields(self): def non_editable_metadata_fields(self):
non_editable_fields = super(TestModuleDescriptor, self).non_editable_metadata_fields non_editable_fields = super(TestModuleDescriptor, self).non_editable_metadata_fields
......
...@@ -34,7 +34,7 @@ class WordCloudFields(object): ...@@ -34,7 +34,7 @@ class WordCloudFields(object):
"""XFields for word cloud.""" """XFields for word cloud."""
num_inputs = StringyInteger( num_inputs = StringyInteger(
display_name="Inputs", display_name="Inputs",
help="Number of text boxes for student to input words/sentences.", help="Number of text boxes available for students to input words/sentences.",
scope=Scope.settings, scope=Scope.settings,
default=5, default=5,
values = {"min" : 1 } values = {"min" : 1 }
...@@ -48,7 +48,7 @@ class WordCloudFields(object): ...@@ -48,7 +48,7 @@ class WordCloudFields(object):
) )
display_student_percents = StringyBoolean( display_student_percents = StringyBoolean(
display_name="Show Percents", display_name="Show Percents",
help="Show statistics for entered words near every word separately at the top.", help="Statistics are shown for entered words near that word.",
scope=Scope.settings, scope=Scope.settings,
default=True default=True
) )
......
...@@ -82,7 +82,7 @@ class HTMLSnippet(object): ...@@ -82,7 +82,7 @@ class HTMLSnippet(object):
class XModuleFields(object): class XModuleFields(object):
display_name = String( display_name = String(
display_name="Display Name", display_name="Display Name",
help="Specifies the name for this component. The name appears as a tooltip in the course ribbon at the top of the page.", help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings, scope=Scope.settings,
default=None default=None
) )
......
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