Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
3d1c54fe
Commit
3d1c54fe
authored
Oct 24, 2014
by
Sarina Canelake
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5595 from Stanford-Online/caijim/resetbutton
Support and tests for adding a reset button to units
parents
98c9880d
6d19a0c8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
344 additions
and
135 deletions
+344
-135
CHANGELOG.rst
+2
-0
cms/djangoapps/contentstore/features/problem-editor.py
+2
-0
common/lib/xmodule/xmodule/capa_base.py
+54
-42
common/lib/xmodule/xmodule/capa_base_constants.py
+28
-0
common/lib/xmodule/xmodule/modulestore/inheritance.py
+13
-2
common/lib/xmodule/xmodule/tests/test_capa_module.py
+135
-67
lms/djangoapps/courseware/features/problems.feature
+98
-24
lms/djangoapps/courseware/features/problems.py
+12
-0
No files found.
CHANGELOG.rst
View file @
3d1c54fe
...
...
@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected.
Common: Add configurable reset button to units
LMS: Support adding cohorts from the instructor dashboard. TNL-162
LMS: Support adding students to a cohort via the instructor dashboard. TNL-163
...
...
cms/djangoapps/contentstore/features/problem-editor.py
View file @
3d1c54fe
...
...
@@ -13,6 +13,7 @@ MAXIMUM_ATTEMPTS = "Maximum Attempts"
PROBLEM_WEIGHT
=
"Problem Weight"
RANDOMIZATION
=
'Randomization'
SHOW_ANSWER
=
"Show Answer"
SHOW_RESET_BUTTON
=
"Show Reset Button"
TIMER_BETWEEN_ATTEMPTS
=
"Timer Between Attempts"
MATLAB_API_KEY
=
"Matlab API key"
...
...
@@ -102,6 +103,7 @@ def i_see_advanced_settings_with_values(step):
[
PROBLEM_WEIGHT
,
""
,
False
],
[
RANDOMIZATION
,
"Never"
,
False
],
[
SHOW_ANSWER
,
"Finished"
,
False
],
[
SHOW_RESET_BUTTON
,
"False"
,
False
],
[
TIMER_BETWEEN_ATTEMPTS
,
"0"
,
False
],
])
...
...
common/lib/xmodule/xmodule/capa_base.py
View file @
3d1c54fe
...
...
@@ -29,6 +29,8 @@ from xblock.fields import Scope, String, Boolean, Dict, Integer, Float
from
.fields
import
Timedelta
,
Date
from
django.utils.timezone
import
UTC
from
.util.duedate
import
get_extended_due_date
from
xmodule.capa_base_constants
import
RANDOMIZATION
,
SHOWANSWER
from
django.conf
import
settings
log
=
logging
.
getLogger
(
"edx.courseware"
)
...
...
@@ -63,9 +65,9 @@ class Randomization(String):
"""
def
from_json
(
self
,
value
):
if
value
in
(
""
,
"true"
):
return
"always"
return
RANDOMIZATION
.
ALWAYS
elif
value
==
"false"
:
return
"per_student"
return
RANDOMIZATION
.
PER_STUDENT
return
value
to_json
=
from_json
...
...
@@ -124,34 +126,43 @@ class CapaFields(object):
help
=
_
(
"Defines when to show the answer to the problem. "
"A default value can be set in Advanced Settings."
),
scope
=
Scope
.
settings
,
default
=
"finished"
,
default
=
SHOWANSWER
.
FINISHED
,
values
=
[
{
"display_name"
:
_
(
"Always"
),
"value"
:
"always"
},
{
"display_name"
:
_
(
"Answered"
),
"value"
:
"answered"
},
{
"display_name"
:
_
(
"Attempted"
),
"value"
:
"attempted"
},
{
"display_name"
:
_
(
"Closed"
),
"value"
:
"closed"
},
{
"display_name"
:
_
(
"Finished"
),
"value"
:
"finished"
},
{
"display_name"
:
_
(
"Correct or Past Due"
),
"value"
:
"correct_or_past_due"
},
{
"display_name"
:
_
(
"Past Due"
),
"value"
:
"past_due"
},
{
"display_name"
:
_
(
"Never"
),
"value"
:
"never"
}]
{
"display_name"
:
_
(
"Always"
),
"value"
:
SHOWANSWER
.
ALWAYS
},
{
"display_name"
:
_
(
"Answered"
),
"value"
:
SHOWANSWER
.
ANSWERED
},
{
"display_name"
:
_
(
"Attempted"
),
"value"
:
SHOWANSWER
.
ATTEMPTED
},
{
"display_name"
:
_
(
"Closed"
),
"value"
:
SHOWANSWER
.
CLOSED
},
{
"display_name"
:
_
(
"Finished"
),
"value"
:
SHOWANSWER
.
FINISHED
},
{
"display_name"
:
_
(
"Correct or Past Due"
),
"value"
:
SHOWANSWER
.
CORRECT_OR_PAST_DUE
},
{
"display_name"
:
_
(
"Past Due"
),
"value"
:
SHOWANSWER
.
PAST_DUE
},
{
"display_name"
:
_
(
"Never"
),
"value"
:
SHOWANSWER
.
NEVER
}]
)
force_save_button
=
Boolean
(
help
=
_
(
"Whether to force the save button to appear on the page"
),
scope
=
Scope
.
settings
,
default
=
False
)
reset_key
=
"DEFAULT_SHOW_RESET_BUTTON"
default_reset_button
=
getattr
(
settings
,
reset_key
)
if
hasattr
(
settings
,
reset_key
)
else
False
show_reset_button
=
Boolean
(
display_name
=
_
(
"Show Reset Button"
),
help
=
_
(
"Determines whether a 'Reset' button is shown so the user may reset their answer. "
"A default value can be set in Advanced Settings."
),
scope
=
Scope
.
settings
,
default
=
default_reset_button
)
rerandomize
=
Randomization
(
display_name
=
_
(
"Randomization"
),
help
=
_
(
"Defines how often inputs are randomized when a student loads the problem. "
"This setting only applies to problems that can have randomly generated numeric values. "
"A default value can be set in Advanced Settings."
),
default
=
"never"
,
default
=
RANDOMIZATION
.
NEVER
,
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"
:
_
(
"Always"
),
"value"
:
RANDOMIZATION
.
ALWAYS
},
{
"display_name"
:
_
(
"On Reset"
),
"value"
:
RANDOMIZATION
.
ONRESET
},
{
"display_name"
:
_
(
"Never"
),
"value"
:
RANDOMIZATION
.
NEVER
},
{
"display_name"
:
_
(
"Per Student"
),
"value"
:
RANDOMIZATION
.
PER_STUDENT
}
]
)
data
=
String
(
help
=
_
(
"XML data for the problem"
),
scope
=
Scope
.
content
,
default
=
"<problem></problem>"
)
...
...
@@ -274,9 +285,9 @@ class CapaMixin(CapaFields):
"""
Choose a new seed.
"""
if
self
.
rerandomize
==
'never'
:
if
self
.
rerandomize
==
RANDOMIZATION
.
NEVER
:
self
.
seed
=
1
elif
self
.
rerandomize
==
"per_student"
and
hasattr
(
self
.
runtime
,
'seed'
):
elif
self
.
rerandomize
==
RANDOMIZATION
.
PER_STUDENT
and
hasattr
(
self
.
runtime
,
'seed'
):
# see comment on randomization_bin
self
.
seed
=
randomization_bin
(
self
.
runtime
.
seed
,
unicode
(
self
.
location
)
.
encode
(
'utf-8'
))
else
:
...
...
@@ -446,7 +457,7 @@ class CapaMixin(CapaFields):
"""
Return True/False to indicate whether to show the "Check" button.
"""
submitted_without_reset
=
(
self
.
is_submitted
()
and
self
.
rerandomize
==
"always"
)
submitted_without_reset
=
(
self
.
is_submitted
()
and
self
.
rerandomize
==
RANDOMIZATION
.
ALWAYS
)
# If the problem is closed (past due / too many attempts)
# then we do NOT show the "check" button
...
...
@@ -463,19 +474,20 @@ class CapaMixin(CapaFields):
"""
is_survey_question
=
(
self
.
max_attempts
==
0
)
if
self
.
rerandomize
in
[
"always"
,
"onreset"
]:
# If the problem is closed (and not a survey question with max_attempts==0),
# then do NOT show the reset button.
# If the problem hasn't been submitted yet, then do NOT show
# the reset button.
if
(
self
.
closed
()
and
not
is_survey_question
)
or
not
self
.
is_submitted
():
if
(
self
.
closed
()
and
not
is_survey_question
):
return
False
else
:
# Button only shows up for randomized problems if the question has been submitted
if
self
.
rerandomize
in
[
RANDOMIZATION
.
ALWAYS
,
RANDOMIZATION
.
ONRESET
]
and
self
.
is_submitted
():
return
True
# Only randomized problems need a "reset" button
else
:
# Do NOT show the button if the problem is correct
if
self
.
is_correct
():
return
False
else
:
return
self
.
show_reset_button
def
should_show_save_button
(
self
):
"""
...
...
@@ -489,7 +501,7 @@ class CapaMixin(CapaFields):
return
not
self
.
closed
()
else
:
is_survey_question
=
(
self
.
max_attempts
==
0
)
needs_reset
=
self
.
is_submitted
()
and
self
.
rerandomize
==
"always"
needs_reset
=
self
.
is_submitted
()
and
self
.
rerandomize
==
RANDOMIZATION
.
ALWAYS
# If the student has unlimited attempts, and their answers
# are not randomized, then we do not need a save button
...
...
@@ -503,7 +515,7 @@ class CapaMixin(CapaFields):
# In those cases. the if statement below is false,
# and the save button can still be displayed.
#
if
self
.
max_attempts
is
None
and
self
.
rerandomize
!=
"always"
:
if
self
.
max_attempts
is
None
and
self
.
rerandomize
!=
RANDOMIZATION
.
ALWAYS
:
return
False
# If the problem is closed (and not a survey question with max_attempts==0),
...
...
@@ -697,28 +709,28 @@ class CapaMixin(CapaFields):
"""
if
self
.
showanswer
==
''
:
return
False
elif
self
.
showanswer
==
"never"
:
elif
self
.
showanswer
==
SHOWANSWER
.
NEVER
:
return
False
elif
self
.
runtime
.
user_is_staff
:
# This is after the 'never' check because admins can see the answer
# unless the problem explicitly prevents it
return
True
elif
self
.
showanswer
==
'attempted'
:
elif
self
.
showanswer
==
SHOWANSWER
.
ATTEMPTED
:
return
self
.
attempts
>
0
elif
self
.
showanswer
==
'answered'
:
elif
self
.
showanswer
==
SHOWANSWER
.
ANSWERED
:
# NOTE: this is slightly different from 'attempted' -- resetting the problems
# makes lcp.done False, but leaves attempts unchanged.
return
self
.
lcp
.
done
elif
self
.
showanswer
==
'closed'
:
elif
self
.
showanswer
==
SHOWANSWER
.
CLOSED
:
return
self
.
closed
()
elif
self
.
showanswer
==
'finished'
:
elif
self
.
showanswer
==
SHOWANSWER
.
FINISHED
:
return
self
.
closed
()
or
self
.
is_correct
()
elif
self
.
showanswer
==
'correct_or_past_due'
:
elif
self
.
showanswer
==
SHOWANSWER
.
CORRECT_OR_PAST_DUE
:
return
self
.
is_correct
()
or
self
.
is_past_due
()
elif
self
.
showanswer
==
'past_due'
:
elif
self
.
showanswer
==
SHOWANSWER
.
PAST_DUE
:
return
self
.
is_past_due
()
elif
self
.
showanswer
==
'always'
:
elif
self
.
showanswer
==
SHOWANSWER
.
ALWAYS
:
return
True
return
False
...
...
@@ -952,7 +964,7 @@ class CapaMixin(CapaFields):
raise
NotFoundError
(
_
(
"Problem is closed."
))
# Problem submitted. Student should reset before checking again
if
self
.
done
and
self
.
rerandomize
==
"always"
:
if
self
.
done
and
self
.
rerandomize
==
RANDOMIZATION
.
ALWAYS
:
event_info
[
'failure'
]
=
'unreset'
self
.
track_function_unmask
(
'problem_check_fail'
,
event_info
)
if
dog_stats_api
:
...
...
@@ -1206,7 +1218,7 @@ class CapaMixin(CapaFields):
# was presented to the user, with values interpolated etc, but that can be done
# later if necessary.
variant
=
''
if
self
.
rerandomize
!=
'never'
:
if
self
.
rerandomize
!=
RANDOMIZATION
.
NEVER
:
variant
=
self
.
seed
is_correct
=
correct_map
.
is_correct
(
input_id
)
...
...
@@ -1333,7 +1345,7 @@ class CapaMixin(CapaFields):
# Problem submitted. Student should reset before saving
# again.
if
self
.
done
and
self
.
rerandomize
==
"always"
:
if
self
.
done
and
self
.
rerandomize
==
RANDOMIZATION
.
ALWAYS
:
event_info
[
'failure'
]
=
'done'
self
.
track_function_unmask
(
'save_problem_fail'
,
event_info
)
return
{
...
...
@@ -1357,7 +1369,7 @@ class CapaMixin(CapaFields):
def
reset_problem
(
self
,
_data
):
"""
Changes problem state to unfinished -- removes student answers,
and causes problem to rerender itself
.
Causes problem to rerender itself if randomization is enabled
.
Returns a dictionary of the form:
{'success': True/False,
...
...
@@ -1380,7 +1392,7 @@ class CapaMixin(CapaFields):
'error'
:
_
(
"Problem is closed."
),
}
if
not
self
.
done
:
if
not
self
.
is_submitted
()
:
event_info
[
'failure'
]
=
'not_done'
self
.
track_function_unmask
(
'reset_problem_fail'
,
event_info
)
return
{
...
...
@@ -1389,7 +1401,7 @@ class CapaMixin(CapaFields):
'error'
:
_
(
"Refresh the page and make an attempt before resetting."
),
}
if
self
.
rerandomize
in
[
"always"
,
"onreset"
]:
if
self
.
is_submitted
()
and
self
.
rerandomize
in
[
RANDOMIZATION
.
ALWAYS
,
RANDOMIZATION
.
ONRESET
]:
# Reset random number generator seed.
self
.
choose_new_seed
()
...
...
common/lib/xmodule/xmodule/capa_base_constants.py
0 → 100644
View file @
3d1c54fe
# -*- coding: utf-8 -*-
"""
Constants for capa_base problems
"""
class
SHOWANSWER
:
"""
Constants for when to show answer
"""
ALWAYS
=
"always"
ANSWERED
=
"answered"
ATTEMPTED
=
"attempted"
CLOSED
=
"closed"
FINISHED
=
"finished"
CORRECT_OR_PAST_DUE
=
"correct_or_past_due"
PAST_DUE
=
"past_due"
NEVER
=
"never"
class
RANDOMIZATION
:
"""
Constants for problem randomization
"""
ALWAYS
=
"always"
ONRESET
=
"onreset"
NEVER
=
"never"
PER_STUDENT
=
"per_student"
common/lib/xmodule/xmodule/modulestore/inheritance.py
View file @
3d1c54fe
"""
Support for inheritance of fields down an XBlock hierarchy.
"""
from
__future__
import
absolute_import
from
datetime
import
datetime
from
pytz
import
UTC
from
xmodule.partitions.partitions
import
UserPartition
from
xblock.fields
import
Scope
,
Boolean
,
String
,
Float
,
XBlockMixin
,
Dict
,
Integer
,
List
from
xblock.runtime
import
KeyValueStore
,
KvsFieldData
from
xmodule.fields
import
Date
,
Timedelta
from
django.conf
import
settings
# Make '_' a no-op so we can scrape strings
_
=
lambda
text
:
text
...
...
@@ -153,6 +154,16 @@ class InheritanceMixin(XBlockMixin):
scope
=
Scope
.
settings
)
reset_key
=
"DEFAULT_SHOW_RESET_BUTTON"
default_reset_button
=
getattr
(
settings
,
reset_key
)
if
hasattr
(
settings
,
reset_key
)
else
False
show_reset_button
=
Boolean
(
display_name
=
_
(
"Show Reset Button for Problems"
),
help
=
_
(
"Enter true or false. If true, problems default to displaying a 'Reset' button. This value may be "
"overriden in each problem's settings. Existing problems whose reset setting have not been changed are affected."
),
scope
=
Scope
.
settings
,
default
=
default_reset_button
)
def
compute_inherited_metadata
(
descriptor
):
"""Given a descriptor, traverse all of its descendants and do metadata
...
...
common/lib/xmodule/xmodule/tests/test_capa_module.py
View file @
3d1c54fe
...
...
@@ -13,6 +13,7 @@ import random
import
os
import
textwrap
import
unittest
import
ddt
from
mock
import
Mock
,
patch
import
webob
...
...
@@ -31,6 +32,7 @@ from xblock.fields import ScopeIds
from
.
import
get_test_system
from
pytz
import
UTC
from
capa.correctmap
import
CorrectMap
from
..capa_base_constants
import
RANDOMIZATION
class
CapaFactory
(
object
):
...
...
@@ -140,7 +142,6 @@ class CapaFactory(object):
return
module
class
CapaFactoryWithFiles
(
CapaFactory
):
"""
A factory for creating a Capa problem with files attached.
...
...
@@ -182,6 +183,7 @@ if submission[0] == '':
"""
)
@ddt.ddt
class
CapaModuleTest
(
unittest
.
TestCase
):
def
setUp
(
self
):
...
...
@@ -540,10 +542,11 @@ class CapaModuleTest(unittest.TestCase):
# Expect that number of attempts NOT incremented
self
.
assertEqual
(
module
.
attempts
,
3
)
def
test_check_problem_resubmitted_with_randomize
(
self
):
rerandomize_values
=
[
'always'
,
'true'
]
for
rerandomize
in
rerandomize_values
:
@ddt.data
(
RANDOMIZATION
.
ALWAYS
,
'true'
)
def
test_check_problem_resubmitted_with_randomize
(
self
,
rerandomize
):
# Randomize turned on
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
,
attempts
=
0
)
...
...
@@ -558,10 +561,12 @@ class CapaModuleTest(unittest.TestCase):
# Expect that number of attempts NOT incremented
self
.
assertEqual
(
module
.
attempts
,
0
)
def
test_check_problem_resubmitted_no_randomize
(
self
):
rerandomize_values
=
[
'never'
,
'false'
,
'per_student'
]
for
rerandomize
in
rerandomize_values
:
@ddt.data
(
RANDOMIZATION
.
NEVER
,
'false'
,
RANDOMIZATION
.
PER_STUDENT
)
def
test_check_problem_resubmitted_no_randomize
(
self
,
rerandomize
):
# Randomize turned off
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
,
attempts
=
0
,
done
=
True
)
...
...
@@ -813,7 +818,7 @@ class CapaModuleTest(unittest.TestCase):
def
test_reset_problem_closed
(
self
):
# pre studio default
module
=
CapaFactory
.
create
(
rerandomize
=
"always"
)
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
)
# Simulate that the problem is closed
with
patch
(
'xmodule.capa_module.CapaModule.closed'
)
as
mock_closed
:
...
...
@@ -944,12 +949,12 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the result is failure
self
.
assertTrue
(
'success'
in
result
and
not
result
[
'success'
])
def
test_save_problem_submitted_with_randomize
(
self
):
@ddt.data
(
RANDOMIZATION
.
ALWAYS
,
'true'
)
def
test_save_problem_submitted_with_randomize
(
self
,
rerandomize
):
# Capa XModule treats 'always' and 'true' equivalently
rerandomize_values
=
[
'always'
,
'true'
]
for
rerandomize
in
rerandomize_values
:
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
,
done
=
True
)
# Try to save
...
...
@@ -959,12 +964,13 @@ class CapaModuleTest(unittest.TestCase):
# Expect that we cannot save
self
.
assertTrue
(
'success'
in
result
and
not
result
[
'success'
])
def
test_save_problem_submitted_no_randomize
(
self
):
@ddt.data
(
RANDOMIZATION
.
NEVER
,
'false'
,
RANDOMIZATION
.
PER_STUDENT
)
def
test_save_problem_submitted_no_randomize
(
self
,
rerandomize
):
# Capa XModule treats 'false' and 'per_student' equivalently
rerandomize_values
=
[
'never'
,
'false'
,
'per_student'
]
for
rerandomize
in
rerandomize_values
:
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
,
done
=
True
)
# Try to save
...
...
@@ -1066,7 +1072,7 @@ class CapaModuleTest(unittest.TestCase):
# If user submitted a problem but hasn't reset,
# do NOT show the check button
# Note: we can only reset when rerandomize="always" or "true"
module
=
CapaFactory
.
create
(
rerandomize
=
"always"
,
done
=
True
)
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
done
=
True
)
self
.
assertFalse
(
module
.
should_show_check_button
())
module
=
CapaFactory
.
create
(
rerandomize
=
"true"
,
done
=
True
)
...
...
@@ -1080,13 +1086,13 @@ class CapaModuleTest(unittest.TestCase):
# and we do NOT have a reset button, then we can show the check button
# Setting rerandomize to "never" or "false" ensures that the reset button
# is not shown
module
=
CapaFactory
.
create
(
rerandomize
=
"never"
,
done
=
True
)
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
NEVER
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_check_button
())
module
=
CapaFactory
.
create
(
rerandomize
=
"false"
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_check_button
())
module
=
CapaFactory
.
create
(
rerandomize
=
"per_student"
,
done
=
True
)
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
PER_STUDENT
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_check_button
())
def
test_should_show_reset_button
(
self
):
...
...
@@ -1101,30 +1107,36 @@ class CapaModuleTest(unittest.TestCase):
module
=
CapaFactory
.
create
(
attempts
=
attempts
,
max_attempts
=
attempts
,
done
=
True
)
self
.
assertFalse
(
module
.
should_show_reset_button
())
#
If we're NOT randomizing, then do NOT
show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
"never"
,
done
=
True
)
self
.
assert
Fals
e
(
module
.
should_show_reset_button
())
#
pre studio default value, DO
show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
done
=
True
)
self
.
assert
Tru
e
(
module
.
should_show_reset_button
())
# If we're NOT randomizing, then do NOT show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
"per_student"
,
done
=
True
)
self
.
assertFalse
(
module
.
should_show_reset_button
())
# If survey question for capa (max_attempts = 0),
# DO show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
max_attempts
=
0
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_reset_button
())
# If we're NOT randomizing, then do NOT show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
"false"
,
done
=
True
)
self
.
assertFalse
(
module
.
should_show_reset_button
())
# If the question is not correct
# DO show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
max_attempts
=
0
,
done
=
True
,
correct
=
False
)
self
.
assertTrue
(
module
.
should_show_reset_button
())
# If the
user hasn't submitted an answer yet,
#
then do NOT
show the reset button
module
=
CapaFactory
.
create
(
done
=
Fals
e
)
# If the
question is correct and randomization is never
#
DO not
show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
NEVER
,
max_attempts
=
0
,
done
=
True
,
correct
=
Tru
e
)
self
.
assertFalse
(
module
.
should_show_reset_button
())
# pre studio default value, DO show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
"always"
,
done
=
True
)
# If the question is correct and randomization is always
# Show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
max_attempts
=
0
,
done
=
True
,
correct
=
True
)
self
.
assertTrue
(
module
.
should_show_reset_button
())
# If survey question for capa (max_attempts = 0),
# DO show the reset button
module
=
CapaFactory
.
create
(
rerandomize
=
"always"
,
max_attempts
=
0
,
done
=
True
)
# Don't show reset button if randomization is turned on and the question is not done
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
show_reset_button
=
False
,
done
=
False
)
self
.
assertFalse
(
module
.
should_show_reset_button
())
# Show reset button if randomization is turned on and the problem is done
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
show_reset_button
=
False
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_reset_button
())
def
test_should_show_save_button
(
self
):
...
...
@@ -1140,7 +1152,7 @@ class CapaModuleTest(unittest.TestCase):
self
.
assertFalse
(
module
.
should_show_save_button
())
# If user submitted a problem but hasn't reset, do NOT show the save button
module
=
CapaFactory
.
create
(
rerandomize
=
"always"
,
done
=
True
)
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
done
=
True
)
self
.
assertFalse
(
module
.
should_show_save_button
())
module
=
CapaFactory
.
create
(
rerandomize
=
"true"
,
done
=
True
)
...
...
@@ -1149,27 +1161,27 @@ class CapaModuleTest(unittest.TestCase):
# If the user has unlimited attempts and we are not randomizing,
# then do NOT show a save button
# because they can keep using "Check"
module
=
CapaFactory
.
create
(
max_attempts
=
None
,
rerandomize
=
"never"
,
done
=
False
)
module
=
CapaFactory
.
create
(
max_attempts
=
None
,
rerandomize
=
RANDOMIZATION
.
NEVER
,
done
=
False
)
self
.
assertFalse
(
module
.
should_show_save_button
())
module
=
CapaFactory
.
create
(
max_attempts
=
None
,
rerandomize
=
"false"
,
done
=
True
)
self
.
assertFalse
(
module
.
should_show_save_button
())
module
=
CapaFactory
.
create
(
max_attempts
=
None
,
rerandomize
=
"per_student"
,
done
=
True
)
module
=
CapaFactory
.
create
(
max_attempts
=
None
,
rerandomize
=
RANDOMIZATION
.
PER_STUDENT
,
done
=
True
)
self
.
assertFalse
(
module
.
should_show_save_button
())
# pre-studio default, DO show the save button
module
=
CapaFactory
.
create
(
rerandomize
=
"always"
,
done
=
False
)
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
done
=
False
)
self
.
assertTrue
(
module
.
should_show_save_button
())
# If we're not randomizing and we have limited attempts, then we can save
module
=
CapaFactory
.
create
(
rerandomize
=
"never"
,
max_attempts
=
2
,
done
=
True
)
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
NEVER
,
max_attempts
=
2
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_save_button
())
module
=
CapaFactory
.
create
(
rerandomize
=
"false"
,
max_attempts
=
2
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_save_button
())
module
=
CapaFactory
.
create
(
rerandomize
=
"per_student"
,
max_attempts
=
2
,
done
=
True
)
module
=
CapaFactory
.
create
(
rerandomize
=
RANDOMIZATION
.
PER_STUDENT
,
max_attempts
=
2
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_save_button
())
# If survey question for capa (max_attempts = 0),
...
...
@@ -1197,7 +1209,7 @@ class CapaModuleTest(unittest.TestCase):
# then show it even if we would ordinarily
# require a reset first
module
=
CapaFactory
.
create
(
force_save_button
=
"true"
,
rerandomize
=
"always"
,
rerandomize
=
RANDOMIZATION
.
ALWAYS
,
done
=
True
)
self
.
assertTrue
(
module
.
should_show_save_button
())
...
...
@@ -1331,12 +1343,18 @@ class CapaModuleTest(unittest.TestCase):
context
=
render_args
[
1
]
self
.
assertTrue
(
error_msg
in
context
[
'problem'
][
'html'
])
def
test_random_seed_no_change
(
self
):
@ddt.data
(
'false'
,
'true'
,
RANDOMIZATION
.
NEVER
,
RANDOMIZATION
.
PER_STUDENT
,
RANDOMIZATION
.
ALWAYS
,
RANDOMIZATION
.
ONRESET
)
def
test_random_seed_no_change
(
self
,
rerandomize
):
# Run the test for each possible rerandomize value
for
rerandomize
in
[
'false'
,
'never'
,
'per_student'
,
'always'
,
'true'
,
'onreset'
]:
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
)
# Get the seed
...
...
@@ -1346,7 +1364,7 @@ class CapaModuleTest(unittest.TestCase):
# If we're not rerandomizing, the seed is always set
# to the same value (1)
if
rerandomize
in
[
'never'
]
:
if
rerandomize
==
RANDOMIZATION
.
NEVER
:
self
.
assertEqual
(
seed
,
1
,
msg
=
"Seed should always be 1 when rerandomize='
%
s'"
%
rerandomize
)
...
...
@@ -1363,16 +1381,28 @@ class CapaModuleTest(unittest.TestCase):
# Expect that the seed is the same
self
.
assertEqual
(
seed
,
module
.
seed
)
def
test_random_seed_with_reset
(
self
):
@ddt.data
(
'false'
,
'true'
,
RANDOMIZATION
.
NEVER
,
RANDOMIZATION
.
PER_STUDENT
,
RANDOMIZATION
.
ALWAYS
,
RANDOMIZATION
.
ONRESET
)
def
test_random_seed_with_reset
(
self
,
rerandomize
):
"""
Run the test for each possible rerandomize value
"""
def
_reset_and_get_seed
(
module
):
'''
"""
Reset the XModule and return the module's seed
'''
"""
# Simulate submitting an attempt
# We need to do this, or reset_problem() will
# fail
with a complaint that we haven't
submitted
# fail
because it won't re-randomize until the problem has been
submitted
# the problem yet.
module
.
done
=
True
...
...
@@ -1397,10 +1427,7 @@ class CapaModuleTest(unittest.TestCase):
break
return
success
# Run the test for each possible rerandomize value
for
rerandomize
in
[
'never'
,
'false'
,
'per_student'
,
'always'
,
'true'
,
'onreset'
]:
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
)
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
,
done
=
True
)
# Get the seed
# By this point, the module should have persisted the seed
...
...
@@ -1411,7 +1438,9 @@ class CapaModuleTest(unittest.TestCase):
# is set to 'never' -- it should still be 1
# The seed also stays the same if we're randomizing
# 'per_student': the same student should see the same problem
if
rerandomize
in
[
'never'
,
'false'
,
'per_student'
]:
if
rerandomize
in
[
RANDOMIZATION
.
NEVER
,
'false'
,
RANDOMIZATION
.
PER_STUDENT
]:
self
.
assertEqual
(
seed
,
_reset_and_get_seed
(
module
))
# Otherwise, we expect the seed to change
...
...
@@ -1427,15 +1456,54 @@ class CapaModuleTest(unittest.TestCase):
msg
=
'Could not get a new seed from reset after 5 tries'
self
.
assertTrue
(
success
,
msg
)
def
test_random_seed_bins
(
self
):
# Assert that we are limiting the number of possible seeds.
@ddt.data
(
'false'
,
'true'
,
RANDOMIZATION
.
NEVER
,
RANDOMIZATION
.
PER_STUDENT
,
RANDOMIZATION
.
ALWAYS
,
RANDOMIZATION
.
ONRESET
)
def
test_random_seed_with_reset_question_unsubmitted
(
self
,
rerandomize
):
"""
Run the test for each possible rerandomize value
"""
def
_reset_and_get_seed
(
module
):
"""
Reset the XModule and return the module's seed
"""
# Check the conditions that generate random seeds
for
rerandomize
in
[
'always'
,
'per_student'
,
'true'
,
'onreset'
]:
# Reset the problem
# By default, the problem is instantiated as unsubmitted
module
.
reset_problem
({})
# Return the seed
return
module
.
seed
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
,
done
=
False
)
# Get the seed
# By this point, the module should have persisted the seed
seed
=
module
.
seed
self
.
assertTrue
(
seed
is
not
None
)
#the seed should never change because the student hasn't finished the problem
self
.
assertEqual
(
seed
,
_reset_and_get_seed
(
module
))
@ddt.data
(
RANDOMIZATION
.
ALWAYS
,
RANDOMIZATION
.
PER_STUDENT
,
'true'
,
RANDOMIZATION
.
ONRESET
)
def
test_random_seed_bins
(
self
,
rerandomize
):
# Assert that we are limiting the number of possible seeds.
# Get a bunch of seeds, they should all be in 0-999.
for
i
in
range
(
200
):
i
=
200
while
i
>
0
:
module
=
CapaFactory
.
create
(
rerandomize
=
rerandomize
)
assert
0
<=
module
.
seed
<
1000
i
-=
1
@patch
(
'xmodule.capa_base.log'
)
@patch
(
'xmodule.capa_base.Progress'
)
...
...
@@ -1765,7 +1833,7 @@ class TestProblemCheckTracking(unittest.TestCase):
def
test_rerandomized_inputs
(
self
):
factory
=
CapaFactory
module
=
factory
.
create
(
rerandomize
=
'always'
)
module
=
factory
.
create
(
rerandomize
=
RANDOMIZATION
.
ALWAYS
)
answer_input_dict
=
{
factory
.
input_key
(
2
):
'3.14'
...
...
lms/djangoapps/courseware/features/problems.feature
View file @
3d1c54fe
...
...
@@ -72,37 +72,77 @@ Feature: LMS.Answer problems
Scenario
:
I
can reset a problem
Given
I am viewing a
"<ProblemType>"
problem
Given
I am viewing a randomization
"<Randomization>"
"<ProblemType>"
problem with reset button on
And
I answer a
"<ProblemType>"
problem
"<Correctness>ly"
When
I reset the problem
Then
my
"<ProblemType>"
answer is marked
"unanswered"
And
The
"<ProblemType>"
problem displays a
"blank"
answer
Examples
:
|
ProblemType
|
Correctness
|
Randomization
|
|
drop
down
|
correct
|
always
|
|
drop
down
|
incorrect
|
always
|
|
multiple
choice
|
correct
|
always
|
|
multiple
choice
|
incorrect
|
always
|
|
checkbox
|
correct
|
always
|
|
checkbox
|
incorrect
|
always
|
|
radio
|
correct
|
always
|
|
radio
|
incorrect
|
always
|
|
string
|
correct
|
always
|
|
string
|
incorrect
|
always
|
|
numerical
|
correct
|
always
|
|
numerical
|
incorrect
|
always
|
|
formula
|
correct
|
always
|
|
formula
|
incorrect
|
always
|
|
script
|
correct
|
always
|
|
script
|
incorrect
|
always
|
|
radio_text
|
correct
|
always
|
|
radio_text
|
incorrect
|
always
|
|
checkbox_text
|
correct
|
always
|
|
checkbox_text
|
incorrect
|
always
|
|
image
|
correct
|
always
|
|
image
|
incorrect
|
always
|
Scenario
:
I
can reset a non-randomized problem that I answer incorrectly
Given
I am viewing a randomization
"<Randomization>"
"<ProblemType>"
problem with reset button on
And
I answer a
"<ProblemType>"
problem
"<Correctness>ly"
When
I reset the problem
Then
my
"<ProblemType>"
answer is marked
"unanswered"
And
The
"<ProblemType>"
problem displays a
"blank"
answer
Examples
:
|
ProblemType
|
Correctness
|
|
drop
down
|
correct
|
|
drop
down
|
incorrect
|
|
multiple
choice
|
correct
|
|
multiple
choice
|
incorrect
|
|
checkbox
|
correct
|
|
checkbox
|
incorrect
|
|
radio
|
correct
|
|
radio
|
incorrect
|
|
string
|
correct
|
|
string
|
incorrect
|
|
numerical
|
correct
|
|
numerical
|
incorrect
|
|
formula
|
correct
|
|
formula
|
incorrect
|
|
script
|
correct
|
|
script
|
incorrect
|
|
radio_text
|
correct
|
|
radio_text
|
incorrect
|
|
checkbox_text
|
correct
|
|
checkbox_text
|
incorrect
|
|
image
|
correct
|
|
image
|
incorrect
|
|
ProblemType
|
Correctness
|
Randomization
|
|
drop
down
|
incorrect
|
never
|
|
drop
down
|
incorrect
|
never
|
|
multiple
choice
|
incorrect
|
never
|
|
checkbox
|
incorrect
|
never
|
|
radio
|
incorrect
|
never
|
|
string
|
incorrect
|
never
|
|
numerical
|
incorrect
|
never
|
|
formula
|
incorrect
|
never
|
|
script
|
incorrect
|
never
|
|
radio_text
|
incorrect
|
never
|
|
checkbox_text
|
incorrect
|
never
|
|
image
|
incorrect
|
never
|
Scenario
:
The reset button doesn't show up
Given
I am viewing a randomization
"<Randomization>"
"<ProblemType>"
problem with reset button on
And
I answer a
"<ProblemType>"
problem
"<Correctness>ly"
Then
The
"Reset"
button does not appear
Examples
:
|
ProblemType
|
Correctness
|
Randomization
|
|
drop
down
|
correct
|
never
|
|
multiple
choice
|
correct
|
never
|
|
checkbox
|
correct
|
never
|
|
radio
|
correct
|
never
|
|
string
|
correct
|
never
|
|
numerical
|
correct
|
never
|
|
formula
|
correct
|
never
|
|
script
|
correct
|
never
|
|
radio_text
|
correct
|
never
|
|
checkbox_text
|
correct
|
never
|
|
image
|
correct
|
never
|
Scenario
:
I
can answer a problem with one attempt correctly and not reset
Given
I am viewing a
"multiple choice"
problem with
"1"
attempt
...
...
@@ -115,6 +155,12 @@ Feature: LMS.Answer problems
When
I answer a
"multiple choice"
problem
"correctly"
Then
The
"Reset"
button does appear
Scenario
:
I
can answer a problem with multiple attempts correctly but cannot reset because randomization is off
Given
I am viewing a randomization
"never"
"multiple choice"
problem with
"3"
attempts with reset
Then
I should see
"You have used 0 of 3 submissions"
somewhere in the page
When
I answer a
"multiple choice"
problem
"correctly"
Then
The
"Reset"
button does not appear
Scenario
:
I
can view how many attempts I have left on a problem
Given
I am viewing a
"multiple choice"
problem with
"3"
attempts
Then
I should see
"You have used 0 of 3 submissions"
somewhere in the page
...
...
@@ -165,6 +211,34 @@ Feature: LMS.Answer problems
|
image
|
correct
|
1/1
point
|
1
point
possible
|
|
image
|
incorrect
|
1
point
possible
|
1
point
possible
|
Scenario
:
I
can see my score on a problem when I answer it and after I reset it
Given
I am viewing a
"<ProblemType>"
problem with randomization
"<Randomization>"
with reset button on
When
I answer a
"<ProblemType>"
problem
"<Correctness>ly"
Then
I should see a score of
"<Score>"
When
I reset the problem
Then
I should see a score of
"<Points Possible>"
Examples
:
|
ProblemType
|
Correctness
|
Score
|
Points
Possible
|
Randomization
|
|
drop
down
|
correct
|
1/1
point
|
1
point
possible
|
never
|
|
drop
down
|
incorrect
|
1
point
possible
|
1
point
possible
|
never
|
|
multiple
choice
|
correct
|
1/1
point
|
1
point
possible
|
never
|
|
multiple
choice
|
incorrect
|
1
point
possible
|
1
point
possible
|
never
|
|
checkbox
|
correct
|
1/1
point
|
1
point
possible
|
never
|
|
checkbox
|
incorrect
|
1
point
possible
|
1
point
possible
|
never
|
|
radio
|
correct
|
1/1
point
|
1
point
possible
|
never
|
|
radio
|
incorrect
|
1
point
possible
|
1
point
possible
|
never
|
|
string
|
correct
|
1/1
point
|
1
point
possible
|
never
|
|
string
|
incorrect
|
1
point
possible
|
1
point
possible
|
never
|
|
numerical
|
correct
|
1/1
point
|
1
point
possible
|
never
|
|
numerical
|
incorrect
|
1
point
possible
|
1
point
possible
|
never
|
|
formula
|
correct
|
1/1
point
|
1
point
possible
|
never
|
|
formula
|
incorrect
|
1
point
possible
|
1
point
possible
|
never
|
|
script
|
correct
|
2/2
points
|
2
points
possible
|
never
|
|
script
|
incorrect
|
2
points
possible
|
2
points
possible
|
never
|
|
image
|
correct
|
1/1
point
|
1
point
possible
|
never
|
|
image
|
incorrect
|
1
point
possible
|
1
point
possible
|
never
|
Scenario
:
I
can see my score on a problem to which I submit a blank answer
Given
I am viewing a
"<ProblemType>"
problem
When
I check a problem
...
...
lms/djangoapps/courseware/features/problems.py
View file @
3d1c54fe
...
...
@@ -26,6 +26,13 @@ def view_problem_with_attempts(step, problem_type, attempts):
_view_problem
(
step
,
problem_type
,
{
'max_attempts'
:
attempts
})
@step
(
u'I am viewing a randomization "([^"]*)" "([^"]*)" problem with "([^"]*)" attempts with reset'
)
def
view_problem_attempts_reset
(
step
,
randomization
,
problem_type
,
attempts
,
):
_view_problem
(
step
,
problem_type
,
{
'max_attempts'
:
attempts
,
'rerandomize'
:
randomization
,
'show_reset_button'
:
True
})
@step
(
u'I am viewing a "([^"]*)" that shows the answer "([^"]*)"'
)
def
view_problem_with_show_answer
(
step
,
problem_type
,
answer
):
_view_problem
(
step
,
problem_type
,
{
'showanswer'
:
answer
})
...
...
@@ -36,6 +43,11 @@ def view_problem(step, problem_type):
_view_problem
(
step
,
problem_type
)
@step
(
u'I am viewing a randomization "([^"]*)" "([^"]*)" problem with reset button on'
)
def
view_random_reset_problem
(
step
,
randomization
,
problem_type
):
_view_problem
(
step
,
problem_type
,
{
'rerandomize'
:
randomization
,
'show_reset_button'
:
True
})
@step
(
u'External graders respond "([^"]*)"'
)
def
set_external_grader_response
(
step
,
correctness
):
assert
(
correctness
in
[
'correct'
,
'incorrect'
])
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment