Commit 551a1c15 by cahrens

pep8 and documentation cleanup.

parent 4920f08d
......@@ -7,11 +7,13 @@ from terrain.steps import reload_the_page
import time
@world.absorb
def create_component_instance(step, component_button_css, instance_id, expected_css):
click_new_component_button(step, component_button_css)
click_component_from_menu(instance_id, expected_css)
@world.absorb
def click_new_component_button(step, component_button_css):
step.given('I have opened a new course section in Studio')
......@@ -20,6 +22,7 @@ def click_new_component_button(step, component_button_css):
world.css_click('a.new-unit-item')
world.css_click(component_button_css)
@world.absorb
def click_component_from_menu(instance_id, expected_css):
new_instance = world.browser.find_by_id(instance_id)
......@@ -29,11 +32,13 @@ def click_component_from_menu(instance_id, expected_css):
new_instance[0].click()
assert_equal(1, len(world.css_find(expected_css)))
@world.absorb
def edit_component_and_select_settings():
world.css_click('a.edit-button')
world.css_click('#settings-mode')
@world.absorb
def verify_setting_entry(setting, display_name, value, explicitly_set):
assert_equal(display_name, setting.find_by_css('.setting-label')[0].value)
......@@ -42,13 +47,17 @@ def verify_setting_entry(setting, display_name, value, explicitly_set):
assert_equal(explicitly_set, settingClearButton.has_class('active'))
assert_equal(not explicitly_set, settingClearButton.has_class('inactive'))
@world.absorb
def verify_all_setting_entries(expected_entries):
settings = world.browser.find_by_css('.wrapper-comp-setting')
assert_equal(len(expected_entries), len(settings))
for (counter, setting) in enumerate(settings):
world.verify_setting_entry(setting, expected_entries[counter][0],
expected_entries[counter][1], expected_entries[counter][2])
world.verify_setting_entry(
setting, expected_entries[counter][0],
expected_entries[counter][1], expected_entries[counter][2]
)
@world.absorb
def save_component_and_reopen(step):
......@@ -58,6 +67,7 @@ def save_component_and_reopen(step):
reload_the_page(step)
edit_component_and_select_settings()
@world.absorb
def cancel_component(step):
world.css_click("a.cancel-button")
......@@ -65,10 +75,12 @@ def cancel_component(step):
# they are not persisted. Refresh the browser to make sure the changes were not persisted.
reload_the_page(step)
@world.absorb
def revert_setting_entry(label):
get_setting_entry(label).find_by_css('.setting-clear')[0].click()
@world.absorb
def get_setting_entry(label):
settings = world.browser.find_by_css('.wrapper-comp-setting')
......@@ -76,5 +88,3 @@ def get_setting_entry(label):
if setting.find_by_css('.setting-label')[0].value == label:
return setting
return None
......@@ -3,10 +3,14 @@
from lettuce import world, step
@step('I have created a Discussion Tag$')
def i_created_blank_common_problem(step):
world.create_component_instance(step, '.large-discussion-icon', 'i4x://edx/templates/discussion/Discussion_Tag',
'.xmodule_DiscussionModule')
world.create_component_instance(
step, '.large-discussion-icon', 'i4x://edx/templates/discussion/Discussion_Tag',
'.xmodule_DiscussionModule'
)
@step('I see three alphabetized settings and their expected values$')
def i_see_only_the_display_name(step):
......
......@@ -3,10 +3,14 @@
from lettuce import world, step
@step('I have created a Blank HTML Page$')
def i_created_blank_common_problem(step):
world.create_component_instance(step, '.large-html-icon', 'i4x://edx/templates/html/Blank_HTML_Page',
'.xmodule_HtmlModule')
world.create_component_instance(
step, '.large-html-icon', 'i4x://edx/templates/html/Blank_HTML_Page',
'.xmodule_HtmlModule'
)
@step('I see only the HTML display name setting$')
def i_see_only_the_html_display_name(step):
......
......@@ -5,21 +5,28 @@ from lettuce import world, step
from nose.tools import assert_equal
DISPLAY_NAME = "Display Name"
MAXIMUM_ATTEMPTS="Maximum Attempts"
PROBLEM_WEIGHT="Problem Weight"
RANDOMIZATION='Randomization'
SHOW_ANSWER="Show Answer"
MAXIMUM_ATTEMPTS = "Maximum Attempts"
PROBLEM_WEIGHT = "Problem Weight"
RANDOMIZATION = 'Randomization'
SHOW_ANSWER = "Show Answer"
############### ACTIONS ####################
@step('I have created a Blank Common Problem$')
def i_created_blank_common_problem(step):
world.create_component_instance(step, '.large-problem-icon', 'i4x://edx/templates/problem/Blank_Common_Problem',
'.xmodule_CapaModule')
world.create_component_instance(
step,
'.large-problem-icon',
'i4x://edx/templates/problem/Blank_Common_Problem',
'.xmodule_CapaModule'
)
@step('I edit and select Settings$')
def i_edit_and_select_settings(step):
world.edit_component_and_select_settings()
@step('I see five alphabetized settings and their expected values$')
def i_see_five_settings_with_values(step):
world.verify_all_setting_entries(
......@@ -31,105 +38,124 @@ def i_see_five_settings_with_values(step):
[SHOW_ANSWER, "Finished", True]
])
@step('I can modify the display name')
def i_can_modify_the_display_name(step):
world.get_setting_entry(DISPLAY_NAME).find_by_css('.setting-input')[0].fill('modified')
verify_modified_display_name()
@step('my display name change is persisted on save')
def my_display_name_change_is_persisted_on_save(step):
world.save_component_and_reopen(step)
verify_modified_display_name()
@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()
@step('I can revert the display name to unset')
def can_revert_display_name_to_unset(step):
world.revert_setting_entry(DISPLAY_NAME)
verify_unset_display_name()
@step('my display name is unset on save')
def my_display_name_is_persisted_on_save(step):
world.save_component_and_reopen(step)
verify_unset_display_name()
@step('I can select Per Student for Randomization')
def i_can_select_per_student_for_randomization(step):
world.browser.select(RANDOMIZATION, "Per Student")
verify_modified_randomization()
@step('my change to randomization is persisted')
def my_change_to_randomization_is_persisted(step):
world.save_component_and_reopen(step)
verify_modified_randomization()
@step('I can revert to the default value for randomization')
def i_can_revert_to_default_for_randomization(step):
world.revert_setting_entry(RANDOMIZATION)
world.save_component_and_reopen(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')
def i_can_set_weight_to_3_5(step):
world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill('3.5')
verify_modified_weight()
@step('my change to weight is persisted')
def my_change_to_randomization_is_persisted(step):
world.save_component_and_reopen(step)
verify_modified_weight()
@step('I can revert to the default value of unset for weight')
def i_can_revert_to_default_for_randomization(step):
world.revert_setting_entry(PROBLEM_WEIGHT)
world.save_component_and_reopen(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')
def set_the_weight_to_abc(step):
world.get_setting_entry(PROBLEM_WEIGHT).find_by_css('.setting-input')[0].fill('abc')
# We show the clear button immediately on type, hence the "True" here.
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", True)
world.verify_setting_entry(world.get_setting_entry(PROBLEM_WEIGHT), PROBLEM_WEIGHT, "", True)
world.save_component_and_reopen(step)
# But no change was actually ever sent to the model, so on reopen, explicitly_set is False
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')
def set_the_weight_to_abc(step):
world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill('2.34')
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)
world.save_component_and_reopen(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')
def set_max_attempts_to_neg_3(step):
world.get_setting_entry(MAXIMUM_ATTEMPTS).find_by_css('.setting-input')[0].fill('-3')
world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "-3", True)
world.verify_setting_entry(world.get_setting_entry(MAXIMUM_ATTEMPTS), MAXIMUM_ATTEMPTS, "-3", True)
world.save_component_and_reopen(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')
def edit_high_level_source_not_visible(step):
verify_high_level_source(step, False)
@step('Edit High Level Source is visible')
def edit_high_level_source_visible(step):
verify_high_level_source(step, True)
@step('If I press Cancel my changes are not persisted')
def cancel_does_not_save_changes(step):
world.cancel_component(step)
step.given("I edit and select Settings")
step.given("I see five alphabetized settings and their expected values")
@step('I have created a LaTeX Problem')
def create_latex_problem(step):
world.click_new_component_button(step, '.large-problem-icon')
......@@ -138,23 +164,28 @@ def create_latex_problem(step):
world.css_click('#ui-id-2')
world.click_component_from_menu("i4x://edx/templates/problem/Problem_Written_in_LaTeX", '.xmodule_CapaModule')
def verify_high_level_source(step, visible):
assert_equal(visible, world.is_css_present('.launch-latex-compiler'))
world.cancel_component(step)
assert_equal(visible, world.is_css_present('.upload-button'))
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():
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():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, 'modified', True)
def verify_modified_display_name_with_special_chars():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, "updated ' \" &", True)
def verify_unset_display_name():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, '', False)
......@@ -3,10 +3,14 @@
from lettuce import world, step
@step('I have created a Video component$')
def i_created_a_video_component(step):
world.create_component_instance(step, '.large-video-icon', 'i4x://edx/templates/video/default',
'.xmodule_VideoModule')
world.create_component_instance(
step, '.large-video-icon', 'i4x://edx/templates/video/default',
'.xmodule_VideoModule'
)
@step('I see only the video display name setting$')
def i_see_only_the_video_display_name(step):
......
......@@ -66,12 +66,15 @@ 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(display_name="Maximum Attempts",
max_attempts = StringyInteger(
display_name="Maximum Attempts",
help="This specifies the number of times the student can try to answer this problem. If unset, infinite attempts are allowed.",
values = {"min" : 1 }, scope=Scope.settings)
values = {"min" : 1 }, 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(display_name="Show Answer",
showanswer = String(
display_name="Show Answer",
help="Specifies when to show the answer to this problem. A default value can be set course-wide in Advanced Settings.",
scope=Scope.settings, default="closed",
values=[
......@@ -81,26 +84,33 @@ class CapaFields(object):
{"display_name": "Closed", "value": "closed"},
{"display_name": "Finished", "value": "finished"},
{"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)
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="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.",
default="always", scope=Scope.settings, values=[{"display_name": "Always", "value": "always"},
{"display_name": "On Reset", "value": "onreset"},
{"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)
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(display_name="Problem Weight",
weight = StringyFloat(
display_name="Problem Weight",
help="Specifies the number of points the problem is worth. If unset, each response field in the problem is worth one point.",
values = {"min" : 0 , "step": .1},
scope=Scope.settings)
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.",
scope=Scope.settings)
source_code = String(
help="Source code for LaTeX and Word problems. This feature is not well-supported.",
scope=Scope.settings
)
class CapaModule(CapaFields, XModule):
......
......@@ -55,26 +55,38 @@ class CombinedOpenEndedFields(object):
scope=Scope.user_state)
student_attempts = StringyInteger(help="Number of attempts taken by the student on this problem", default=0,
scope=Scope.user_state)
ready_to_reset = StringyBoolean(help="If the problem is ready to be reset or not.", default=False,
scope=Scope.user_state)
attempts = StringyInteger(display_name="Maximum Attempts",
ready_to_reset = StringyBoolean(
help="If the problem is ready to be reset or not.", default=False,
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,
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)
accept_file_upload = StringyBoolean(display_name="Allow File Uploads",
help="Whether or not the student can submit files as a response.", default=False, scope=Scope.settings)
skip_spelling_checks = StringyBoolean(display_name="Disable Quality Filter",
# 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.",
default=False, scope=Scope.settings)
accept_file_upload = StringyBoolean(
display_name="Allow File Uploads",
help="Whether or not the student can submit files as a response.", default=False, scope=Scope.settings
)
skip_spelling_checks = StringyBoolean(
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)
graceperiod = String(help="Amount of time after the due date that submissions will be accepted", default=None,
scope=Scope.settings)
graceperiod = String(
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)
data = String(help="XML data for the problem", scope=Scope.content)
weight = StringyFloat(display_name="Problem Weight",
weight = StringyFloat(
display_name="Problem Weight",
help="The number of points the problem is worth. By default, each problem is worth one point.",
scope=Scope.settings, values = {"min" : 0 , "step": ".1"})
scope=Scope.settings, values = {"min" : 0 , "step": ".1"}
)
markdown = String(help="Markdown source of this module", scope=Scope.settings)
......
......@@ -8,12 +8,16 @@ from xblock.core import String, Scope
class DiscussionFields(object):
discussion_id = String(scope=Scope.settings)
discussion_category = String(display_name="Category",
discussion_category = String(
display_name="Category",
help="Specifies a category 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",
scope=Scope.settings
)
discussion_target = String(
display_name="Subcategory",
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)
scope=Scope.settings
)
sort_key = String(scope=Scope.settings)
......
......@@ -28,25 +28,37 @@ EXTERNAL_GRADER_NO_CONTACT_ERROR = "Failed to contact external graders. Please
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. '
'When False, a panel is displayed with all problems available for peer grading.',
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings)
link_to_location = String(display_name="Link to Problem Location",
default=USE_FOR_SINGLE_LOCATION, scope=Scope.settings
)
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.',
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.',
default=IS_GRADED, scope=Scope.settings)
default=LINK_TO_LOCATION, scope=Scope.settings
)
is_graded = StringyBoolean(
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)
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,
scope=Scope.settings, values={"min" : 0 })
student_data_for_location = Object(help="Student data for a given peer grading problem.",
scope=Scope.user_state)
weight = StringyFloat(display_name="Problem Weight",
max_grade = StringyInteger(
help="The maximum grade that a student can receive for this problem.", default=MAX_SCORE,
scope=Scope.settings, values={"min": 0}
)
student_data_for_location = Object(
help="Student data for a given peer grading problem.",
scope=Scope.user_state
)
weight = StringyFloat(
display_name="Problem Weight",
help="Specifies the number of points the problem is worth. By default, each problem is worth one point.",
scope=Scope.settings, values = {"min" : 0 , "step": ".1"})
scope=Scope.settings, values={"min": 0, "step": ".1"}
)
class PeerGradingModule(PeerGradingFields, XModule):
......@@ -299,14 +311,14 @@ class PeerGradingModule(PeerGradingFields, XModule):
try:
response = self.peer_gs.save_grade(location, grader_id, submission_id,
score, feedback, submission_key, rubric_scores, submission_flagged)
score, feedback, submission_key, rubric_scores, submission_flagged)
return response
except GradingServiceError:
#This is a dev_facing_error
log.exception("""Error saving grade to open ended grading service. server url: {0}, location: {1}, submission_id:{2},
submission_key: {3}, score: {4}"""
.format(self.peer_gs.url,
location, submission_id, submission_key, score)
location, submission_id, submission_key, score)
)
#This is a student_facing_error
return {
......@@ -437,7 +449,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
try:
response = self.peer_gs.save_calibration_essay(location, grader_id, calibration_essay_id,
submission_key, score, feedback, rubric_scores)
submission_key, score, feedback, rubric_scores)
if 'actual_rubric' in response:
rubric_renderer = combined_open_ended_rubric.CombinedOpenEndedRubric(self.system, True)
response['actual_rubric'] = rubric_renderer.render_rubric(response['actual_rubric'])['html']
......
#pylint: disable=C0111
#pylint: disable=W0621
from xmodule.x_module import XModuleFields
from xblock.core import Scope, String, Object, Boolean
from xmodule.fields import Date, StringyInteger, StringyFloat
from xmodule.xml_module import XmlDescriptor
import unittest
from . import test_system
from .import test_system
from mock import Mock
class CrazyJsonString(String):
def to_json(self, value):
return value + " JSON"
class TestFields(object):
# 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.
due = Date(scope=Scope.settings)
# Will not be returned by editable_metadata_fields because is not Scope.settings.
student_answers = Object(scope=Scope.user_state)
# Will be returned, and can override the inherited value from XModule.
display_name = String(scope=Scope.settings, default='local default', display_name = 'Local Display Name',
help='local help')
display_name = String(scope=Scope.settings, default='local default', display_name='Local Display Name',
help='local help')
# Used for testing select type, effect of to_json method
string_select = CrazyJsonString(scope=Scope.settings, default='default value',
values=[{'display_name' : 'first', 'value' : 'value a'},
{'display_name' : 'second','value' : 'value b'}])
string_select = CrazyJsonString(
scope=Scope.settings,
default='default value',
values=[{'display_name': 'first', 'value': 'value a'},
{'display_name': 'second', 'value': 'value b'}]
)
# Used for testing select type
float_select = StringyFloat(scope=Scope.settings, default=.999, values=[1.23, 0.98])
# 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
boolean_select = Boolean(scope=Scope.settings)
class EditableMetadataFieldsTest(unittest.TestCase):
def test_display_name_field(self):
editable_fields = self.get_xml_editable_fields({})
# Tests that the xblock fields (currently tags and name) get filtered out.
# Also tests that xml_attributes is filtered out of XmlDescriptor.
self.assertEqual(1, len(editable_fields), "Expected only 1 editable field for xml descriptor.")
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=False, inheritable=False, value=None, default_value=None)
self.assert_field_values(
editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=False, inheritable=False, value=None, default_value=None
)
def test_override_default(self):
# Tests that explicitly_set is correct when a value overrides the default (not inheritable).
editable_fields = self.get_xml_editable_fields({'display_name': 'foo'})
self.assert_field_values(editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=True, inheritable=False, value='foo', default_value=None)
self.assert_field_values(
editable_fields, 'display_name', XModuleFields.display_name,
explicitly_set=True, inheritable=False, value='foo', default_value=None
)
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
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',
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')
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'
)
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',
options=TestFields.max_attempts.values)
options=TestFields.max_attempts.values
)
def test_inherited_field(self):
model_val = {'display_name' : 'inherited'}
model_val = {'display_name': 'inherited'}
descriptor = self.get_descriptor(model_val)
# Mimic an inherited value for display_name (inherited and inheritable are the same in this case).
descriptor._inherited_metadata = model_val
descriptor._inheritable_metadata = model_val
editable_fields = descriptor.editable_metadata_fields
self.assert_field_values(editable_fields, 'display_name', TestFields.display_name,
explicitly_set=False, inheritable=True, value='inherited', default_value='inherited')
self.assert_field_values(
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.
descriptor._inheritable_metadata = {'display_name' : 'inheritable value'}
descriptor._inheritable_metadata = {'display_name': 'inheritable value'}
descriptor._inherited_metadata = {}
editable_fields = descriptor.editable_metadata_fields
self.assert_field_values(editable_fields, 'display_name', TestFields.display_name,
explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value')
self.assert_field_values(
editable_fields, 'display_name', TestFields.display_name,
explicitly_set=True, inheritable=True, value='explicit', default_value='inheritable value'
)
def test_type_and_options(self):
# test_display_name_field verifies that a String field is of type "Generic".
......@@ -92,23 +110,31 @@ class EditableMetadataFieldsTest(unittest.TestCase):
editable_fields = descriptor.editable_metadata_fields
# 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',
type='Select', options=[{'display_name' : 'first', 'value' : 'value a JSON'},
{'display_name' : 'second','value' : 'value b JSON'}])
type='Select', options=[{'display_name': 'first', 'value': 'value a 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,
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,
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
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,
type='Float', options={'min': 0 , 'step' : .3})
type='Float', options={'min': 0, 'step': .3}
)
# Start of helper methods
......@@ -119,7 +145,6 @@ class EditableMetadataFieldsTest(unittest.TestCase):
def get_descriptor(self, model_data):
class TestModuleDescriptor(TestFields, XmlDescriptor):
@property
def non_editable_metadata_fields(self):
non_editable_fields = super(TestModuleDescriptor, self).non_editable_metadata_fields
......
......@@ -82,7 +82,7 @@ class HTMLSnippet(object):
class XModuleFields(object):
display_name = String(
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="Specifies the name for this component. The name appears in the course navigation at the top of the page.",
scope=Scope.settings,
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