Commit 551a1c15 by cahrens

pep8 and documentation cleanup.

parent 4920f08d
...@@ -7,11 +7,13 @@ from terrain.steps import reload_the_page ...@@ -7,11 +7,13 @@ from terrain.steps import reload_the_page
import time import time
@world.absorb @world.absorb
def create_component_instance(step, component_button_css, instance_id, expected_css): def create_component_instance(step, component_button_css, instance_id, expected_css):
click_new_component_button(step, component_button_css) click_new_component_button(step, component_button_css)
click_component_from_menu(instance_id, expected_css) click_component_from_menu(instance_id, expected_css)
@world.absorb @world.absorb
def click_new_component_button(step, component_button_css): def click_new_component_button(step, component_button_css):
step.given('I have opened a new course section in Studio') step.given('I have opened a new course section in Studio')
...@@ -20,6 +22,7 @@ def click_new_component_button(step, component_button_css): ...@@ -20,6 +22,7 @@ def click_new_component_button(step, component_button_css):
world.css_click('a.new-unit-item') world.css_click('a.new-unit-item')
world.css_click(component_button_css) world.css_click(component_button_css)
@world.absorb @world.absorb
def click_component_from_menu(instance_id, expected_css): def click_component_from_menu(instance_id, expected_css):
new_instance = world.browser.find_by_id(instance_id) new_instance = world.browser.find_by_id(instance_id)
...@@ -29,11 +32,13 @@ def click_component_from_menu(instance_id, expected_css): ...@@ -29,11 +32,13 @@ def click_component_from_menu(instance_id, expected_css):
new_instance[0].click() new_instance[0].click()
assert_equal(1, len(world.css_find(expected_css))) assert_equal(1, len(world.css_find(expected_css)))
@world.absorb @world.absorb
def edit_component_and_select_settings(): def edit_component_and_select_settings():
world.css_click('a.edit-button') world.css_click('a.edit-button')
world.css_click('#settings-mode') world.css_click('#settings-mode')
@world.absorb @world.absorb
def verify_setting_entry(setting, display_name, value, explicitly_set): def verify_setting_entry(setting, display_name, value, explicitly_set):
assert_equal(display_name, setting.find_by_css('.setting-label')[0].value) 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): ...@@ -42,13 +47,17 @@ def verify_setting_entry(setting, display_name, value, explicitly_set):
assert_equal(explicitly_set, settingClearButton.has_class('active')) assert_equal(explicitly_set, settingClearButton.has_class('active'))
assert_equal(not explicitly_set, settingClearButton.has_class('inactive')) assert_equal(not explicitly_set, settingClearButton.has_class('inactive'))
@world.absorb @world.absorb
def verify_all_setting_entries(expected_entries): 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
def save_component_and_reopen(step): def save_component_and_reopen(step):
...@@ -58,6 +67,7 @@ def save_component_and_reopen(step): ...@@ -58,6 +67,7 @@ def save_component_and_reopen(step):
reload_the_page(step) reload_the_page(step)
edit_component_and_select_settings() edit_component_and_select_settings()
@world.absorb @world.absorb
def cancel_component(step): def cancel_component(step):
world.css_click("a.cancel-button") world.css_click("a.cancel-button")
...@@ -65,10 +75,12 @@ def cancel_component(step): ...@@ -65,10 +75,12 @@ def cancel_component(step):
# they are not persisted. Refresh the browser to make sure the changes were not persisted. # they are not persisted. Refresh the browser to make sure the changes were not persisted.
reload_the_page(step) reload_the_page(step)
@world.absorb @world.absorb
def revert_setting_entry(label): def revert_setting_entry(label):
get_setting_entry(label).find_by_css('.setting-clear')[0].click() get_setting_entry(label).find_by_css('.setting-clear')[0].click()
@world.absorb @world.absorb
def get_setting_entry(label): def get_setting_entry(label):
settings = world.browser.find_by_css('.wrapper-comp-setting') settings = world.browser.find_by_css('.wrapper-comp-setting')
...@@ -76,5 +88,3 @@ def get_setting_entry(label): ...@@ -76,5 +88,3 @@ def get_setting_entry(label):
if setting.find_by_css('.setting-label')[0].value == label: if setting.find_by_css('.setting-label')[0].value == label:
return setting return setting
return None return None
...@@ -3,10 +3,14 @@ ...@@ -3,10 +3,14 @@
from lettuce import world, step from lettuce import world, step
@step('I have created a Discussion Tag$') @step('I have created a Discussion Tag$')
def i_created_blank_common_problem(step): def i_created_blank_common_problem(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_display_name(step):
......
...@@ -3,10 +3,14 @@ ...@@ -3,10 +3,14 @@
from lettuce import world, step 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_common_problem(step): def i_created_blank_common_problem(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$')
def i_see_only_the_html_display_name(step): def i_see_only_the_html_display_name(step):
......
...@@ -5,21 +5,28 @@ from lettuce import world, step ...@@ -5,21 +5,28 @@ from lettuce import world, step
from nose.tools import assert_equal from nose.tools import assert_equal
DISPLAY_NAME = "Display Name" DISPLAY_NAME = "Display Name"
MAXIMUM_ATTEMPTS="Maximum Attempts" MAXIMUM_ATTEMPTS = "Maximum Attempts"
PROBLEM_WEIGHT="Problem Weight" PROBLEM_WEIGHT = "Problem Weight"
RANDOMIZATION='Randomization' RANDOMIZATION = 'Randomization'
SHOW_ANSWER="Show Answer" 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):
world.create_component_instance(step, '.large-problem-icon', 'i4x://edx/templates/problem/Blank_Common_Problem', world.create_component_instance(
'.xmodule_CapaModule') step,
'.large-problem-icon',
'i4x://edx/templates/problem/Blank_Common_Problem',
'.xmodule_CapaModule'
)
@step('I edit and select Settings$') @step('I edit and select Settings$')
def i_edit_and_select_settings(step): def i_edit_and_select_settings(step):
world.edit_component_and_select_settings() world.edit_component_and_select_settings()
@step('I see five alphabetized settings and their expected values$') @step('I see five alphabetized settings and their expected values$')
def i_see_five_settings_with_values(step): def i_see_five_settings_with_values(step):
world.verify_all_setting_entries( world.verify_all_setting_entries(
...@@ -31,105 +38,124 @@ def i_see_five_settings_with_values(step): ...@@ -31,105 +38,124 @@ def i_see_five_settings_with_values(step):
[SHOW_ANSWER, "Finished", True] [SHOW_ANSWER, "Finished", True]
]) ])
@step('I can modify the display name') @step('I can modify the display name')
def i_can_modify_the_display_name(step): def i_can_modify_the_display_name(step):
world.get_setting_entry(DISPLAY_NAME).find_by_css('.setting-input')[0].fill('modified') world.get_setting_entry(DISPLAY_NAME).find_by_css('.setting-input')[0].fill('modified')
verify_modified_display_name() verify_modified_display_name()
@step('my display name change is persisted on save') @step('my display name change is persisted on save')
def my_display_name_change_is_persisted_on_save(step): def my_display_name_change_is_persisted_on_save(step):
world.save_component_and_reopen(step) world.save_component_and_reopen(step)
verify_modified_display_name() verify_modified_display_name()
@step('I can specify special characters in the display name') @step('I can specify special characters in the display name')
def i_can_modify_the_display_name_with_special_chars(step): 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 ' \" &") world.get_setting_entry(DISPLAY_NAME).find_by_css('.setting-input')[0].fill("updated ' \" &")
verify_modified_display_name_with_special_chars() verify_modified_display_name_with_special_chars()
@step('my special characters and persisted on save') @step('my special characters and persisted on save')
def special_chars_persisted_on_save(step): def special_chars_persisted_on_save(step):
world.save_component_and_reopen(step) world.save_component_and_reopen(step)
verify_modified_display_name_with_special_chars() verify_modified_display_name_with_special_chars()
@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)
verify_unset_display_name() verify_unset_display_name()
@step('my display name is unset on save') @step('my display name is unset on save')
def my_display_name_is_persisted_on_save(step): def my_display_name_is_persisted_on_save(step):
world.save_component_and_reopen(step) world.save_component_and_reopen(step)
verify_unset_display_name() verify_unset_display_name()
@step('I can select Per Student for Randomization') @step('I can select Per Student for Randomization')
def i_can_select_per_student_for_randomization(step): def i_can_select_per_student_for_randomization(step):
world.browser.select(RANDOMIZATION, "Per Student") world.browser.select(RANDOMIZATION, "Per Student")
verify_modified_randomization() verify_modified_randomization()
@step('my change to randomization is persisted') @step('my change to randomization is persisted')
def my_change_to_randomization_is_persisted(step): def my_change_to_randomization_is_persisted(step):
world.save_component_and_reopen(step) world.save_component_and_reopen(step)
verify_modified_randomization() verify_modified_randomization()
@step('I can revert to the default value for randomization') @step('I can revert to the default value for randomization')
def i_can_revert_to_default_for_randomization(step): def i_can_revert_to_default_for_randomization(step):
world.revert_setting_entry(RANDOMIZATION) world.revert_setting_entry(RANDOMIZATION)
world.save_component_and_reopen(step) 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') @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')
verify_modified_weight() verify_modified_weight()
@step('my change to weight is persisted') @step('my change to weight is persisted')
def my_change_to_randomization_is_persisted(step): def my_change_to_randomization_is_persisted(step):
world.save_component_and_reopen(step) world.save_component_and_reopen(step)
verify_modified_weight() verify_modified_weight()
@step('I can revert to the default value of unset for weight') @step('I can revert to the default value of unset for weight')
def i_can_revert_to_default_for_randomization(step): def i_can_revert_to_default_for_randomization(step):
world.revert_setting_entry(PROBLEM_WEIGHT) world.revert_setting_entry(PROBLEM_WEIGHT)
world.save_component_and_reopen(step) 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') @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')
# We show the clear button immediately on type, hence the "True" here. # 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) world.save_component_and_reopen(step)
# But no change was actually ever sent to the model, so on reopen, explicitly_set is False # 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') @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')
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.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') @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')
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.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') @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)
@step('Edit High Level Source is visible') @step('Edit High Level Source is visible')
def edit_high_level_source_visible(step): def edit_high_level_source_visible(step):
verify_high_level_source(step, True) verify_high_level_source(step, True)
@step('If I press Cancel my changes are not persisted') @step('If I press Cancel my changes are not persisted')
def cancel_does_not_save_changes(step): def cancel_does_not_save_changes(step):
world.cancel_component(step) world.cancel_component(step)
step.given("I edit and select Settings") step.given("I edit and select Settings")
step.given("I see five alphabetized settings and their expected values") step.given("I see five alphabetized settings and their expected values")
@step('I have created a LaTeX Problem') @step('I have created a LaTeX Problem')
def create_latex_problem(step): def create_latex_problem(step):
world.click_new_component_button(step, '.large-problem-icon') world.click_new_component_button(step, '.large-problem-icon')
...@@ -138,23 +164,28 @@ def create_latex_problem(step): ...@@ -138,23 +164,28 @@ def create_latex_problem(step):
world.css_click('#ui-id-2') world.css_click('#ui-id-2')
world.click_component_from_menu("i4x://edx/templates/problem/Problem_Written_in_LaTeX", '.xmodule_CapaModule') world.click_component_from_menu("i4x://edx/templates/problem/Problem_Written_in_LaTeX", '.xmodule_CapaModule')
def verify_high_level_source(step, visible): def verify_high_level_source(step, visible):
assert_equal(visible, world.is_css_present('.launch-latex-compiler')) assert_equal(visible, world.is_css_present('.launch-latex-compiler'))
world.cancel_component(step) world.cancel_component(step)
assert_equal(visible, world.is_css_present('.upload-button')) assert_equal(visible, world.is_css_present('.upload-button'))
def verify_modified_weight(): 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)
def verify_modified_display_name_with_special_chars(): def verify_modified_display_name_with_special_chars():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, "updated ' \" &", True) world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, "updated ' \" &", True)
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,14 @@ ...@@ -3,10 +3,14 @@
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):
......
...@@ -66,12 +66,15 @@ class ComplexEncoder(json.JSONEncoder): ...@@ -66,12 +66,15 @@ 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(
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.", 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) 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(
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.", 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", scope=Scope.settings, default="closed",
values=[ values=[
...@@ -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="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"}, 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(
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.", 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}, 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):
......
...@@ -55,26 +55,38 @@ class CombinedOpenEndedFields(object): ...@@ -55,26 +55,38 @@ class CombinedOpenEndedFields(object):
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(
display_name="Problem Weight",
help="The number of points the problem is worth. By default, each problem is worth one point.", 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) 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(
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.", 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) scope=Scope.settings
discussion_target = String(display_name="Subcategory", )
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.", 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) 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="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.", 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): class PeerGradingModule(PeerGradingFields, XModule):
...@@ -299,14 +311,14 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -299,14 +311,14 @@ class PeerGradingModule(PeerGradingFields, XModule):
try: try:
response = self.peer_gs.save_grade(location, grader_id, submission_id, 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 return response
except GradingServiceError: except GradingServiceError:
#This is a dev_facing_error #This is a dev_facing_error
log.exception("""Error saving grade to open ended grading service. server url: {0}, location: {1}, submission_id:{2}, log.exception("""Error saving grade to open ended grading service. server url: {0}, location: {1}, submission_id:{2},
submission_key: {3}, score: {4}""" submission_key: {3}, score: {4}"""
.format(self.peer_gs.url, .format(self.peer_gs.url,
location, submission_id, submission_key, score) location, submission_id, submission_key, score)
) )
#This is a student_facing_error #This is a student_facing_error
return { return {
...@@ -437,7 +449,7 @@ class PeerGradingModule(PeerGradingFields, XModule): ...@@ -437,7 +449,7 @@ class PeerGradingModule(PeerGradingFields, XModule):
try: try:
response = self.peer_gs.save_calibration_essay(location, grader_id, calibration_essay_id, 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: if 'actual_rubric' in response:
rubric_renderer = combined_open_ended_rubric.CombinedOpenEndedRubric(self.system, True) rubric_renderer = combined_open_ended_rubric.CombinedOpenEndedRubric(self.system, True)
response['actual_rubric'] = rubric_renderer.render_rubric(response['actual_rubric'])['html'] response['actual_rubric'] = rubric_renderer.render_rubric(response['actual_rubric'])['html']
......
#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
......
...@@ -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="Specifies the name for this component. The name appears in the course 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