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
57fb306c
Commit
57fb306c
authored
Sep 20, 2013
by
Vik Paruchuri
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1068 from edx/feature/vik/oe-save
Implement a save button for open ended responses
parents
936d2e0f
d9ce5e07
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
167 additions
and
23 deletions
+167
-23
common/lib/xmodule/xmodule/combined_open_ended_module.py
+20
-5
common/lib/xmodule/xmodule/css/combinedopenended/display.scss
+1
-1
common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
+21
-1
common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
+1
-0
common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
+3
-4
common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
+30
-0
common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
+5
-9
common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
+35
-0
common/lib/xmodule/xmodule/tests/test_self_assessment.py
+49
-2
lms/templates/combinedopenended/openended/open_ended.html
+1
-1
lms/templates/combinedopenended/selfassessment/self_assessment_prompt.html
+1
-0
No files found.
common/lib/xmodule/xmodule/combined_open_ended_module.py
View file @
57fb306c
...
...
@@ -14,13 +14,28 @@ import textwrap
log
=
logging
.
getLogger
(
"mitx.courseware"
)
V1_SETTINGS_ATTRIBUTES
=
[
"display_name"
,
"max_attempts"
,
"graded"
,
"accept_file_upload"
,
"skip_spelling_checks"
,
"due"
,
"graceperiod"
,
"weight"
,
"min_to_calibrate"
,
"max_to_calibrate"
,
"peer_grader_count"
,
"required_peer_grading"
,
"display_name"
,
"max_attempts"
,
"graded"
,
"accept_file_upload"
,
"skip_spelling_checks"
,
"due"
,
"graceperiod"
,
"weight"
,
"min_to_calibrate"
,
"max_to_calibrate"
,
"peer_grader_count"
,
"required_peer_grading"
,
]
V1_STUDENT_ATTRIBUTES
=
[
"current_task_number"
,
"task_states"
,
"state"
,
"student_attempts"
,
"ready_to_reset"
,
"old_task_states"
]
V1_STUDENT_ATTRIBUTES
=
[
"current_task_number"
,
"task_states"
,
"state"
,
"student_attempts"
,
"ready_to_reset"
,
"old_task_states"
,
]
V1_ATTRIBUTES
=
V1_SETTINGS_ATTRIBUTES
+
V1_STUDENT_ATTRIBUTES
...
...
common/lib/xmodule/xmodule/css/combinedopenended/display.scss
View file @
57fb306c
...
...
@@ -322,7 +322,7 @@ div.combined-rubric-container {
div
.written-feedback
{
background
:
#f6f6f6
;
padding
:
1
5px
;
padding
:
5px
;
}
}
...
...
common/lib/xmodule/xmodule/js/src/combinedopenended/display.coffee
View file @
57fb306c
...
...
@@ -119,6 +119,7 @@ class @CombinedOpenEnded
next_rubric_sel
:
'.rubric-next-button'
previous_rubric_sel
:
'.rubric-previous-button'
oe_alert_sel
:
'.open-ended-alert'
save_button_sel
:
'.save-button'
constructor
:
(
el
)
->
@
el
=
el
...
...
@@ -183,6 +184,7 @@ class @CombinedOpenEnded
@
hint_wrapper
=
@
$
(
@
oe
).
find
(
@
hint_wrapper_sel
)
@
message_wrapper
=
@
$
(
@
oe
).
find
(
@
message_wrapper_sel
)
@
submit_button
=
@
$
(
@
oe
).
find
(
@
submit_button_sel
)
@
save_button
=
@
$
(
@
oe
).
find
(
@
save_button_sel
)
@
child_state
=
@
oe
.
data
(
'state'
)
@
child_type
=
@
oe
.
data
(
'child-type'
)
if
@
child_type
==
"openended"
...
...
@@ -270,6 +272,8 @@ class @CombinedOpenEnded
# rebind to the appropriate function for the current state
@
submit_button
.
unbind
(
'click'
)
@
submit_button
.
show
()
@
save_button
.
unbind
(
'click'
)
@
save_button
.
hide
()
@
reset_button
.
hide
()
@
hide_file_upload
()
@
next_problem_button
.
hide
()
...
...
@@ -295,6 +299,8 @@ class @CombinedOpenEnded
@
submit_button
.
prop
(
'value'
,
'Submit'
)
@
submit_button
.
click
@
confirm_save_answer
@
setup_file_upload
()
@
save_button
.
click
@
store_answer
@
save_button
.
show
()
else
if
@
child_state
==
'assessing'
@
answer_area
.
attr
(
"disabled"
,
true
)
@
replace_text_inputs
()
...
...
@@ -334,13 +340,26 @@ class @CombinedOpenEnded
else
@
reset_button
.
show
()
find_assessment_elements
:
->
@
assessment
=
@
$
(
'input[name="grade-selection"]'
)
find_hint_elements
:
->
@
hint_area
=
@
$
(
'textarea.post_assessment'
)
store_answer
:
(
event
)
=>
event
.
preventDefault
()
if
@
child_state
==
'initial'
data
=
{
'student_answer'
:
@
answer_area
.
val
()}
@
save_button
.
attr
(
"disabled"
,
true
)
$
.
postWithPrefix
"
#{
@
ajax_url
}
/store_answer"
,
data
,
(
response
)
=>
if
response
.
success
@
gentle_alert
(
"Answer saved."
)
else
@
errors_area
.
html
(
response
.
error
)
@
save_button
.
attr
(
"disabled"
,
false
)
else
@
errors_area
.
html
(
@
out_of_sync_message
)
replace_answer
:
(
response
)
=>
if
response
.
success
@
rubric_wrapper
.
html
(
response
.
rubric_html
)
...
...
@@ -360,6 +379,7 @@ class @CombinedOpenEnded
@
save_answer
(
event
)
if
confirm
(
'Please confirm that you wish to submit your work. You will not be able to make any changes after submitting.'
)
save_answer
:
(
event
)
=>
@
$el
.
find
(
@
oe_alert_sel
).
remove
()
@
submit_button
.
attr
(
"disabled"
,
true
)
@
submit_button
.
hide
()
event
.
preventDefault
()
...
...
common/lib/xmodule/xmodule/open_ended_grading_classes/combined_open_ended_modulev1.py
View file @
57fb306c
...
...
@@ -784,6 +784,7 @@ class CombinedOpenEndedV1Module():
self
.
task_states
[
self
.
current_task_number
]
=
self
.
current_task
.
get_instance_state
()
self
.
current_task_number
=
0
self
.
ready_to_reset
=
False
self
.
setup_next_task
()
return
{
'success'
:
True
,
'html'
:
self
.
get_html_nonsystem
()}
...
...
common/lib/xmodule/xmodule/open_ended_grading_classes/open_ended_module.py
View file @
57fb306c
...
...
@@ -605,6 +605,7 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
'save_post_assessment'
:
self
.
message_post
,
'skip_post_assessment'
:
self
.
skip_post_assessment
,
'check_for_score'
:
self
.
check_for_score
,
'store_answer'
:
self
.
store_answer
,
}
if
dispatch
not
in
handlers
:
...
...
@@ -688,8 +689,6 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
# set context variables and render template
eta_string
=
None
if
self
.
child_state
!=
self
.
INITIAL
:
latest
=
self
.
latest_answer
()
previous_answer
=
latest
if
latest
is
not
None
else
self
.
initial_display
post_assessment
=
self
.
latest_post_assessment
(
system
)
score
=
self
.
latest_score
()
correct
=
'correct'
if
self
.
is_submission_correct
(
score
)
else
'incorrect'
...
...
@@ -698,8 +697,8 @@ class OpenEndedModule(openendedchild.OpenEndedChild):
else
:
post_assessment
=
""
correct
=
""
previous_answer
=
""
previous_answer
=
previous_answer
.
replace
(
"
\n
"
,
"<br/>"
)
previous_answer
=
self
.
get_display_answer
()
context
=
{
'prompt'
:
self
.
child_prompt
,
'previous_answer'
:
previous_answer
,
...
...
common/lib/xmodule/xmodule/open_ended_grading_classes/openendedchild.py
View file @
57fb306c
...
...
@@ -82,6 +82,7 @@ class OpenEndedChild(object):
self
.
child_state
=
instance_state
.
get
(
'child_state'
,
self
.
INITIAL
)
self
.
child_created
=
instance_state
.
get
(
'child_created'
,
False
)
self
.
child_attempts
=
instance_state
.
get
(
'child_attempts'
,
0
)
self
.
stored_answer
=
instance_state
.
get
(
'stored_answer'
,
None
)
self
.
max_attempts
=
static_data
[
'max_attempts'
]
self
.
child_prompt
=
static_data
[
'prompt'
]
...
...
@@ -195,6 +196,7 @@ class OpenEndedChild(object):
"""
answer
=
OpenEndedChild
.
sanitize_html
(
answer
)
self
.
child_history
.
append
({
'answer'
:
answer
})
self
.
stored_answer
=
None
def
record_latest_score
(
self
,
score
):
"""Assumes that state is right, so we're adding a score to the latest
...
...
@@ -231,6 +233,7 @@ class OpenEndedChild(object):
'max_score'
:
self
.
_max_score
,
'child_attempts'
:
self
.
child_attempts
,
'child_created'
:
False
,
'stored_answer'
:
self
.
stored_answer
,
}
return
json
.
dumps
(
state
)
...
...
@@ -262,6 +265,33 @@ class OpenEndedChild(object):
self
.
change_state
(
self
.
INITIAL
)
return
{
'success'
:
True
}
def
get_display_answer
(
self
):
latest
=
self
.
latest_answer
()
if
self
.
child_state
==
self
.
INITIAL
:
if
self
.
stored_answer
is
not
None
:
previous_answer
=
self
.
stored_answer
elif
latest
is
not
None
and
len
(
latest
)
>
0
:
previous_answer
=
latest
else
:
previous_answer
=
""
previous_answer
=
previous_answer
.
replace
(
"<br/>"
,
"
\n
"
)
.
replace
(
"<br>"
,
"
\n
"
)
else
:
if
latest
is
not
None
and
len
(
latest
)
>
0
:
previous_answer
=
latest
else
:
previous_answer
=
""
previous_answer
=
previous_answer
.
replace
(
"
\n
"
,
"<br/>"
)
return
previous_answer
def
store_answer
(
self
,
data
,
system
):
if
self
.
child_state
!=
self
.
INITIAL
:
# We can only store an answer if the problem has not moved into the assessment phase.
return
self
.
out_of_sync_error
(
data
)
self
.
stored_answer
=
data
[
'student_answer'
]
return
{
'success'
:
True
}
def
get_progress
(
self
):
'''
For now, just return last score / max_score
...
...
common/lib/xmodule/xmodule/open_ended_grading_classes/self_assessment_module.py
View file @
57fb306c
...
...
@@ -55,13 +55,8 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
@return: Rendered HTML
"""
# set context variables and render template
if
self
.
child_state
!=
self
.
INITIAL
:
latest
=
self
.
latest_answer
()
previous_answer
=
latest
if
latest
is
not
None
else
''
else
:
previous_answer
=
''
previous_answer
=
self
.
get_display_answer
()
previous_answer
=
previous_answer
.
replace
(
"
\n
"
,
"<br/>"
)
context
=
{
'prompt'
:
self
.
child_prompt
,
'previous_answer'
:
previous_answer
,
...
...
@@ -91,6 +86,7 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
'save_answer'
:
self
.
save_answer
,
'save_assessment'
:
self
.
save_assessment
,
'save_post_assessment'
:
self
.
save_hint
,
'store_answer'
:
self
.
store_answer
,
}
if
dispatch
not
in
handlers
:
...
...
@@ -218,13 +214,13 @@ class SelfAssessmentModule(openendedchild.OpenEndedChild):
return
self
.
out_of_sync_error
(
data
)
try
:
score
=
int
(
data
[
'assessment'
]
)
score
=
int
(
data
.
get
(
'assessment'
)
)
score_list
=
data
.
getlist
(
'score_list[]'
)
for
i
in
xrange
(
0
,
len
(
score_list
)):
score_list
[
i
]
=
int
(
score_list
[
i
])
except
ValueError
:
except
(
ValueError
,
TypeError
)
:
# This is a dev_facing_error
log
.
error
(
"Non-integer score value passed to save_assessment
,
or no score list present."
)
log
.
error
(
"Non-integer score value passed to save_assessment
,
or no score list present."
)
# This is a student_facing_error
return
{
'success'
:
False
,
'error'
:
"Error saving your score. Please notify course staff."
}
...
...
common/lib/xmodule/xmodule/tests/test_combined_open_ended.py
View file @
57fb306c
...
...
@@ -344,6 +344,41 @@ class OpenEndedModuleTest(unittest.TestCase):
score
=
self
.
openendedmodule
.
latest_score
()
self
.
assertEquals
(
score
,
1
)
def
test_open_ended_display
(
self
):
"""
Test storing answer with the open ended module.
"""
# Create a module with no state yet. Important that this start off as a blank slate.
test_module
=
OpenEndedModule
(
self
.
test_system
,
self
.
location
,
self
.
definition
,
self
.
descriptor
,
self
.
static_data
,
self
.
metadata
)
saved_response
=
"Saved response."
submitted_response
=
"Submitted response."
# Initially, there will be no stored answer.
self
.
assertEqual
(
test_module
.
stored_answer
,
None
)
# And the initial answer to display will be an empty string.
self
.
assertEqual
(
test_module
.
get_display_answer
(),
""
)
# Now, store an answer in the module.
test_module
.
handle_ajax
(
"store_answer"
,
{
'student_answer'
:
saved_response
},
get_test_system
())
# The stored answer should now equal our response.
self
.
assertEqual
(
test_module
.
stored_answer
,
saved_response
)
self
.
assertEqual
(
test_module
.
get_display_answer
(),
saved_response
)
# Mock out the send_to_grader function so it doesn't try to connect to the xqueue.
test_module
.
send_to_grader
=
Mock
(
return_value
=
True
)
# Submit a student response to the question.
test_module
.
handle_ajax
(
"save_answer"
,
{
"student_answer"
:
submitted_response
,
"can_upload_files"
:
False
,
"student_file"
:
None
},
get_test_system
()
)
# Submitting an answer should clear the stored answer.
self
.
assertEqual
(
test_module
.
stored_answer
,
None
)
# Confirm that the answer is stored properly.
self
.
assertEqual
(
test_module
.
latest_answer
(),
submitted_response
)
class
CombinedOpenEndedModuleTest
(
unittest
.
TestCase
):
"""
...
...
common/lib/xmodule/xmodule/tests/test_self_assessment.py
View file @
57fb306c
...
...
@@ -4,6 +4,7 @@ import unittest
from
xmodule.open_ended_grading_classes.self_assessment_module
import
SelfAssessmentModule
from
xmodule.modulestore
import
Location
from
xmodule.tests.test_util_open_ended
import
MockQueryDict
from
lxml
import
etree
from
.
import
get_test_system
...
...
@@ -38,7 +39,7 @@ class SelfAssessmentTest(unittest.TestCase):
'state'
:
SelfAssessmentModule
.
INITIAL
,
'attempts'
:
2
})
static_data
=
{
s
elf
.
s
tatic_data
=
{
'max_attempts'
:
10
,
'rubric'
:
etree
.
XML
(
self
.
rubric
),
'prompt'
:
self
.
prompt
,
...
...
@@ -60,7 +61,7 @@ class SelfAssessmentTest(unittest.TestCase):
self
.
module
=
SelfAssessmentModule
(
get_test_system
(),
self
.
location
,
self
.
definition
,
self
.
descriptor
,
static_data
)
s
elf
.
s
tatic_data
)
def
test_get_html
(
self
):
html
=
self
.
module
.
get_html
(
self
.
module
.
system
)
...
...
@@ -104,3 +105,49 @@ class SelfAssessmentTest(unittest.TestCase):
responses
[
'assessment'
]
=
'1'
self
.
module
.
save_assessment
(
mock_query_dict
,
self
.
module
.
system
)
self
.
assertEqual
(
self
.
module
.
child_state
,
self
.
module
.
DONE
)
def
test_self_assessment_display
(
self
):
"""
Test storing an answer with the self assessment module.
"""
# Create a module with no state yet. Important that this start off as a blank slate.
test_module
=
SelfAssessmentModule
(
get_test_system
(),
self
.
location
,
self
.
definition
,
self
.
descriptor
,
self
.
static_data
)
saved_response
=
"Saved response."
submitted_response
=
"Submitted response."
# Initially, there will be no stored answer.
self
.
assertEqual
(
test_module
.
stored_answer
,
None
)
# And the initial answer to display will be an empty string.
self
.
assertEqual
(
test_module
.
get_display_answer
(),
""
)
# Now, store an answer in the module.
test_module
.
handle_ajax
(
"store_answer"
,
{
'student_answer'
:
saved_response
},
get_test_system
())
# The stored answer should now equal our response.
self
.
assertEqual
(
test_module
.
stored_answer
,
saved_response
)
self
.
assertEqual
(
test_module
.
get_display_answer
(),
saved_response
)
# Submit a student response to the question.
test_module
.
handle_ajax
(
"save_answer"
,
{
"student_answer"
:
submitted_response
,
"can_upload_files"
:
False
,
"student_file"
:
None
},
get_test_system
())
# Submitting an answer should clear the stored answer.
self
.
assertEqual
(
test_module
.
stored_answer
,
None
)
# Confirm that the answer is stored properly.
self
.
assertEqual
(
test_module
.
latest_answer
(),
submitted_response
)
# Mock saving an assessment.
assessment
=
[
0
]
assessment_dict
=
MockQueryDict
({
'assessment'
:
sum
(
assessment
),
'score_list[]'
:
assessment
})
data
=
test_module
.
handle_ajax
(
"save_assessment"
,
assessment_dict
,
get_test_system
())
self
.
assertTrue
(
json
.
loads
(
data
)[
'success'
])
# Reset the module so the student can try again.
test_module
.
reset
(
get_test_system
())
# Confirm that the right response is loaded.
self
.
assertEqual
(
test_module
.
get_display_answer
(),
submitted_response
)
lms/templates/combinedopenended/openended/open_ended.html
View file @
57fb306c
...
...
@@ -30,7 +30,7 @@
</div>
<div
class=
"file-upload"
></div>
<input
type=
"button"
value=
"${_('Save')}"
class=
"save-button"
name=
"save"
/>
<input
type=
"button"
value=
"${_('Submit')}"
class=
"submit-button"
name=
"show"
/>
<input
name=
"skip"
class=
"skip-button"
type=
"button"
value=
"${_('Skip Post-Assessment')}"
/>
...
...
lms/templates/combinedopenended/selfassessment/self_assessment_prompt.html
View file @
57fb306c
...
...
@@ -19,6 +19,7 @@
<div
class=
"rubric-wrapper"
>
${initial_rubric}
</div>
<div
class=
"file-upload"
></div>
<input
type=
"button"
value=
"${_('Save')}"
class=
"save-button"
name=
"save"
/>
<input
type=
"button"
value=
"${_('Submit')}"
class=
"submit-button"
name=
"show"
/>
<div
class=
"open-ended-action"
></div>
...
...
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