pep8 and documentation cleanup.
This commit is contained in:
@@ -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']
|
||||
|
||||
@@ -1,88 +1,106 @@
|
||||
#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
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user