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
521c469a
Commit
521c469a
authored
Mar 18, 2013
by
Diana Huang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add the ability for input types to have their own state
and add in a handler for ungraded responses via xqueue
parent
45d8086e
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
124 additions
and
8 deletions
+124
-8
common/lib/capa/capa/capa_problem.py
+24
-0
common/lib/capa/capa/inputtypes.py
+50
-6
common/lib/capa/capa/templates/matlabinput.html
+3
-0
common/lib/capa/capa/tests/test_inputtypes.py
+31
-1
common/lib/xmodule/xmodule/capa_module.py
+16
-1
No files found.
common/lib/capa/capa/capa_problem.py
View file @
521c469a
...
@@ -111,6 +111,7 @@ class LoncapaProblem(object):
...
@@ -111,6 +111,7 @@ class LoncapaProblem(object):
if
self
.
system
is
None
:
if
self
.
system
is
None
:
raise
Exception
()
raise
Exception
()
self
.
seed
=
seed
self
.
seed
=
seed
self
.
input_state
=
None
if
state
:
if
state
:
if
'seed'
in
state
:
if
'seed'
in
state
:
...
@@ -121,11 +122,16 @@ class LoncapaProblem(object):
...
@@ -121,11 +122,16 @@ class LoncapaProblem(object):
self
.
correct_map
.
set_dict
(
state
[
'correct_map'
])
self
.
correct_map
.
set_dict
(
state
[
'correct_map'
])
if
'done'
in
state
:
if
'done'
in
state
:
self
.
done
=
state
[
'done'
]
self
.
done
=
state
[
'done'
]
if
'input_state'
in
state
:
self
.
input_state
=
state
[
'input_state'
]
# TODO: Does this deplete the Linux entropy pool? Is this fast enough?
# TODO: Does this deplete the Linux entropy pool? Is this fast enough?
if
not
self
.
seed
:
if
not
self
.
seed
:
self
.
seed
=
struct
.
unpack
(
'i'
,
os
.
urandom
(
4
))[
0
]
self
.
seed
=
struct
.
unpack
(
'i'
,
os
.
urandom
(
4
))[
0
]
if
not
self
.
input_state
:
self
.
input_state
=
{}
# Convert startouttext and endouttext to proper <text></text>
# Convert startouttext and endouttext to proper <text></text>
problem_text
=
re
.
sub
(
"startouttext
\
s*/"
,
"text"
,
problem_text
)
problem_text
=
re
.
sub
(
"startouttext
\
s*/"
,
"text"
,
problem_text
)
problem_text
=
re
.
sub
(
"endouttext
\
s*/"
,
"/text"
,
problem_text
)
problem_text
=
re
.
sub
(
"endouttext
\
s*/"
,
"/text"
,
problem_text
)
...
@@ -188,6 +194,7 @@ class LoncapaProblem(object):
...
@@ -188,6 +194,7 @@ class LoncapaProblem(object):
return
{
'seed'
:
self
.
seed
,
return
{
'seed'
:
self
.
seed
,
'student_answers'
:
self
.
student_answers
,
'student_answers'
:
self
.
student_answers
,
'correct_map'
:
self
.
correct_map
.
get_dict
(),
'correct_map'
:
self
.
correct_map
.
get_dict
(),
'input_state'
:
self
.
input_state
,
'done'
:
self
.
done
}
'done'
:
self
.
done
}
def
get_max_score
(
self
):
def
get_max_score
(
self
):
...
@@ -237,6 +244,19 @@ class LoncapaProblem(object):
...
@@ -237,6 +244,19 @@ class LoncapaProblem(object):
self
.
correct_map
.
set_dict
(
cmap
.
get_dict
())
self
.
correct_map
.
set_dict
(
cmap
.
get_dict
())
return
cmap
return
cmap
def
ungraded_response
(
self
,
xqueue_msg
,
queuekey
):
'''
Handle any responses from the xqueue that are not related to grading
Does not return any value
'''
# check against each inputtype
for
the_input
in
self
.
inputs
.
values
():
# if the input type has an xqueue_response function, pass in the values
if
hasattr
(
the_input
,
'ungraded_response'
):
the_input
.
ungraded_response
(
xqueue_msg
,
queuekey
)
def
is_queued
(
self
):
def
is_queued
(
self
):
'''
'''
Returns True if any part of the problem has been submitted to an external queue
Returns True if any part of the problem has been submitted to an external queue
...
@@ -527,11 +547,15 @@ class LoncapaProblem(object):
...
@@ -527,11 +547,15 @@ class LoncapaProblem(object):
value
=
""
value
=
""
if
self
.
student_answers
and
problemid
in
self
.
student_answers
:
if
self
.
student_answers
and
problemid
in
self
.
student_answers
:
value
=
self
.
student_answers
[
problemid
]
value
=
self
.
student_answers
[
problemid
]
if
input_id
not
in
self
.
input_state
:
self
.
input_state
[
input_id
]
=
{}
# do the rendering
# do the rendering
state
=
{
'value'
:
value
,
state
=
{
'value'
:
value
,
'status'
:
status
,
'status'
:
status
,
'id'
:
input_id
,
'id'
:
input_id
,
'input_state'
:
self
.
input_state
[
input_id
],
'feedback'
:
{
'message'
:
msg
,
'feedback'
:
{
'message'
:
msg
,
'hint'
:
hint
,
'hint'
:
hint
,
'hintmode'
:
hintmode
,
}}
'hintmode'
:
hintmode
,
}}
...
...
common/lib/capa/capa/inputtypes.py
View file @
521c469a
...
@@ -134,6 +134,8 @@ class InputTypeBase(object):
...
@@ -134,6 +134,8 @@ class InputTypeBase(object):
* 'id' -- the id of this input, typically
* 'id' -- the id of this input, typically
"{problem-location}_{response-num}_{input-num}"
"{problem-location}_{response-num}_{input-num}"
* 'status' (answered, unanswered, unsubmitted)
* 'status' (answered, unanswered, unsubmitted)
* 'input_state' -- dictionary containing any inputtype-specific state
that has been preserved
* 'feedback' (dictionary containing keys for hints, errors, or other
* 'feedback' (dictionary containing keys for hints, errors, or other
feedback from previous attempt. Specifically 'message', 'hint',
feedback from previous attempt. Specifically 'message', 'hint',
'hintmode'. If 'hintmode' is 'always', the hint is always displayed.)
'hintmode'. If 'hintmode' is 'always', the hint is always displayed.)
...
@@ -160,6 +162,7 @@ class InputTypeBase(object):
...
@@ -160,6 +162,7 @@ class InputTypeBase(object):
self
.
msg
=
feedback
.
get
(
'message'
,
''
)
self
.
msg
=
feedback
.
get
(
'message'
,
''
)
self
.
hint
=
feedback
.
get
(
'hint'
,
''
)
self
.
hint
=
feedback
.
get
(
'hint'
,
''
)
self
.
hintmode
=
feedback
.
get
(
'hintmode'
,
None
)
self
.
hintmode
=
feedback
.
get
(
'hintmode'
,
None
)
self
.
input_state
=
state
.
get
(
'input_state'
,
{})
# put hint above msg if it should be displayed
# put hint above msg if it should be displayed
if
self
.
hintmode
==
'always'
:
if
self
.
hintmode
==
'always'
:
...
@@ -643,8 +646,11 @@ class MatlabInput(CodeInput):
...
@@ -643,8 +646,11 @@ class MatlabInput(CodeInput):
self
.
queue_len
=
0
self
.
queue_len
=
0
self
.
queuename
=
'matlab'
self
.
queuename
=
'matlab'
# Flag indicating that the problem has been queued, 'msg' is length of
# Flag indicating that the problem has been queued, 'msg' is length of
self
.
queue_msg
=
None
# queue
# queue
if
self
.
status
==
'incomplete'
:
if
self
.
status
==
'incomplete'
:
if
'queue_msg'
in
self
.
input_state
:
self
.
queue_msg
=
self
.
input_state
[
'queue_msg'
]
self
.
status
=
'queued'
self
.
status
=
'queued'
self
.
queue_len
=
self
.
msg
self
.
queue_len
=
self
.
msg
self
.
msg
=
self
.
submitted_msg
self
.
msg
=
self
.
submitted_msg
...
@@ -652,13 +658,47 @@ class MatlabInput(CodeInput):
...
@@ -652,13 +658,47 @@ class MatlabInput(CodeInput):
def
handle_ajax
(
self
,
dispatch
,
get
):
def
handle_ajax
(
self
,
dispatch
,
get
):
''' Handle AJAX calls directed to this input'''
if
dispatch
==
'plot'
:
if
dispatch
==
'plot'
:
return
self
.
plot_data
(
get
)
return
self
.
_plot_data
(
get
)
elif
dispatch
==
'xqueue_response'
:
# render the response
def
ungraded_response
(
self
,
queue_msg
,
queuekey
):
pass
''' Handle any XQueue responses that have to be saved and rendered '''
# check the queuekey against the saved queuekey
if
(
'queuestate'
in
self
.
input_state
and
self
.
input_state
[
'queuestate'
]
==
'queued'
and
self
.
input_state
[
'queuekey'
]
==
queuekey
):
msg
=
_parse_message
(
queue_msg
)
# save the queue message so that it can be rendered later
self
.
input_state
[
'queue_msg'
]
=
msg
self
.
input_state
[
'queued'
]
=
'dequeued'
def
plot_data
(
self
,
get
):
def
_extra_context
(
self
):
''' Set up additional context variables'''
extra_context
=
{
'queue_len'
:
self
.
queue_len
}
if
self
.
queue_msg
is
not
None
:
extra_context
[
'queue_msg'
]
=
self
.
queue_msg
else
:
extra_context
[
'queue_msg'
]
=
''
return
extra_context
def
_parse_data
(
self
,
queue_msg
):
'''
takes a queue_msg returned from the queue and parses it and returns
whatever is stored in msg
returns string msg
'''
try
:
result
=
json
.
loads
(
queue_msg
)
except
(
TypeError
,
ValueError
):
log
.
error
(
"External message should be a JSON serialized dict."
" Received queue_msg =
%
s"
%
queue_msg
)
raise
# TODO: needs more error checking
msg
=
result
[
'msg'
]
return
msg
def
_plot_data
(
self
,
get
):
''' send data via xqueue to the mathworks backend'''
''' send data via xqueue to the mathworks backend'''
# only send data if xqueue exists
# only send data if xqueue exists
if
self
.
system
.
xqueue
is
not
None
:
if
self
.
system
.
xqueue
is
not
None
:
...
@@ -668,7 +708,7 @@ class MatlabInput(CodeInput):
...
@@ -668,7 +708,7 @@ class MatlabInput(CodeInput):
# construct xqueue headers
# construct xqueue headers
qinterface
=
self
.
system
.
xqueue
[
'interface'
]
qinterface
=
self
.
system
.
xqueue
[
'interface'
]
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
callback_url
=
self
.
system
.
xqueue
[
'construct_callback'
](
'
input_ajax
'
)
callback_url
=
self
.
system
.
xqueue
[
'construct_callback'
](
'
ungraded_response
'
)
anonymous_student_id
=
self
.
system
.
anonymous_student_id
anonymous_student_id
=
self
.
system
.
anonymous_student_id
queuekey
=
xqueue_interface
.
make_hashkey
(
str
(
self
.
system
.
seed
)
+
qtime
+
queuekey
=
xqueue_interface
.
make_hashkey
(
str
(
self
.
system
.
seed
)
+
qtime
+
anonymous_student_id
+
anonymous_student_id
+
...
@@ -678,6 +718,10 @@ class MatlabInput(CodeInput):
...
@@ -678,6 +718,10 @@ class MatlabInput(CodeInput):
lms_key
=
queuekey
,
lms_key
=
queuekey
,
queue_name
=
self
.
queuename
)
queue_name
=
self
.
queuename
)
# save the input state
self
.
input_state
[
'queuekey'
]
=
queuekey
self
.
input_state
[
'queuestate'
]
=
'queued'
# construct xqueue body
# construct xqueue body
student_info
=
{
'anonymous_student_id'
:
anonymous_student_id
,
student_info
=
{
'anonymous_student_id'
:
anonymous_student_id
,
...
...
common/lib/capa/capa/templates/matlabinput.html
View file @
521c469a
...
@@ -29,6 +29,9 @@
...
@@ -29,6 +29,9 @@
<div
class=
"external-grader-message"
>
<div
class=
"external-grader-message"
>
${msg|n}
${msg|n}
</div>
</div>
<div
class=
"external-grader-message"
>
${queue_msg|n}
</div>
<div
class=
"plot-button"
>
<div
class=
"plot-button"
>
<input
type=
"button"
name=
"plot-button"
value=
"Plot"
/>
<input
type=
"button"
name=
"plot-button"
value=
"Plot"
/>
...
...
common/lib/capa/capa/tests/test_inputtypes.py
View file @
521c469a
...
@@ -344,6 +344,35 @@ class MatlabTest(unittest.TestCase):
...
@@ -344,6 +344,35 @@ class MatlabTest(unittest.TestCase):
'mode'
:
self
.
mode
,
'mode'
:
self
.
mode
,
'rows'
:
self
.
rows
,
'rows'
:
self
.
rows
,
'cols'
:
self
.
cols
,
'cols'
:
self
.
cols
,
'queue_msg'
:
''
,
'linenumbers'
:
'true'
,
'hidden'
:
''
,
'tabsize'
:
int
(
self
.
tabsize
),
'queue_len'
:
'3'
,
}
self
.
assertEqual
(
context
,
expected
)
def
test_rendering_with_state
(
self
):
state
=
{
'value'
:
'print "good evening"'
,
'status'
:
'incomplete'
,
'input_state'
:
{
'queue_msg'
:
'message'
},
'feedback'
:
{
'message'
:
'3'
},
}
elt
=
etree
.
fromstring
(
self
.
xml
)
input_class
=
lookup_tag
(
'matlabinput'
)
the_input
=
self
.
input_class
(
test_system
,
elt
,
state
)
context
=
the_input
.
_get_render_context
()
expected
=
{
'id'
:
'prob_1_2'
,
'value'
:
'print "good evening"'
,
'status'
:
'queued'
,
'msg'
:
self
.
input_class
.
submitted_msg
,
'mode'
:
self
.
mode
,
'rows'
:
self
.
rows
,
'cols'
:
self
.
cols
,
'queue_msg'
:
'message'
,
'linenumbers'
:
'true'
,
'linenumbers'
:
'true'
,
'hidden'
:
''
,
'hidden'
:
''
,
'tabsize'
:
int
(
self
.
tabsize
),
'tabsize'
:
int
(
self
.
tabsize
),
...
@@ -358,8 +387,9 @@ class MatlabTest(unittest.TestCase):
...
@@ -358,8 +387,9 @@ class MatlabTest(unittest.TestCase):
test_system
.
xqueue
[
'interface'
]
.
send_to_queue
.
assert_called_with
(
header
=
ANY
,
body
=
ANY
)
test_system
.
xqueue
[
'interface'
]
.
send_to_queue
.
assert_called_with
(
header
=
ANY
,
body
=
ANY
)
self
.
assertTrue
(
response
[
'success'
])
self
.
assertTrue
(
response
[
'success'
])
self
.
assertTrue
(
self
.
the_input
.
input_state
[
'queuekey'
]
is
not
None
)
self
.
assertEqual
(
self
.
the_input
.
input_state
[
'queuestate'
],
'queued'
)
...
...
common/lib/xmodule/xmodule/capa_module.py
View file @
521c469a
...
@@ -93,6 +93,7 @@ class CapaFields(object):
...
@@ -93,6 +93,7 @@ class CapaFields(object):
rerandomize
=
Randomization
(
help
=
"When to rerandomize the problem"
,
default
=
"always"
,
scope
=
Scope
.
settings
)
rerandomize
=
Randomization
(
help
=
"When to rerandomize the problem"
,
default
=
"always"
,
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
)
correct_map
=
Object
(
help
=
"Dictionary with the correctness of current student answers"
,
scope
=
Scope
.
student_state
,
default
=
{})
correct_map
=
Object
(
help
=
"Dictionary with the correctness of current student answers"
,
scope
=
Scope
.
student_state
,
default
=
{})
input_state
=
Object
(
help
=
"Dictionary for maintaining the state of inputtypes"
,
scope
=
Scope
.
student_state
,
default
=
{})
student_answers
=
Object
(
help
=
"Dictionary with the current student responses"
,
scope
=
Scope
.
student_state
)
student_answers
=
Object
(
help
=
"Dictionary with the current student responses"
,
scope
=
Scope
.
student_state
)
done
=
Boolean
(
help
=
"Whether the student has answered the problem"
,
scope
=
Scope
.
student_state
)
done
=
Boolean
(
help
=
"Whether the student has answered the problem"
,
scope
=
Scope
.
student_state
)
display_name
=
String
(
help
=
"Display name for this module"
,
scope
=
Scope
.
settings
)
display_name
=
String
(
help
=
"Display name for this module"
,
scope
=
Scope
.
settings
)
...
@@ -188,6 +189,7 @@ class CapaModule(CapaFields, XModule):
...
@@ -188,6 +189,7 @@ class CapaModule(CapaFields, XModule):
'done'
:
self
.
done
,
'done'
:
self
.
done
,
'correct_map'
:
self
.
correct_map
,
'correct_map'
:
self
.
correct_map
,
'student_answers'
:
self
.
student_answers
,
'student_answers'
:
self
.
student_answers
,
'input_state'
:
self
.
input_state
,
'seed'
:
self
.
seed
,
'seed'
:
self
.
seed
,
}
}
...
@@ -195,6 +197,7 @@ class CapaModule(CapaFields, XModule):
...
@@ -195,6 +197,7 @@ class CapaModule(CapaFields, XModule):
lcp_state
=
self
.
lcp
.
get_state
()
lcp_state
=
self
.
lcp
.
get_state
()
self
.
done
=
lcp_state
[
'done'
]
self
.
done
=
lcp_state
[
'done'
]
self
.
correct_map
=
lcp_state
[
'correct_map'
]
self
.
correct_map
=
lcp_state
[
'correct_map'
]
self
.
input_state
=
lcp_state
[
'input_state'
]
self
.
student_answers
=
lcp_state
[
'student_answers'
]
self
.
student_answers
=
lcp_state
[
'student_answers'
]
self
.
seed
=
lcp_state
[
'seed'
]
self
.
seed
=
lcp_state
[
'seed'
]
...
@@ -443,7 +446,8 @@ class CapaModule(CapaFields, XModule):
...
@@ -443,7 +446,8 @@ class CapaModule(CapaFields, XModule):
'problem_save'
:
self
.
save_problem
,
'problem_save'
:
self
.
save_problem
,
'problem_show'
:
self
.
get_answer
,
'problem_show'
:
self
.
get_answer
,
'score_update'
:
self
.
update_score
,
'score_update'
:
self
.
update_score
,
'input_ajax'
:
self
.
lcp
.
handle_input_ajax
'input_ajax'
:
self
.
lcp
.
handle_input_ajax
,
'ungraded_response'
:
self
.
handle_ungraded_response
}
}
if
dispatch
not
in
handlers
:
if
dispatch
not
in
handlers
:
...
@@ -537,6 +541,17 @@ class CapaModule(CapaFields, XModule):
...
@@ -537,6 +541,17 @@ class CapaModule(CapaFields, XModule):
return
dict
()
# No AJAX return is needed
return
dict
()
# No AJAX return is needed
def
handle_ungraded_response
(
self
,
get
):
'''
Get the XQueue response
'''
queuekey
=
get
[
'queuekey'
]
score_msg
=
get
[
'xqueue_body'
]
# pass along the xqueue message to the problem
self
.
lcp
.
ungraded_response
(
score_msg
,
queuekey
)
self
.
set_state_from_lcp
()
def
get_answer
(
self
,
get
):
def
get_answer
(
self
,
get
):
'''
'''
For the "show answer" button.
For the "show answer" button.
...
...
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