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
7f742978
Commit
7f742978
authored
Jan 17, 2014
by
Sarina Canelake
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2219 from edx/sarina/i19-capa-new
i19 base capa strings
parents
207f3c58
0efa75d6
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
353 additions
and
277 deletions
+353
-277
common/lib/capa/capa/capa_problem.py
+57
-55
common/lib/capa/capa/correctmap.py
+8
-8
common/lib/capa/capa/customrender.py
+4
-4
common/lib/capa/capa/inputtypes.py
+69
-52
common/lib/capa/capa/responsetypes.py
+107
-85
common/lib/capa/capa/tests/test_inputtypes.py
+5
-5
common/lib/capa/capa/tests/test_responsetypes.py
+1
-1
common/lib/capa/capa/util.py
+11
-9
common/lib/capa/capa/xqueue_interface.py
+8
-6
common/lib/xmodule/xmodule/capa_base.py
+77
-48
common/lib/xmodule/xmodule/capa_module.py
+4
-2
lms/djangoapps/instructor_task/tests/test_integration.py
+2
-2
No files found.
common/lib/capa/capa/capa_problem.py
View file @
7f742978
...
@@ -7,11 +7,11 @@
...
@@ -7,11 +7,11 @@
# Each Response may have one or more Input entry fields.
# Each Response may have one or more Input entry fields.
# The capa problem may include a solution.
# The capa problem may include a solution.
#
#
'''
"""
Main module which shows problems (of "capa" type).
Main module which shows problems (of "capa" type).
This is used by capa_module.
This is used by capa_module.
'''
"""
from
datetime
import
datetime
from
datetime
import
datetime
import
logging
import
logging
...
@@ -108,9 +108,9 @@ class LoncapaSystem(object):
...
@@ -108,9 +108,9 @@ class LoncapaSystem(object):
class
LoncapaProblem
(
object
):
class
LoncapaProblem
(
object
):
'''
"""
Main class for capa Problems.
Main class for capa Problems.
'''
"""
def
__init__
(
self
,
problem_text
,
id
,
capa_system
,
state
=
None
,
seed
=
None
):
def
__init__
(
self
,
problem_text
,
id
,
capa_system
,
state
=
None
,
seed
=
None
):
"""
"""
...
@@ -181,9 +181,9 @@ class LoncapaProblem(object):
...
@@ -181,9 +181,9 @@ class LoncapaProblem(object):
self
.
extracted_tree
=
self
.
_extract_html
(
self
.
tree
)
self
.
extracted_tree
=
self
.
_extract_html
(
self
.
tree
)
def
do_reset
(
self
):
def
do_reset
(
self
):
'''
"""
Reset internal state to unfinished, with no answers
Reset internal state to unfinished, with no answers
'''
"""
self
.
student_answers
=
dict
()
self
.
student_answers
=
dict
()
self
.
correct_map
=
CorrectMap
()
self
.
correct_map
=
CorrectMap
()
self
.
done
=
False
self
.
done
=
False
...
@@ -203,11 +203,11 @@ class LoncapaProblem(object):
...
@@ -203,11 +203,11 @@ class LoncapaProblem(object):
return
u"LoncapaProblem ({0})"
.
format
(
self
.
problem_id
)
return
u"LoncapaProblem ({0})"
.
format
(
self
.
problem_id
)
def
get_state
(
self
):
def
get_state
(
self
):
'''
"""
Stored per-user session data neeeded to:
Stored per-user session data neeeded to:
1) Recreate the problem
1) Recreate the problem
2) Populate any student answers.
2) Populate any student answers.
'''
"""
return
{
'seed'
:
self
.
seed
,
return
{
'seed'
:
self
.
seed
,
'student_answers'
:
self
.
student_answers
,
'student_answers'
:
self
.
student_answers
,
...
@@ -216,9 +216,9 @@ class LoncapaProblem(object):
...
@@ -216,9 +216,9 @@ class LoncapaProblem(object):
'done'
:
self
.
done
}
'done'
:
self
.
done
}
def
get_max_score
(
self
):
def
get_max_score
(
self
):
'''
"""
Return the maximum score for this problem.
Return the maximum score for this problem.
'''
"""
maxscore
=
0
maxscore
=
0
for
responder
in
self
.
responders
.
values
():
for
responder
in
self
.
responders
.
values
():
maxscore
+=
responder
.
get_max_score
()
maxscore
+=
responder
.
get_max_score
()
...
@@ -235,7 +235,7 @@ class LoncapaProblem(object):
...
@@ -235,7 +235,7 @@ class LoncapaProblem(object):
try
:
try
:
correct
+=
self
.
correct_map
.
get_npoints
(
key
)
correct
+=
self
.
correct_map
.
get_npoints
(
key
)
except
Exception
:
except
Exception
:
log
.
error
(
'key=
%
s, correct_map =
%
s'
%
(
key
,
self
.
correct_map
)
)
log
.
error
(
'key=
%
s, correct_map =
%
s'
,
key
,
self
.
correct_map
)
raise
raise
if
(
not
self
.
student_answers
)
or
len
(
self
.
student_answers
)
==
0
:
if
(
not
self
.
student_answers
)
or
len
(
self
.
student_answers
)
==
0
:
...
@@ -246,12 +246,12 @@ class LoncapaProblem(object):
...
@@ -246,12 +246,12 @@ class LoncapaProblem(object):
'total'
:
self
.
get_max_score
()}
'total'
:
self
.
get_max_score
()}
def
update_score
(
self
,
score_msg
,
queuekey
):
def
update_score
(
self
,
score_msg
,
queuekey
):
'''
"""
Deliver grading response (e.g. from async code checking) to
Deliver grading response (e.g. from async code checking) to
the specific ResponseType that requested grading
the specific ResponseType that requested grading
Returns an updated CorrectMap
Returns an updated CorrectMap
'''
"""
cmap
=
CorrectMap
()
cmap
=
CorrectMap
()
cmap
.
update
(
self
.
correct_map
)
cmap
.
update
(
self
.
correct_map
)
for
responder
in
self
.
responders
.
values
():
for
responder
in
self
.
responders
.
values
():
...
@@ -263,12 +263,12 @@ class LoncapaProblem(object):
...
@@ -263,12 +263,12 @@ class LoncapaProblem(object):
return
cmap
return
cmap
def
ungraded_response
(
self
,
xqueue_msg
,
queuekey
):
def
ungraded_response
(
self
,
xqueue_msg
,
queuekey
):
'''
"""
Handle any responses from the xqueue that do not contain grades
Handle any responses from the xqueue that do not contain grades
Will try to pass the queue message to all inputtypes that can handle ungraded responses
Will try to pass the queue message to all inputtypes that can handle ungraded responses
Does not return any value
Does not return any value
'''
"""
# check against each inputtype
# check against each inputtype
for
the_input
in
self
.
inputs
.
values
():
for
the_input
in
self
.
inputs
.
values
():
# if the input type has an ungraded function, pass in the values
# if the input type has an ungraded function, pass in the values
...
@@ -276,17 +276,17 @@ class LoncapaProblem(object):
...
@@ -276,17 +276,17 @@ class LoncapaProblem(object):
the_input
.
ungraded_response
(
xqueue_msg
,
queuekey
)
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
(e.g. for grading.)
(e.g. for grading.)
'''
"""
return
any
(
self
.
correct_map
.
is_queued
(
answer_id
)
for
answer_id
in
self
.
correct_map
)
return
any
(
self
.
correct_map
.
is_queued
(
answer_id
)
for
answer_id
in
self
.
correct_map
)
def
get_recentmost_queuetime
(
self
):
def
get_recentmost_queuetime
(
self
):
'''
"""
Returns a DateTime object that represents the timestamp of the most recent
Returns a DateTime object that represents the timestamp of the most recent
queueing request, or None if not queued
queueing request, or None if not queued
'''
"""
if
not
self
.
is_queued
():
if
not
self
.
is_queued
():
return
None
return
None
...
@@ -304,7 +304,7 @@ class LoncapaProblem(object):
...
@@ -304,7 +304,7 @@ class LoncapaProblem(object):
return
max
(
queuetimes
)
return
max
(
queuetimes
)
def
grade_answers
(
self
,
answers
):
def
grade_answers
(
self
,
answers
):
'''
"""
Grade student responses. Called by capa_module.check_problem.
Grade student responses. Called by capa_module.check_problem.
`answers` is a dict of all the entries from request.POST, but with the first part
`answers` is a dict of all the entries from request.POST, but with the first part
...
@@ -313,7 +313,7 @@ class LoncapaProblem(object):
...
@@ -313,7 +313,7 @@ class LoncapaProblem(object):
Thus, for example, input_ID123 -> ID123, and input_fromjs_ID123 -> fromjs_ID123
Thus, for example, input_ID123 -> ID123, and input_fromjs_ID123 -> fromjs_ID123
Calls the Response for each question in this problem, to do the actual grading.
Calls the Response for each question in this problem, to do the actual grading.
'''
"""
# if answers include File objects, convert them to filenames.
# if answers include File objects, convert them to filenames.
self
.
student_answers
=
convert_files_to_filenames
(
answers
)
self
.
student_answers
=
convert_files_to_filenames
(
answers
)
...
@@ -363,7 +363,6 @@ class LoncapaProblem(object):
...
@@ -363,7 +363,6 @@ class LoncapaProblem(object):
# start new with empty CorrectMap
# start new with empty CorrectMap
newcmap
=
CorrectMap
()
newcmap
=
CorrectMap
()
# Call each responsetype instance to do actual grading
# Call each responsetype instance to do actual grading
for
responder
in
self
.
responders
.
values
():
for
responder
in
self
.
responders
.
values
():
# File objects are passed only if responsetype explicitly allows
# File objects are passed only if responsetype explicitly allows
...
@@ -372,7 +371,8 @@ class LoncapaProblem(object):
...
@@ -372,7 +371,8 @@ class LoncapaProblem(object):
# an earlier submission, so for now skip these entirely.
# an earlier submission, so for now skip these entirely.
# TODO: figure out where to get file submissions when rescoring.
# TODO: figure out where to get file submissions when rescoring.
if
'filesubmission'
in
responder
.
allowed_inputfields
and
student_answers
is
None
:
if
'filesubmission'
in
responder
.
allowed_inputfields
and
student_answers
is
None
:
raise
Exception
(
"Cannot rescore problems with possible file submissions"
)
_
=
self
.
capa_system
.
i18n
.
ugettext
raise
Exception
(
_
(
"Cannot rescore problems with possible file submissions"
))
# use 'student_answers' only if it is provided, and if it might contain a file
# use 'student_answers' only if it is provided, and if it might contain a file
# submission that would not exist in the persisted "student_answers".
# submission that would not exist in the persisted "student_answers".
...
@@ -404,7 +404,7 @@ class LoncapaProblem(object):
...
@@ -404,7 +404,7 @@ class LoncapaProblem(object):
if
answer
:
if
answer
:
answer_map
[
entry
.
get
(
'id'
)]
=
contextualize_text
(
answer
,
self
.
context
)
answer_map
[
entry
.
get
(
'id'
)]
=
contextualize_text
(
answer
,
self
.
context
)
log
.
debug
(
'answer_map =
%
s'
%
answer_map
)
log
.
debug
(
'answer_map =
%
s'
,
answer_map
)
return
answer_map
return
answer_map
def
get_answer_ids
(
self
):
def
get_answer_ids
(
self
):
...
@@ -420,18 +420,18 @@ class LoncapaProblem(object):
...
@@ -420,18 +420,18 @@ class LoncapaProblem(object):
return
answer_ids
return
answer_ids
def
get_html
(
self
):
def
get_html
(
self
):
'''
"""
Main method called externally to get the HTML to be rendered for this capa Problem.
Main method called externally to get the HTML to be rendered for this capa Problem.
'''
"""
html
=
contextualize_text
(
etree
.
tostring
(
self
.
_extract_html
(
self
.
tree
)),
self
.
context
)
html
=
contextualize_text
(
etree
.
tostring
(
self
.
_extract_html
(
self
.
tree
)),
self
.
context
)
return
html
return
html
def
handle_input_ajax
(
self
,
data
):
def
handle_input_ajax
(
self
,
data
):
'''
"""
InputTypes can support specialized AJAX calls. Find the correct input and pass along the correct data
InputTypes can support specialized AJAX calls. Find the correct input and pass along the correct data
Also, parse out the dispatch from the get so that it can be passed onto the input type nicely
Also, parse out the dispatch from the get so that it can be passed onto the input type nicely
'''
"""
# pull out the id
# pull out the id
input_id
=
data
[
'input_id'
]
input_id
=
data
[
'input_id'
]
...
@@ -439,16 +439,16 @@ class LoncapaProblem(object):
...
@@ -439,16 +439,16 @@ class LoncapaProblem(object):
dispatch
=
data
[
'dispatch'
]
dispatch
=
data
[
'dispatch'
]
return
self
.
inputs
[
input_id
]
.
handle_ajax
(
dispatch
,
data
)
return
self
.
inputs
[
input_id
]
.
handle_ajax
(
dispatch
,
data
)
else
:
else
:
log
.
warning
(
"Could not find matching input for id:
%
s"
%
input_id
)
log
.
warning
(
"Could not find matching input for id:
%
s"
,
input_id
)
return
{}
return
{}
# ======= Private Methods Below ========
# ======= Private Methods Below ========
def
_process_includes
(
self
):
def
_process_includes
(
self
):
'''
"""
Handle any <include file="foo"> tags by reading in the specified file and inserting it
Handle any <include file="foo"> tags by reading in the specified file and inserting it
into our XML tree. Fail gracefully if debugging.
into our XML tree. Fail gracefully if debugging.
'''
"""
includes
=
self
.
tree
.
findall
(
'.//include'
)
includes
=
self
.
tree
.
findall
(
'.//include'
)
for
inc
in
includes
:
for
inc
in
includes
:
filename
=
inc
.
get
(
'file'
)
filename
=
inc
.
get
(
'file'
)
...
@@ -458,14 +458,12 @@ class LoncapaProblem(object):
...
@@ -458,14 +458,12 @@ class LoncapaProblem(object):
ifp
=
self
.
capa_system
.
filestore
.
open
(
filename
)
ifp
=
self
.
capa_system
.
filestore
.
open
(
filename
)
except
Exception
as
err
:
except
Exception
as
err
:
log
.
warning
(
log
.
warning
(
'Error
%
s in problem xml include:
%
s'
%
(
'Error
%
s in problem xml include:
%
s'
,
err
,
etree
.
tostring
(
inc
,
pretty_print
=
True
)
err
,
)
etree
.
tostring
(
inc
,
pretty_print
=
True
)
)
)
log
.
warning
(
log
.
warning
(
'Cannot find file
%
s in
%
s'
%
(
'Cannot find file
%
s in
%
s'
,
filename
,
self
.
capa_system
.
filestore
filename
,
self
.
capa_system
.
filestore
)
)
)
# if debugging, don't fail - just log error
# if debugging, don't fail - just log error
# TODO (vshnayder): need real error handling, display to users
# TODO (vshnayder): need real error handling, display to users
...
@@ -478,11 +476,11 @@ class LoncapaProblem(object):
...
@@ -478,11 +476,11 @@ class LoncapaProblem(object):
incxml
=
etree
.
XML
(
ifp
.
read
())
incxml
=
etree
.
XML
(
ifp
.
read
())
except
Exception
as
err
:
except
Exception
as
err
:
log
.
warning
(
log
.
warning
(
'Error
%
s in problem xml include:
%
s'
%
(
'Error
%
s in problem xml include:
%
s'
,
err
,
etree
.
tostring
(
inc
,
pretty_print
=
True
)
err
,
)
etree
.
tostring
(
inc
,
pretty_print
=
True
)
)
)
log
.
warning
(
'Cannot parse XML in
%
s'
%
(
filename
))
log
.
warning
(
'Cannot parse XML in
%
s'
,
(
filename
))
# if debugging, don't fail - just log error
# if debugging, don't fail - just log error
# TODO (vshnayder): same as above
# TODO (vshnayder): same as above
if
not
self
.
capa_system
.
DEBUG
:
if
not
self
.
capa_system
.
DEBUG
:
...
@@ -522,7 +520,7 @@ class LoncapaProblem(object):
...
@@ -522,7 +520,7 @@ class LoncapaProblem(object):
# Check that we are within the filestore tree.
# Check that we are within the filestore tree.
reldir
=
os
.
path
.
relpath
(
dir
,
self
.
capa_system
.
filestore
.
root_path
)
reldir
=
os
.
path
.
relpath
(
dir
,
self
.
capa_system
.
filestore
.
root_path
)
if
".."
in
reldir
:
if
".."
in
reldir
:
log
.
warning
(
"Ignoring Python directory outside of course:
%
r"
%
dir
)
log
.
warning
(
"Ignoring Python directory outside of course:
%
r"
,
dir
)
continue
continue
abs_dir
=
os
.
path
.
normpath
(
dir
)
abs_dir
=
os
.
path
.
normpath
(
dir
)
...
@@ -531,13 +529,13 @@ class LoncapaProblem(object):
...
@@ -531,13 +529,13 @@ class LoncapaProblem(object):
return
path
return
path
def
_extract_context
(
self
,
tree
):
def
_extract_context
(
self
,
tree
):
'''
"""
Extract content of <script>...</script> from the problem.xml file, and exec it in the
Extract content of <script>...</script> from the problem.xml file, and exec it in the
context of this problem. Provides ability to randomize problems, and also set
context of this problem. Provides ability to randomize problems, and also set
variables for problem answer checking.
variables for problem answer checking.
Problem XML goes to Python execution context. Runs everything in script tags.
Problem XML goes to Python execution context. Runs everything in script tags.
'''
"""
context
=
{}
context
=
{}
context
[
'seed'
]
=
self
.
seed
context
[
'seed'
]
=
self
.
seed
all_code
=
''
all_code
=
''
...
@@ -584,7 +582,7 @@ class LoncapaProblem(object):
...
@@ -584,7 +582,7 @@ class LoncapaProblem(object):
return
context
return
context
def
_extract_html
(
self
,
problemtree
):
# private
def
_extract_html
(
self
,
problemtree
):
# private
'''
"""
Main (private) function which converts Problem XML tree to HTML.
Main (private) function which converts Problem XML tree to HTML.
Calls itself recursively.
Calls itself recursively.
...
@@ -592,7 +590,7 @@ class LoncapaProblem(object):
...
@@ -592,7 +590,7 @@ class LoncapaProblem(object):
Calls render_html of Response instances to render responses into XHTML.
Calls render_html of Response instances to render responses into XHTML.
Used by get_html.
Used by get_html.
'''
"""
if
not
isinstance
(
problemtree
.
tag
,
basestring
):
if
not
isinstance
(
problemtree
.
tag
,
basestring
):
# Comment and ProcessingInstruction nodes are not Elements,
# Comment and ProcessingInstruction nodes are not Elements,
# and we're ok leaving those behind.
# and we're ok leaving those behind.
...
@@ -632,13 +630,17 @@ class LoncapaProblem(object):
...
@@ -632,13 +630,17 @@ class LoncapaProblem(object):
self
.
input_state
[
input_id
]
=
{}
self
.
input_state
[
input_id
]
=
{}
# do the rendering
# do the rendering
state
=
{
'value'
:
value
,
state
=
{
'status'
:
status
,
'value'
:
value
,
'id'
:
input_id
,
'status'
:
status
,
'input_state'
:
self
.
input_state
[
input_id
],
'id'
:
input_id
,
'feedback'
:
{
'message'
:
msg
,
'input_state'
:
self
.
input_state
[
input_id
],
'hint'
:
hint
,
'feedback'
:
{
'hintmode'
:
hintmode
,
}}
'message'
:
msg
,
'hint'
:
hint
,
'hintmode'
:
hintmode
,
}
}
input_type_cls
=
inputtypes
.
registry
.
get_class_for_tag
(
problemtree
.
tag
)
input_type_cls
=
inputtypes
.
registry
.
get_class_for_tag
(
problemtree
.
tag
)
# save the input type so that we can make ajax calls on it if we need to
# save the input type so that we can make ajax calls on it if we need to
...
@@ -678,7 +680,7 @@ class LoncapaProblem(object):
...
@@ -678,7 +680,7 @@ class LoncapaProblem(object):
return
tree
return
tree
def
_preprocess_problem
(
self
,
tree
):
# private
def
_preprocess_problem
(
self
,
tree
):
# private
'''
"""
Assign IDs to all the responses
Assign IDs to all the responses
Assign sub-IDs to all entries (textline, schematic, etc.)
Assign sub-IDs to all entries (textline, schematic, etc.)
Annoted correctness and value
Annoted correctness and value
...
@@ -687,7 +689,7 @@ class LoncapaProblem(object):
...
@@ -687,7 +689,7 @@ class LoncapaProblem(object):
Also create capa Response instances for each responsetype and save as self.responders
Also create capa Response instances for each responsetype and save as self.responders
Obtain all responder answers and save as self.responder_answers dict (key = response)
Obtain all responder answers and save as self.responder_answers dict (key = response)
'''
"""
response_id
=
1
response_id
=
1
self
.
responders
=
{}
self
.
responders
=
{}
for
response
in
tree
.
xpath
(
'//'
+
"|//"
.
join
(
responsetypes
.
registry
.
registered_tags
())):
for
response
in
tree
.
xpath
(
'//'
+
"|//"
.
join
(
responsetypes
.
registry
.
registered_tags
())):
...
...
common/lib/capa/capa/correctmap.py
View file @
7f742978
...
@@ -63,13 +63,13 @@ class CorrectMap(object):
...
@@ -63,13 +63,13 @@ class CorrectMap(object):
return
repr
(
self
.
cmap
)
return
repr
(
self
.
cmap
)
def
get_dict
(
self
):
def
get_dict
(
self
):
'''
"""
return dict version of self
return dict version of self
'''
"""
return
self
.
cmap
return
self
.
cmap
def
set_dict
(
self
,
correct_map
):
def
set_dict
(
self
,
correct_map
):
'''
"""
Set internal dict of CorrectMap to provided correct_map dict
Set internal dict of CorrectMap to provided correct_map dict
correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This
correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This
...
@@ -85,7 +85,7 @@ class CorrectMap(object):
...
@@ -85,7 +85,7 @@ class CorrectMap(object):
Special migration case:
Special migration case:
If correct_map is a one-level dict, then convert it to the new dict of dicts format.
If correct_map is a one-level dict, then convert it to the new dict of dicts format.
'''
"""
# empty current dict
# empty current dict
self
.
__init__
()
self
.
__init__
()
...
@@ -149,17 +149,17 @@ class CorrectMap(object):
...
@@ -149,17 +149,17 @@ class CorrectMap(object):
return
self
.
get_property
(
answer_id
,
'hintmode'
,
None
)
return
self
.
get_property
(
answer_id
,
'hintmode'
,
None
)
def
set_hint_and_mode
(
self
,
answer_id
,
hint
,
hintmode
):
def
set_hint_and_mode
(
self
,
answer_id
,
hint
,
hintmode
):
'''
"""
- hint : (string) HTML text for hint
- hint : (string) HTML text for hint
- hintmode : (string) mode for hint display ('always' or 'on_request')
- hintmode : (string) mode for hint display ('always' or 'on_request')
'''
"""
self
.
set_property
(
answer_id
,
'hint'
,
hint
)
self
.
set_property
(
answer_id
,
'hint'
,
hint
)
self
.
set_property
(
answer_id
,
'hintmode'
,
hintmode
)
self
.
set_property
(
answer_id
,
'hintmode'
,
hintmode
)
def
update
(
self
,
other_cmap
):
def
update
(
self
,
other_cmap
):
'''
"""
Update this CorrectMap with the contents of another CorrectMap
Update this CorrectMap with the contents of another CorrectMap
'''
"""
if
not
isinstance
(
other_cmap
,
CorrectMap
):
if
not
isinstance
(
other_cmap
,
CorrectMap
):
raise
Exception
(
'CorrectMap.update called with invalid argument
%
s'
%
other_cmap
)
raise
Exception
(
'CorrectMap.update called with invalid argument
%
s'
%
other_cmap
)
self
.
cmap
.
update
(
other_cmap
.
get_dict
())
self
.
cmap
.
update
(
other_cmap
.
get_dict
())
...
...
common/lib/capa/capa/customrender.py
View file @
7f742978
...
@@ -26,7 +26,7 @@ class MathRenderer(object):
...
@@ -26,7 +26,7 @@ class MathRenderer(object):
tags
=
[
'math'
]
tags
=
[
'math'
]
def
__init__
(
self
,
system
,
xml
):
def
__init__
(
self
,
system
,
xml
):
r
'''
r
"""
Render math using latex-like formatting.
Render math using latex-like formatting.
Examples:
Examples:
...
@@ -37,7 +37,7 @@ class MathRenderer(object):
...
@@ -37,7 +37,7 @@ class MathRenderer(object):
We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline]
We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline]
TODO: use shorter tags (but this will require converting problem XML files!)
TODO: use shorter tags (but this will require converting problem XML files!)
'''
"""
self
.
system
=
system
self
.
system
=
system
self
.
xml
=
xml
self
.
xml
=
xml
...
@@ -79,13 +79,13 @@ registry.register(MathRenderer)
...
@@ -79,13 +79,13 @@ registry.register(MathRenderer)
class
SolutionRenderer
(
object
):
class
SolutionRenderer
(
object
):
'''
"""
A solution is just a <span>...</span> which is given an ID, that is used for displaying an
A solution is just a <span>...</span> which is given an ID, that is used for displaying an
extended answer (a problem "solution") after "show answers" is pressed.
extended answer (a problem "solution") after "show answers" is pressed.
Note that the solution content is NOT rendered and returned in the HTML. It is obtained by an
Note that the solution content is NOT rendered and returned in the HTML. It is obtained by an
ajax call.
ajax call.
'''
"""
tags
=
[
'solution'
]
tags
=
[
'solution'
]
def
__init__
(
self
,
system
,
xml
):
def
__init__
(
self
,
system
,
xml
):
...
...
common/lib/capa/capa/inputtypes.py
View file @
7f742978
...
@@ -102,7 +102,8 @@ class Attribute(object):
...
@@ -102,7 +102,8 @@ class Attribute(object):
val
=
element
.
get
(
self
.
name
)
val
=
element
.
get
(
self
.
name
)
if
self
.
default
==
self
.
_sentinel
and
val
is
None
:
if
self
.
default
==
self
.
_sentinel
and
val
is
None
:
raise
ValueError
(
raise
ValueError
(
'Missing required attribute {0}.'
.
format
(
self
.
name
))
'Missing required attribute {0}.'
.
format
(
self
.
name
)
)
if
val
is
None
:
if
val
is
None
:
# not required, so return default
# not required, so return default
...
@@ -156,8 +157,9 @@ class InputTypeBase(object):
...
@@ -156,8 +157,9 @@ class InputTypeBase(object):
self
.
input_id
=
state
.
get
(
'id'
,
xml
.
get
(
'id'
))
self
.
input_id
=
state
.
get
(
'id'
,
xml
.
get
(
'id'
))
if
self
.
input_id
is
None
:
if
self
.
input_id
is
None
:
raise
ValueError
(
"input id state is None. xml is {0}"
.
format
(
raise
ValueError
(
etree
.
tostring
(
xml
)))
"input id state is None. xml is {0}"
.
format
(
etree
.
tostring
(
xml
))
)
self
.
value
=
state
.
get
(
'value'
,
''
)
self
.
value
=
state
.
get
(
'value'
,
''
)
...
@@ -259,8 +261,9 @@ class InputTypeBase(object):
...
@@ -259,8 +261,9 @@ class InputTypeBase(object):
'msg'
:
self
.
msg
,
'msg'
:
self
.
msg
,
'STATIC_URL'
:
self
.
capa_system
.
STATIC_URL
,
'STATIC_URL'
:
self
.
capa_system
.
STATIC_URL
,
}
}
context
.
update
((
a
,
v
)
for
(
context
.
update
(
a
,
v
)
in
self
.
loaded_attributes
.
iteritems
()
if
a
in
self
.
to_render
)
(
a
,
v
)
for
(
a
,
v
)
in
self
.
loaded_attributes
.
iteritems
()
if
a
in
self
.
to_render
)
context
.
update
(
self
.
_extra_context
())
context
.
update
(
self
.
_extra_context
())
return
context
return
context
...
@@ -394,7 +397,7 @@ class ChoiceGroup(InputTypeBase):
...
@@ -394,7 +397,7 @@ class ChoiceGroup(InputTypeBase):
@staticmethod
@staticmethod
def
extract_choices
(
element
):
def
extract_choices
(
element
):
'''
"""
Extracts choices for a few input types, such as ChoiceGroup, RadioGroup and
Extracts choices for a few input types, such as ChoiceGroup, RadioGroup and
CheckboxGroup.
CheckboxGroup.
...
@@ -402,7 +405,7 @@ class ChoiceGroup(InputTypeBase):
...
@@ -402,7 +405,7 @@ class ChoiceGroup(InputTypeBase):
TODO: allow order of choices to be randomized, following lon-capa spec. Use
TODO: allow order of choices to be randomized, following lon-capa spec. Use
"location" attribute, ie random, top, bottom.
"location" attribute, ie random, top, bottom.
'''
"""
choices
=
[]
choices
=
[]
...
@@ -574,7 +577,8 @@ class TextLine(InputTypeBase):
...
@@ -574,7 +577,8 @@ class TextLine(InputTypeBase):
# Preprocessor to insert between raw input and Mathjax
# Preprocessor to insert between raw input and Mathjax
self
.
preprocessor
=
{
self
.
preprocessor
=
{
'class_name'
:
self
.
loaded_attributes
[
'preprocessorClassName'
],
'class_name'
:
self
.
loaded_attributes
[
'preprocessorClassName'
],
'script_src'
:
self
.
loaded_attributes
[
'preprocessorSrc'
]}
'script_src'
:
self
.
loaded_attributes
[
'preprocessorSrc'
],
}
if
None
in
self
.
preprocessor
.
values
():
if
None
in
self
.
preprocessor
.
values
():
self
.
preprocessor
=
None
self
.
preprocessor
=
None
...
@@ -594,9 +598,6 @@ class FileSubmission(InputTypeBase):
...
@@ -594,9 +598,6 @@ class FileSubmission(InputTypeBase):
template
=
"filesubmission.html"
template
=
"filesubmission.html"
tags
=
[
'filesubmission'
]
tags
=
[
'filesubmission'
]
# pulled out for testing
submitted_msg
=
(
"Your file(s) have been submitted; as soon as your submission is"
" graded, this message will be replaced with the grader's feedback."
)
@staticmethod
@staticmethod
def
parse_files
(
files
):
def
parse_files
(
files
):
...
@@ -618,6 +619,11 @@ class FileSubmission(InputTypeBase):
...
@@ -618,6 +619,11 @@ class FileSubmission(InputTypeBase):
Do some magic to handle queueing status (render as "queued" instead of "incomplete"),
Do some magic to handle queueing status (render as "queued" instead of "incomplete"),
pull queue_len from the msg field. (TODO: get rid of the queue_len hack).
pull queue_len from the msg field. (TODO: get rid of the queue_len hack).
"""
"""
_
=
self
.
capa_system
.
i18n
.
ugettext
submitted_msg
=
_
(
"Your file(s) have been submitted. As soon as your submission is"
" graded, this message will be replaced with the grader's feedback."
)
self
.
submitted_msg
=
submitted_msg
# Check if problem has been queued
# Check if problem has been queued
self
.
queue_len
=
0
self
.
queue_len
=
0
# Flag indicating that the problem has been queued, 'msg' is length of
# Flag indicating that the problem has been queued, 'msg' is length of
...
@@ -625,7 +631,7 @@ class FileSubmission(InputTypeBase):
...
@@ -625,7 +631,7 @@ class FileSubmission(InputTypeBase):
if
self
.
status
==
'incomplete'
:
if
self
.
status
==
'incomplete'
:
self
.
status
=
'queued'
self
.
status
=
'queued'
self
.
queue_len
=
self
.
msg
self
.
queue_len
=
self
.
msg
self
.
msg
=
FileSubmission
.
submitted_msg
self
.
msg
=
self
.
submitted_msg
def
_extra_context
(
self
):
def
_extra_context
(
self
):
return
{
'queue_len'
:
self
.
queue_len
,
}
return
{
'queue_len'
:
self
.
queue_len
,
}
...
@@ -641,15 +647,13 @@ class CodeInput(InputTypeBase):
...
@@ -641,15 +647,13 @@ class CodeInput(InputTypeBase):
"""
"""
template
=
"codeinput.html"
template
=
"codeinput.html"
tags
=
[
'codeinput'
,
tags
=
[
'textbox'
,
'codeinput'
,
# Another (older) name--at some point we may want to make it use a
'textbox'
,
# non-codemirror editor.
# Another (older) name--at some point we may want to make it use a
]
# non-codemirror editor.
]
# pulled out for testing
submitted_msg
=
(
"Submitted. As soon as your submission is"
" graded, this message will be replaced with the grader's feedback."
)
@classmethod
@classmethod
def
get_attributes
(
cls
):
def
get_attributes
(
cls
):
...
@@ -665,7 +669,7 @@ class CodeInput(InputTypeBase):
...
@@ -665,7 +669,7 @@ class CodeInput(InputTypeBase):
Attribute
(
'linenumbers'
,
'true'
),
Attribute
(
'linenumbers'
,
'true'
),
# Template expects tabsize to be an int it can do math with
# Template expects tabsize to be an int it can do math with
Attribute
(
'tabsize'
,
4
,
transform
=
int
),
Attribute
(
'tabsize'
,
4
,
transform
=
int
),
]
]
def
setup_code_response_rendering
(
self
):
def
setup_code_response_rendering
(
self
):
"""
"""
...
@@ -686,7 +690,12 @@ class CodeInput(InputTypeBase):
...
@@ -686,7 +690,12 @@ class CodeInput(InputTypeBase):
self
.
msg
=
self
.
submitted_msg
self
.
msg
=
self
.
submitted_msg
def
setup
(
self
):
def
setup
(
self
):
''' setup this input type '''
""" setup this input type """
_
=
self
.
capa_system
.
i18n
.
ugettext
submitted_msg
=
_
(
"Your answer has been submitted. As soon as your submission is"
" graded, this message will be replaced with the grader's feedback."
)
self
.
submitted_msg
=
submitted_msg
self
.
setup_code_response_rendering
()
self
.
setup_code_response_rendering
()
def
_extra_context
(
self
):
def
_extra_context
(
self
):
...
@@ -699,7 +708,7 @@ class CodeInput(InputTypeBase):
...
@@ -699,7 +708,7 @@ class CodeInput(InputTypeBase):
@registry.register
@registry.register
class
MatlabInput
(
CodeInput
):
class
MatlabInput
(
CodeInput
):
'''
"""
InputType for handling Matlab code input
InputType for handling Matlab code input
TODO: API_KEY will go away once we have a way to specify it per-course
TODO: API_KEY will go away once we have a way to specify it per-course
...
@@ -710,17 +719,20 @@ class MatlabInput(CodeInput):
...
@@ -710,17 +719,20 @@ class MatlabInput(CodeInput):
%
api_key=API_KEY
%
api_key=API_KEY
</plot_payload>
</plot_payload>
</matlabinput>
</matlabinput>
'''
"""
template
=
"matlabinput.html"
template
=
"matlabinput.html"
tags
=
[
'matlabinput'
]
tags
=
[
'matlabinput'
]
plot_submitted_msg
=
(
"Submitted. As soon as a response is returned, "
"this message will be replaced by that feedback."
)
def
setup
(
self
):
def
setup
(
self
):
'''
"""
Handle matlab-specific parsing
Handle matlab-specific parsing
'''
"""
_
=
self
.
capa_system
.
i18n
.
ugettext
submitted_msg
=
_
(
"Submitted. As soon as a response is returned, "
"this message will be replaced by that feedback."
)
self
.
submitted_msg
=
submitted_msg
self
.
setup_code_response_rendering
()
self
.
setup_code_response_rendering
()
xml
=
self
.
xml
xml
=
self
.
xml
...
@@ -736,10 +748,10 @@ class MatlabInput(CodeInput):
...
@@ -736,10 +748,10 @@ class MatlabInput(CodeInput):
if
'queuestate'
in
self
.
input_state
and
self
.
input_state
[
'queuestate'
]
==
'queued'
:
if
'queuestate'
in
self
.
input_state
and
self
.
input_state
[
'queuestate'
]
==
'queued'
:
self
.
status
=
'queued'
self
.
status
=
'queued'
self
.
queue_len
=
1
self
.
queue_len
=
1
self
.
msg
=
self
.
plot_
submitted_msg
self
.
msg
=
self
.
submitted_msg
def
handle_ajax
(
self
,
dispatch
,
data
):
def
handle_ajax
(
self
,
dispatch
,
data
):
'''
"""
Handle AJAX calls directed to this input
Handle AJAX calls directed to this input
Args:
Args:
...
@@ -748,14 +760,14 @@ class MatlabInput(CodeInput):
...
@@ -748,14 +760,14 @@ class MatlabInput(CodeInput):
Returns:
Returns:
dict - 'success' - whether or not we successfully queued this submission
dict - 'success' - whether or not we successfully queued this submission
- 'message' - message to be rendered in case of error
- 'message' - message to be rendered in case of error
'''
"""
if
dispatch
==
'plot'
:
if
dispatch
==
'plot'
:
return
self
.
_plot_data
(
data
)
return
self
.
_plot_data
(
data
)
return
{}
return
{}
def
ungraded_response
(
self
,
queue_msg
,
queuekey
):
def
ungraded_response
(
self
,
queue_msg
,
queuekey
):
'''
"""
Handle the response from the XQueue
Handle the response from the XQueue
Stores the response in the input_state so it can be rendered later
Stores the response in the input_state so it can be rendered later
...
@@ -765,7 +777,7 @@ class MatlabInput(CodeInput):
...
@@ -765,7 +777,7 @@ class MatlabInput(CodeInput):
Returns:
Returns:
nothing
nothing
'''
"""
# check the queuekey against the saved queuekey
# check the queuekey against the saved queuekey
if
(
'queuestate'
in
self
.
input_state
and
self
.
input_state
[
'queuestate'
]
==
'queued'
if
(
'queuestate'
in
self
.
input_state
and
self
.
input_state
[
'queuestate'
]
==
'queued'
and
self
.
input_state
[
'queuekey'
]
==
queuekey
):
and
self
.
input_state
[
'queuekey'
]
==
queuekey
):
...
@@ -787,7 +799,7 @@ class MatlabInput(CodeInput):
...
@@ -787,7 +799,7 @@ class MatlabInput(CodeInput):
return
True
return
True
def
_extra_context
(
self
):
def
_extra_context
(
self
):
''' Set up additional context variables'''
""" Set up additional context variables"""
extra_context
=
{
extra_context
=
{
'queue_len'
:
str
(
self
.
queue_len
),
'queue_len'
:
str
(
self
.
queue_len
),
'queue_msg'
:
self
.
queue_msg
,
'queue_msg'
:
self
.
queue_msg
,
...
@@ -796,31 +808,31 @@ class MatlabInput(CodeInput):
...
@@ -796,31 +808,31 @@ class MatlabInput(CodeInput):
return
extra_context
return
extra_context
def
_parse_data
(
self
,
queue_msg
):
def
_parse_data
(
self
,
queue_msg
):
'''
"""
Parses the message out of the queue message
Parses the message out of the queue message
Args:
Args:
queue_msg (str) - a JSON encoded string
queue_msg (str) - a JSON encoded string
Returns:
Returns:
returns the value for the the key 'msg' in queue_msg
returns the value for the the key 'msg' in queue_msg
'''
"""
try
:
try
:
result
=
json
.
loads
(
queue_msg
)
result
=
json
.
loads
(
queue_msg
)
except
(
TypeError
,
ValueError
):
except
(
TypeError
,
ValueError
):
log
.
error
(
"External message should be a JSON serialized dict."
log
.
error
(
"External message should be a JSON serialized dict."
" Received queue_msg =
%
s"
%
queue_msg
)
" Received queue_msg =
%
s"
,
queue_msg
)
raise
raise
msg
=
result
[
'msg'
]
msg
=
result
[
'msg'
]
return
msg
return
msg
def
_plot_data
(
self
,
data
):
def
_plot_data
(
self
,
data
):
'''
"""
AJAX handler for the plot button
AJAX handler for the plot button
Args:
Args:
get (dict) - should have key 'submission' which contains the student submission
get (dict) - should have key 'submission' which contains the student submission
Returns:
Returns:
dict - 'success' - whether or not we successfully queued this submission
dict - 'success' - whether or not we successfully queued this submission
- 'message' - message to be rendered in case of error
- 'message' - message to be rendered in case of error
'''
"""
# only send data if xqueue exists
# only send data if xqueue exists
if
self
.
capa_system
.
xqueue
is
None
:
if
self
.
capa_system
.
xqueue
is
None
:
return
{
'success'
:
False
,
'message'
:
'Cannot connect to the queue'
}
return
{
'success'
:
False
,
'message'
:
'Cannot connect to the queue'
}
...
@@ -843,11 +855,15 @@ class MatlabInput(CodeInput):
...
@@ -843,11 +855,15 @@ class MatlabInput(CodeInput):
queue_name
=
self
.
queuename
)
queue_name
=
self
.
queuename
)
# construct xqueue body
# construct xqueue body
student_info
=
{
'anonymous_student_id'
:
anonymous_student_id
,
student_info
=
{
'submission_time'
:
qtime
}
'anonymous_student_id'
:
anonymous_student_id
,
contents
=
{
'grader_payload'
:
self
.
plot_payload
,
'submission_time'
:
qtime
'student_info'
:
json
.
dumps
(
student_info
),
}
'student_response'
:
response
}
contents
=
{
'grader_payload'
:
self
.
plot_payload
,
'student_info'
:
json
.
dumps
(
student_info
),
'student_response'
:
response
}
(
error
,
msg
)
=
qinterface
.
send_to_queue
(
header
=
xheader
,
(
error
,
msg
)
=
qinterface
.
send_to_queue
(
header
=
xheader
,
body
=
json
.
dumps
(
contents
))
body
=
json
.
dumps
(
contents
))
...
@@ -881,7 +897,8 @@ class Schematic(InputTypeBase):
...
@@ -881,7 +897,8 @@ class Schematic(InputTypeBase):
Attribute
(
'parts'
,
None
),
Attribute
(
'parts'
,
None
),
Attribute
(
'analyses'
,
None
),
Attribute
(
'analyses'
,
None
),
Attribute
(
'initial_value'
,
None
),
Attribute
(
'initial_value'
,
None
),
Attribute
(
'submit_analyses'
,
None
),
]
Attribute
(
'submit_analyses'
,
None
),
]
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
...
@@ -1011,10 +1028,10 @@ class ChemicalEquationInput(InputTypeBase):
...
@@ -1011,10 +1028,10 @@ class ChemicalEquationInput(InputTypeBase):
}
}
def
handle_ajax
(
self
,
dispatch
,
data
):
def
handle_ajax
(
self
,
dispatch
,
data
):
'''
"""
Since we only have chemcalc preview this input, check to see if it
Since we only have chemcalc preview this input, check to see if it
matches the corresponding dispatch and send it through if it does
matches the corresponding dispatch and send it through if it does
'''
"""
if
dispatch
==
'preview_chemcalc'
:
if
dispatch
==
'preview_chemcalc'
:
return
self
.
preview_chemcalc
(
data
)
return
self
.
preview_chemcalc
(
data
)
return
{}
return
{}
...
@@ -1097,10 +1114,10 @@ class FormulaEquationInput(InputTypeBase):
...
@@ -1097,10 +1114,10 @@ class FormulaEquationInput(InputTypeBase):
}
}
def
handle_ajax
(
self
,
dispatch
,
get
):
def
handle_ajax
(
self
,
dispatch
,
get
):
'''
"""
Since we only have formcalc preview this input, check to see if it
Since we only have formcalc preview this input, check to see if it
matches the corresponding dispatch and send it through if it does
matches the corresponding dispatch and send it through if it does
'''
"""
if
dispatch
==
'preview_formcalc'
:
if
dispatch
==
'preview_formcalc'
:
return
self
.
preview_formcalc
(
get
)
return
self
.
preview_formcalc
(
get
)
return
{}
return
{}
...
@@ -1407,7 +1424,7 @@ class AnnotationInput(InputTypeBase):
...
@@ -1407,7 +1424,7 @@ class AnnotationInput(InputTypeBase):
self
.
_validate_options
()
self
.
_validate_options
()
def
_find_options
(
self
):
def
_find_options
(
self
):
''' Returns an array of dicts where each dict represents an option. '''
""" Returns an array of dicts where each dict represents an option. """
elements
=
self
.
xml
.
findall
(
'./options/option'
)
elements
=
self
.
xml
.
findall
(
'./options/option'
)
return
[{
return
[{
'id'
:
index
,
'id'
:
index
,
...
@@ -1416,7 +1433,7 @@ class AnnotationInput(InputTypeBase):
...
@@ -1416,7 +1433,7 @@ class AnnotationInput(InputTypeBase):
}
for
(
index
,
option
)
in
enumerate
(
elements
)]
}
for
(
index
,
option
)
in
enumerate
(
elements
)]
def
_validate_options
(
self
):
def
_validate_options
(
self
):
''' Raises a ValueError if the choice attribute is missing or invalid. '''
""" Raises a ValueError if the choice attribute is missing or invalid. """
valid_choices
=
(
'correct'
,
'partially-correct'
,
'incorrect'
)
valid_choices
=
(
'correct'
,
'partially-correct'
,
'incorrect'
)
for
option
in
self
.
options
:
for
option
in
self
.
options
:
choice
=
option
[
'choice'
]
choice
=
option
[
'choice'
]
...
@@ -1427,7 +1444,7 @@ class AnnotationInput(InputTypeBase):
...
@@ -1427,7 +1444,7 @@ class AnnotationInput(InputTypeBase):
choice
,
', '
.
join
(
valid_choices
)))
choice
,
', '
.
join
(
valid_choices
)))
def
_unpack
(
self
,
json_value
):
def
_unpack
(
self
,
json_value
):
''' Unpacks the json input state into a dict. '''
""" Unpacks the json input state into a dict. """
d
=
json
.
loads
(
json_value
)
d
=
json
.
loads
(
json_value
)
if
type
(
d
)
!=
dict
:
if
type
(
d
)
!=
dict
:
d
=
{}
d
=
{}
...
...
common/lib/capa/capa/responsetypes.py
View file @
7f742978
#
#
# File: courseware/capa/responsetypes.py
# File: courseware/capa/responsetypes.py
#
#
'''
"""
Problem response evaluation. Handles checking of student responses,
Problem response evaluation. Handles checking of student responses,
of a variety of types.
of a variety of types.
Used by capa_problem.py
Used by capa_problem.py
'''
"""
# standard library imports
# standard library imports
import
abc
import
abc
...
@@ -57,25 +57,25 @@ CORRECTMAP_PY = None
...
@@ -57,25 +57,25 @@ CORRECTMAP_PY = None
class
LoncapaProblemError
(
Exception
):
class
LoncapaProblemError
(
Exception
):
'''
"""
Error in specification of a problem
Error in specification of a problem
'''
"""
pass
pass
class
ResponseError
(
Exception
):
class
ResponseError
(
Exception
):
'''
"""
Error for failure in processing a response, including
Error for failure in processing a response, including
exceptions that occur when executing a custom script.
exceptions that occur when executing a custom script.
'''
"""
pass
pass
class
StudentInputError
(
Exception
):
class
StudentInputError
(
Exception
):
'''
"""
Error for an invalid student input.
Error for an invalid student input.
For example, submitting a string when the problem expects a number
For example, submitting a string when the problem expects a number
'''
"""
pass
pass
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
...
@@ -130,7 +130,7 @@ class LoncapaResponse(object):
...
@@ -130,7 +130,7 @@ class LoncapaResponse(object):
required_attributes
=
[]
required_attributes
=
[]
def
__init__
(
self
,
xml
,
inputfields
,
context
,
system
):
def
__init__
(
self
,
xml
,
inputfields
,
context
,
system
):
'''
"""
Init is passed the following arguments:
Init is passed the following arguments:
- xml : ElementTree of this Response
- xml : ElementTree of this Response
...
@@ -138,7 +138,7 @@ class LoncapaResponse(object):
...
@@ -138,7 +138,7 @@ class LoncapaResponse(object):
- context : script processor context
- context : script processor context
- system : LoncapaSystem instance which provides OS, rendering, and user context
- system : LoncapaSystem instance which provides OS, rendering, and user context
'''
"""
self
.
xml
=
xml
self
.
xml
=
xml
self
.
inputfields
=
inputfields
self
.
inputfields
=
inputfields
self
.
context
=
context
self
.
context
=
context
...
@@ -146,6 +146,8 @@ class LoncapaResponse(object):
...
@@ -146,6 +146,8 @@ class LoncapaResponse(object):
self
.
id
=
xml
.
get
(
'id'
)
self
.
id
=
xml
.
get
(
'id'
)
# The LoncapaProblemError messages here do not need to be translated as they are
# only displayed to the user when settings.DEBUG is True
for
abox
in
inputfields
:
for
abox
in
inputfields
:
if
abox
.
tag
not
in
self
.
allowed_inputfields
:
if
abox
.
tag
not
in
self
.
allowed_inputfields
:
msg
=
"
%
s: cannot have input field
%
s"
%
(
msg
=
"
%
s: cannot have input field
%
s"
%
(
...
@@ -194,20 +196,20 @@ class LoncapaResponse(object):
...
@@ -194,20 +196,20 @@ class LoncapaResponse(object):
self
.
setup_response
()
self
.
setup_response
()
def
get_max_score
(
self
):
def
get_max_score
(
self
):
'''
"""
Return the total maximum points of all answer fields under this Response
Return the total maximum points of all answer fields under this Response
'''
"""
return
sum
(
self
.
maxpoints
.
values
())
return
sum
(
self
.
maxpoints
.
values
())
def
render_html
(
self
,
renderer
,
response_msg
=
''
):
def
render_html
(
self
,
renderer
,
response_msg
=
''
):
'''
"""
Return XHTML Element tree representation of this Response.
Return XHTML Element tree representation of this Response.
Arguments:
Arguments:
- renderer : procedure which produces HTML given an ElementTree
- renderer : procedure which produces HTML given an ElementTree
- response_msg: a message displayed at the end of the Response
- response_msg: a message displayed at the end of the Response
'''
"""
# render ourself as a <span> + our content
# render ourself as a <span> + our content
tree
=
etree
.
Element
(
'span'
)
tree
=
etree
.
Element
(
'span'
)
...
@@ -229,12 +231,12 @@ class LoncapaResponse(object):
...
@@ -229,12 +231,12 @@ class LoncapaResponse(object):
return
tree
return
tree
def
evaluate_answers
(
self
,
student_answers
,
old_cmap
):
def
evaluate_answers
(
self
,
student_answers
,
old_cmap
):
'''
"""
Called by capa_problem.LoncapaProblem to evaluate student answers, and to
Called by capa_problem.LoncapaProblem to evaluate student answers, and to
generate hints (if any).
generate hints (if any).
Returns the new CorrectMap, with (correctness,msg,hint,hintmode) for each answer_id.
Returns the new CorrectMap, with (correctness,msg,hint,hintmode) for each answer_id.
'''
"""
new_cmap
=
self
.
get_score
(
student_answers
)
new_cmap
=
self
.
get_score
(
student_answers
)
self
.
get_hints
(
convert_files_to_filenames
(
self
.
get_hints
(
convert_files_to_filenames
(
student_answers
),
new_cmap
,
old_cmap
)
student_answers
),
new_cmap
,
old_cmap
)
...
@@ -242,14 +244,14 @@ class LoncapaResponse(object):
...
@@ -242,14 +244,14 @@ class LoncapaResponse(object):
return
new_cmap
return
new_cmap
def
get_hints
(
self
,
student_answers
,
new_cmap
,
old_cmap
):
def
get_hints
(
self
,
student_answers
,
new_cmap
,
old_cmap
):
'''
"""
Generate adaptive hints for this problem based on student answers, the old CorrectMap,
Generate adaptive hints for this problem based on student answers, the old CorrectMap,
and the new CorrectMap produced by get_score.
and the new CorrectMap produced by get_score.
Does not return anything.
Does not return anything.
Modifies new_cmap, by adding hints to answer_id entries as appropriate.
Modifies new_cmap, by adding hints to answer_id entries as appropriate.
'''
"""
hintgroup
=
self
.
xml
.
find
(
'hintgroup'
)
hintgroup
=
self
.
xml
.
find
(
'hintgroup'
)
if
hintgroup
is
None
:
if
hintgroup
is
None
:
return
return
...
@@ -301,9 +303,10 @@ class LoncapaResponse(object):
...
@@ -301,9 +303,10 @@ class LoncapaResponse(object):
unsafely
=
self
.
capa_system
.
can_execute_unsafe_code
(),
unsafely
=
self
.
capa_system
.
can_execute_unsafe_code
(),
)
)
except
Exception
as
err
:
except
Exception
as
err
:
msg
=
'Error
%
s in evaluating hint function
%
s'
%
(
err
,
hintfn
)
_
=
self
.
capa_system
.
i18n
.
ugettext
msg
+=
"
\n
See XML source line
%
s"
%
getattr
(
msg
=
_
(
'Error {err} in evaluating hint function {hintfn}.'
)
.
format
(
err
=
err
,
hintfn
=
hintfn
)
self
.
xml
,
'sourceline'
,
'<unavailable>'
)
sourcenum
=
getattr
(
self
.
xml
,
'sourceline'
,
_
(
'(Source code line unavailable)'
))
msg
+=
"
\n
"
+
_
(
"See XML source line {sourcenum}."
)
.
format
(
sourcenum
=
sourcenum
)
raise
ResponseError
(
msg
)
raise
ResponseError
(
msg
)
new_cmap
.
set_dict
(
globals_dict
[
'new_cmap_dict'
])
new_cmap
.
set_dict
(
globals_dict
[
'new_cmap_dict'
])
...
@@ -346,24 +349,24 @@ class LoncapaResponse(object):
...
@@ -346,24 +349,24 @@ class LoncapaResponse(object):
@abc.abstractmethod
@abc.abstractmethod
def
get_score
(
self
,
student_answers
):
def
get_score
(
self
,
student_answers
):
'''
"""
Return a CorrectMap for the answers expected vs given. This includes
Return a CorrectMap for the answers expected vs given. This includes
(correctness, npoints, msg) for each answer_id.
(correctness, npoints, msg) for each answer_id.
Arguments:
Arguments:
- student_answers : dict of (answer_id, answer) where answer = student input (string)
- student_answers : dict of (answer_id, answer) where answer = student input (string)
'''
"""
pass
pass
@abc.abstractmethod
@abc.abstractmethod
def
get_answers
(
self
):
def
get_answers
(
self
):
'''
"""
Return a dict of (answer_id, answer_text) for each answer for this question.
Return a dict of (answer_id, answer_text) for each answer for this question.
'''
"""
pass
pass
def
check_hint_condition
(
self
,
hxml_set
,
student_answers
):
def
check_hint_condition
(
self
,
hxml_set
,
student_answers
):
'''
"""
Return a list of hints to show.
Return a list of hints to show.
- hxml_set : list of Element trees, each specifying a condition to be
- hxml_set : list of Element trees, each specifying a condition to be
...
@@ -373,7 +376,7 @@ class LoncapaResponse(object):
...
@@ -373,7 +376,7 @@ class LoncapaResponse(object):
Returns a list of names of hint conditions which were satisfied. Those are used
Returns a list of names of hint conditions which were satisfied. Those are used
to determine which hints are displayed.
to determine which hints are displayed.
'''
"""
pass
pass
def
setup_response
(
self
):
def
setup_response
(
self
):
...
@@ -673,9 +676,9 @@ class ChoiceResponse(LoncapaResponse):
...
@@ -673,9 +676,9 @@ class ChoiceResponse(LoncapaResponse):
'name'
)
for
choice
in
correct_xml
])
'name'
)
for
choice
in
correct_xml
])
def
assign_choice_names
(
self
):
def
assign_choice_names
(
self
):
'''
"""
Initialize name attributes in <choice> tags for this response.
Initialize name attributes in <choice> tags for this response.
'''
"""
for
index
,
choice
in
enumerate
(
self
.
xml
.
xpath
(
'//*[@id=$id]//choice'
,
for
index
,
choice
in
enumerate
(
self
.
xml
.
xpath
(
'//*[@id=$id]//choice'
,
id
=
self
.
xml
.
get
(
'id'
))):
id
=
self
.
xml
.
get
(
'id'
))):
...
@@ -729,12 +732,13 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -729,12 +732,13 @@ class MultipleChoiceResponse(LoncapaResponse):
self
.
correct_choices
=
[
self
.
correct_choices
=
[
contextualize_text
(
choice
.
get
(
'name'
),
self
.
context
)
contextualize_text
(
choice
.
get
(
'name'
),
self
.
context
)
for
choice
in
cxml
for
choice
in
cxml
if
contextualize_text
(
choice
.
get
(
'correct'
),
self
.
context
)
==
"true"
]
if
contextualize_text
(
choice
.
get
(
'correct'
),
self
.
context
)
==
"true"
]
def
mc_setup_response
(
self
):
def
mc_setup_response
(
self
):
'''
"""
Initialize name attributes in <choice> stanzas in the <choicegroup> in this response.
Initialize name attributes in <choice> stanzas in the <choicegroup> in this response.
'''
"""
i
=
0
i
=
0
for
response
in
self
.
xml
.
xpath
(
"choicegroup"
):
for
response
in
self
.
xml
.
xpath
(
"choicegroup"
):
rtype
=
response
.
get
(
'type'
)
rtype
=
response
.
get
(
'type'
)
...
@@ -749,9 +753,9 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -749,9 +753,9 @@ class MultipleChoiceResponse(LoncapaResponse):
choice
.
set
(
"name"
,
"choice_"
+
choice
.
get
(
"name"
))
choice
.
set
(
"name"
,
"choice_"
+
choice
.
get
(
"name"
))
def
get_score
(
self
,
student_answers
):
def
get_score
(
self
,
student_answers
):
'''
"""
grade student response.
grade student response.
'''
"""
# log.debug('%s: student_answers=%s, correct_choices=%s' % (
# log.debug('%s: student_answers=%s, correct_choices=%s' % (
# unicode(self), student_answers, self.correct_choices))
# unicode(self), student_answers, self.correct_choices))
if
(
self
.
answer_id
in
student_answers
if
(
self
.
answer_id
in
student_answers
...
@@ -794,9 +798,9 @@ class TrueFalseResponse(MultipleChoiceResponse):
...
@@ -794,9 +798,9 @@ class TrueFalseResponse(MultipleChoiceResponse):
@registry.register
@registry.register
class
OptionResponse
(
LoncapaResponse
):
class
OptionResponse
(
LoncapaResponse
):
'''
"""
TODO: handle direction and randomize
TODO: handle direction and randomize
'''
"""
tags
=
[
'optionresponse'
]
tags
=
[
'optionresponse'
]
hint_tag
=
'optionhint'
hint_tag
=
'optionhint'
...
@@ -828,10 +832,10 @@ class OptionResponse(LoncapaResponse):
...
@@ -828,10 +832,10 @@ class OptionResponse(LoncapaResponse):
@registry.register
@registry.register
class
NumericalResponse
(
LoncapaResponse
):
class
NumericalResponse
(
LoncapaResponse
):
'''
"""
This response type expects a number or formulaic expression that evaluates
This response type expects a number or formulaic expression that evaluates
to a number (e.g. `4+5/2^2`), and accepts with a tolerance.
to a number (e.g. `4+5/2^2`), and accepts with a tolerance.
'''
"""
tags
=
[
'numericalresponse'
]
tags
=
[
'numericalresponse'
]
hint_tag
=
'numericalhint'
hint_tag
=
'numericalhint'
...
@@ -877,19 +881,20 @@ class NumericalResponse(LoncapaResponse):
...
@@ -877,19 +881,20 @@ class NumericalResponse(LoncapaResponse):
log
.
debug
(
"Content error--answer '
%
s' is not a valid number"
,
self
.
correct_answer
)
log
.
debug
(
"Content error--answer '
%
s' is not a valid number"
,
self
.
correct_answer
)
_
=
self
.
capa_system
.
i18n
.
ugettext
_
=
self
.
capa_system
.
i18n
.
ugettext
raise
StudentInputError
(
raise
StudentInputError
(
_
(
"There was a problem with the staff answer to this problem"
)
_
(
"There was a problem with the staff answer to this problem
.
"
)
)
)
return
correct_ans
return
correct_ans
def
get_score
(
self
,
student_answers
):
def
get_score
(
self
,
student_answers
):
'''Grade a numeric response '''
"""Grade a numeric response"""
student_answer
=
student_answers
[
self
.
answer_id
]
student_answer
=
student_answers
[
self
.
answer_id
]
correct_float
=
self
.
get_staff_ans
()
correct_float
=
self
.
get_staff_ans
()
_
=
self
.
capa_system
.
i18n
.
ugettext
general_exception
=
StudentInputError
(
general_exception
=
StudentInputError
(
u"Could not interpret '{0}' as a number"
.
format
(
cgi
.
escape
(
student_answer
))
_
(
u"Could not interpret '{student_answer}' as a number."
)
.
format
(
student_answer
=
cgi
.
escape
(
student_answer
))
)
)
# Begin `evaluator` block
# Begin `evaluator` block
...
@@ -898,7 +903,7 @@ class NumericalResponse(LoncapaResponse):
...
@@ -898,7 +903,7 @@ class NumericalResponse(LoncapaResponse):
student_float
=
evaluator
({},
{},
student_answer
)
student_float
=
evaluator
({},
{},
student_answer
)
except
UndefinedVariable
as
undef_var
:
except
UndefinedVariable
as
undef_var
:
raise
StudentInputError
(
raise
StudentInputError
(
u"You may not use variables ({0}) in numerical problems"
.
format
(
undef_var
.
message
)
_
(
u"You may not use variables ({bad_variables}) in numerical problems."
)
.
format
(
bad_variables
=
undef_var
.
message
)
)
)
except
ValueError
as
val_err
:
except
ValueError
as
val_err
:
if
'factorial'
in
val_err
.
message
:
if
'factorial'
in
val_err
.
message
:
...
@@ -907,14 +912,14 @@ class NumericalResponse(LoncapaResponse):
...
@@ -907,14 +912,14 @@ class NumericalResponse(LoncapaResponse):
# ve.message will be: `factorial() only accepts integral values` or
# ve.message will be: `factorial() only accepts integral values` or
# `factorial() not defined for negative values`
# `factorial() not defined for negative values`
raise
StudentInputError
(
raise
StudentInputError
(
(
"factorial function evaluated outside its domain:"
_
(
"factorial function evaluated outside its domain:"
"'{0}'"
)
.
format
(
cgi
.
escape
(
student_answer
))
"'{student_answer}'"
)
.
format
(
student_answer
=
cgi
.
escape
(
student_answer
))
)
)
else
:
else
:
raise
general_exception
raise
general_exception
except
ParseException
:
except
ParseException
:
raise
StudentInputError
(
raise
StudentInputError
(
u"Invalid math syntax: '{0}'"
.
format
(
cgi
.
escape
(
student_answer
))
_
(
u"Invalid math syntax: '{student_answer}'"
)
.
format
(
student_answer
=
cgi
.
escape
(
student_answer
))
)
)
except
Exception
:
except
Exception
:
raise
general_exception
raise
general_exception
...
@@ -957,7 +962,7 @@ class NumericalResponse(LoncapaResponse):
...
@@ -957,7 +962,7 @@ class NumericalResponse(LoncapaResponse):
@registry.register
@registry.register
class
StringResponse
(
LoncapaResponse
):
class
StringResponse
(
LoncapaResponse
):
'''
"""
This response type allows one or more answers.
This response type allows one or more answers.
Additional answers are added by `additional_answer` tag.
Additional answers are added by `additional_answer` tag.
...
@@ -987,7 +992,7 @@ class StringResponse(LoncapaResponse):
...
@@ -987,7 +992,7 @@ class StringResponse(LoncapaResponse):
</hintpart >
</hintpart >
</hintgroup>
</hintgroup>
</stringresponse>
</stringresponse>
'''
"""
tags
=
[
'stringresponse'
]
tags
=
[
'stringresponse'
]
hint_tag
=
'stringhint'
hint_tag
=
'stringhint'
allowed_inputfields
=
[
'textline'
]
allowed_inputfields
=
[
'textline'
]
...
@@ -1020,7 +1025,7 @@ class StringResponse(LoncapaResponse):
...
@@ -1020,7 +1025,7 @@ class StringResponse(LoncapaResponse):
self
.
xml
.
remove
(
el
)
self
.
xml
.
remove
(
el
)
def
get_score
(
self
,
student_answers
):
def
get_score
(
self
,
student_answers
):
'''Grade a string response '''
"""Grade a string response """
student_answer
=
student_answers
[
self
.
answer_id
]
.
strip
()
student_answer
=
student_answers
[
self
.
answer_id
]
.
strip
()
correct
=
self
.
check_string
(
self
.
correct_answer
,
student_answer
)
correct
=
self
.
check_string
(
self
.
correct_answer
,
student_answer
)
return
CorrectMap
(
self
.
answer_id
,
'correct'
if
correct
else
'incorrect'
)
return
CorrectMap
(
self
.
answer_id
,
'correct'
if
correct
else
'incorrect'
)
...
@@ -1092,10 +1097,10 @@ class StringResponse(LoncapaResponse):
...
@@ -1092,10 +1097,10 @@ class StringResponse(LoncapaResponse):
@registry.register
@registry.register
class
CustomResponse
(
LoncapaResponse
):
class
CustomResponse
(
LoncapaResponse
):
'''
"""
Custom response. The python code to be run should be in <answer>...</answer>
Custom response. The python code to be run should be in <answer>...</answer>
or in a <script>...</script>
or in a <script>...</script>
'''
"""
tags
=
[
'customresponse'
]
tags
=
[
'customresponse'
]
...
@@ -1176,10 +1181,10 @@ class CustomResponse(LoncapaResponse):
...
@@ -1176,10 +1181,10 @@ class CustomResponse(LoncapaResponse):
self
.
code
=
answer
.
text
self
.
code
=
answer
.
text
def
get_score
(
self
,
student_answers
):
def
get_score
(
self
,
student_answers
):
'''
"""
student_answers is a dict with everything from request.POST, but with the first part
student_answers is a dict with everything from request.POST, but with the first part
of each key removed (the string before the first "_").
of each key removed (the string before the first "_").
'''
"""
log
.
debug
(
'
%
s: student_answers=
%
s'
,
unicode
(
self
),
student_answers
)
log
.
debug
(
'
%
s: student_answers=
%
s'
,
unicode
(
self
),
student_answers
)
...
@@ -1345,8 +1350,10 @@ class CustomResponse(LoncapaResponse):
...
@@ -1345,8 +1350,10 @@ class CustomResponse(LoncapaResponse):
# Raise an exception
# Raise an exception
else
:
else
:
log
.
error
(
traceback
.
format_exc
())
log
.
error
(
traceback
.
format_exc
())
_
=
self
.
capa_system
.
i18n
.
ugettext
raise
ResponseError
(
raise
ResponseError
(
"CustomResponse: check function returned an invalid dict"
)
_
(
"CustomResponse: check function returned an invalid dictionary!"
)
)
else
:
else
:
correct
=
[
'correct'
if
ret
else
'incorrect'
]
*
len
(
idset
)
correct
=
[
'correct'
if
ret
else
'incorrect'
]
*
len
(
idset
)
...
@@ -1385,7 +1392,7 @@ class CustomResponse(LoncapaResponse):
...
@@ -1385,7 +1392,7 @@ class CustomResponse(LoncapaResponse):
return
""
return
""
def
get_answers
(
self
):
def
get_answers
(
self
):
'''
"""
Give correct answer expected for this response.
Give correct answer expected for this response.
use default_answer_map from entry elements (eg textline),
use default_answer_map from entry elements (eg textline),
...
@@ -1393,7 +1400,7 @@ class CustomResponse(LoncapaResponse):
...
@@ -1393,7 +1400,7 @@ class CustomResponse(LoncapaResponse):
but for simplicity, if an "expect" attribute was given by the content author
but for simplicity, if an "expect" attribute was given by the content author
ie <customresponse expect="foo" ...> then that.
ie <customresponse expect="foo" ...> then that.
'''
"""
if
len
(
self
.
answer_ids
)
>
1
:
if
len
(
self
.
answer_ids
)
>
1
:
return
self
.
default_answer_map
return
self
.
default_answer_map
if
self
.
expect
:
if
self
.
expect
:
...
@@ -1401,12 +1408,12 @@ class CustomResponse(LoncapaResponse):
...
@@ -1401,12 +1408,12 @@ class CustomResponse(LoncapaResponse):
return
self
.
default_answer_map
return
self
.
default_answer_map
def
_handle_exec_exception
(
self
,
err
):
def
_handle_exec_exception
(
self
,
err
):
'''
"""
Handle an exception raised during the execution of
Handle an exception raised during the execution of
custom Python code.
custom Python code.
Raises a ResponseError
Raises a ResponseError
'''
"""
# Log the error if we are debugging
# Log the error if we are debugging
msg
=
'Error occurred while evaluating CustomResponse'
msg
=
'Error occurred while evaluating CustomResponse'
...
@@ -1498,11 +1505,11 @@ class CodeResponse(LoncapaResponse):
...
@@ -1498,11 +1505,11 @@ class CodeResponse(LoncapaResponse):
queue_name
=
None
queue_name
=
None
def
setup_response
(
self
):
def
setup_response
(
self
):
'''
"""
Configure CodeResponse from XML. Supports both CodeResponse and ExternalResponse XML
Configure CodeResponse from XML. Supports both CodeResponse and ExternalResponse XML
TODO: Determines whether in synchronous or asynchronous (queued) mode
TODO: Determines whether in synchronous or asynchronous (queued) mode
'''
"""
xml
=
self
.
xml
xml
=
self
.
xml
# TODO: XML can override external resource (grader/queue) URL
# TODO: XML can override external resource (grader/queue) URL
self
.
url
=
xml
.
get
(
'url'
,
None
)
self
.
url
=
xml
.
get
(
'url'
,
None
)
...
@@ -1522,12 +1529,12 @@ class CodeResponse(LoncapaResponse):
...
@@ -1522,12 +1529,12 @@ class CodeResponse(LoncapaResponse):
self
.
_parse_coderesponse_xml
(
codeparam
)
self
.
_parse_coderesponse_xml
(
codeparam
)
def
_parse_coderesponse_xml
(
self
,
codeparam
):
def
_parse_coderesponse_xml
(
self
,
codeparam
):
'''
"""
Parse the new CodeResponse XML format. When successful, sets:
Parse the new CodeResponse XML format. When successful, sets:
self.initial_display
self.initial_display
self.answer (an answer to display to the student in the LMS)
self.answer (an answer to display to the student in the LMS)
self.payload
self.payload
'''
"""
# Note that CodeResponse is agnostic to the specific contents of
# Note that CodeResponse is agnostic to the specific contents of
# grader_payload
# grader_payload
grader_payload
=
codeparam
.
find
(
'grader_payload'
)
grader_payload
=
codeparam
.
find
(
'grader_payload'
)
...
@@ -1614,9 +1621,10 @@ class CodeResponse(LoncapaResponse):
...
@@ -1614,9 +1621,10 @@ class CodeResponse(LoncapaResponse):
cmap
=
CorrectMap
()
cmap
=
CorrectMap
()
if
error
:
if
error
:
cmap
.
set
(
self
.
answer_id
,
queuestate
=
None
,
_
=
self
.
capa_system
.
i18n
.
ugettext
msg
=
'Unable to deliver your submission to grader. (Reason:
%
s.)'
error_msg
=
_
(
'Unable to deliver your submission to grader (Reason: {error_msg}).'
' Please try again later.'
%
msg
)
' Please try again later.'
)
.
format
(
error_msg
=
msg
)
cmap
.
set
(
self
.
answer_id
,
queuestate
=
None
,
msg
=
error_msg
)
else
:
else
:
# Queueing mechanism flags:
# Queueing mechanism flags:
# 1) Backend: Non-null CorrectMap['queuestate'] indicates that
# 1) Backend: Non-null CorrectMap['queuestate'] indicates that
...
@@ -1632,9 +1640,13 @@ class CodeResponse(LoncapaResponse):
...
@@ -1632,9 +1640,13 @@ class CodeResponse(LoncapaResponse):
def
update_score
(
self
,
score_msg
,
oldcmap
,
queuekey
):
def
update_score
(
self
,
score_msg
,
oldcmap
,
queuekey
):
"""Updates the user's score based on the returned message from the grader."""
"""Updates the user's score based on the returned message from the grader."""
(
valid_score_msg
,
correct
,
points
,
msg
)
=
self
.
_parse_score_msg
(
score_msg
)
(
valid_score_msg
,
correct
,
points
,
msg
)
=
self
.
_parse_score_msg
(
score_msg
)
_
=
self
.
capa_system
.
i18n
.
ugettext
if
not
valid_score_msg
:
if
not
valid_score_msg
:
oldcmap
.
set
(
self
.
answer_id
,
# Translators: 'grader' refers to the edX automatic code grader.
msg
=
'Invalid grader reply. Please contact the course staff.'
)
error_msg
=
_
(
'Invalid grader reply. Please contact the course staff.'
)
oldcmap
.
set
(
self
.
answer_id
,
msg
=
error_msg
)
return
oldcmap
return
oldcmap
correctness
=
'correct'
if
correct
else
'incorrect'
correctness
=
'correct'
if
correct
else
'incorrect'
...
@@ -1944,6 +1956,8 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1944,6 +1956,8 @@ class FormulaResponse(LoncapaResponse):
Each dictionary represents a test case for the answer.
Each dictionary represents a test case for the answer.
Returns a tuple of formula evaluation results.
Returns a tuple of formula evaluation results.
"""
"""
_
=
self
.
capa_system
.
i18n
.
ugettext
out
=
[]
out
=
[]
for
var_dict
in
var_dict_list
:
for
var_dict
in
var_dict_list
:
try
:
try
:
...
@@ -1959,7 +1973,7 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1959,7 +1973,7 @@ class FormulaResponse(LoncapaResponse):
cgi
.
escape
(
answer
)
cgi
.
escape
(
answer
)
)
)
raise
StudentInputError
(
raise
StudentInputError
(
"Invalid input: "
+
err
.
message
+
" not permitted in answer"
_
(
"Invalid input: {bad_input} not permitted in answer."
)
.
format
(
bad_input
=
err
.
message
)
)
)
except
ValueError
as
err
:
except
ValueError
as
err
:
if
'factorial'
in
err
.
message
:
if
'factorial'
in
err
.
message
:
...
@@ -1974,19 +1988,25 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1974,19 +1988,25 @@ class FormulaResponse(LoncapaResponse):
cgi
.
escape
(
answer
)
cgi
.
escape
(
answer
)
)
)
raise
StudentInputError
(
raise
StudentInputError
(
(
"factorial function not permitted in answer "
_
(
"factorial function not permitted in answer "
"for this problem. Provided answer was: "
"for this problem. Provided answer was: "
"{0}"
)
.
format
(
cgi
.
escape
(
answer
))
"{bad_input}"
)
.
format
(
bad_input
=
cgi
.
escape
(
answer
))
)
)
# If non-factorial related ValueError thrown, handle it the same as any other Exception
# If non-factorial related ValueError thrown, handle it the same as any other Exception
log
.
debug
(
'formularesponse: error
%
s in formula'
,
err
)
log
.
debug
(
'formularesponse: error
%
s in formula'
,
err
)
raise
StudentInputError
(
"Invalid input: Could not parse '
%
s' as a formula"
%
raise
StudentInputError
(
cgi
.
escape
(
answer
))
_
(
"Invalid input: Could not parse '{bad_input}' as a formula."
)
.
format
(
bad_input
=
cgi
.
escape
(
answer
)
)
)
except
Exception
as
err
:
except
Exception
as
err
:
# traceback.print_exc()
# traceback.print_exc()
log
.
debug
(
'formularesponse: error
%
s in formula'
,
err
)
log
.
debug
(
'formularesponse: error
%
s in formula'
,
err
)
raise
StudentInputError
(
"Invalid input: Could not parse '
%
s' as a formula"
%
raise
StudentInputError
(
cgi
.
escape
(
answer
))
_
(
"Invalid input: Could not parse '{bad_input}' as a formula"
)
.
format
(
bad_input
=
cgi
.
escape
(
answer
)
)
)
return
out
return
out
def
randomize_variables
(
self
,
samples
):
def
randomize_variables
(
self
,
samples
):
...
@@ -2124,7 +2144,9 @@ class SchematicResponse(LoncapaResponse):
...
@@ -2124,7 +2144,9 @@ class SchematicResponse(LoncapaResponse):
unsafely
=
self
.
capa_system
.
can_execute_unsafe_code
(),
unsafely
=
self
.
capa_system
.
can_execute_unsafe_code
(),
)
)
except
Exception
as
err
:
except
Exception
as
err
:
msg
=
'Error
%
s in evaluating SchematicResponse'
%
err
_
=
self
.
capa_system
.
i18n
.
ugettext
# Translators: 'SchematicResponse' is a problem type and should not be translated.
msg
=
_
(
'Error in evaluating SchematicResponse. The error was: {error_msg}'
)
.
format
(
error_msg
=
err
)
raise
ResponseError
(
msg
)
raise
ResponseError
(
msg
)
cmap
=
CorrectMap
()
cmap
=
CorrectMap
()
cmap
.
set_dict
(
dict
(
zip
(
sorted
(
self
.
answer_ids
),
self
.
context
[
'correct'
])))
cmap
.
set_dict
(
dict
(
zip
(
sorted
(
self
.
answer_ids
),
self
.
context
[
'correct'
])))
...
@@ -2674,6 +2696,7 @@ class ChoiceTextResponse(LoncapaResponse):
...
@@ -2674,6 +2696,7 @@ class ChoiceTextResponse(LoncapaResponse):
Returns True if and only if all student inputs are correct.
Returns True if and only if all student inputs are correct.
"""
"""
_
=
self
.
capa_system
.
i18n
.
ugettext
inputs_correct
=
True
inputs_correct
=
True
for
answer_name
,
answer_value
in
numtolerance_inputs
.
iteritems
():
for
answer_name
,
answer_value
in
numtolerance_inputs
.
iteritems
():
# If `self.corrrect_inputs` does not contain an entry for
# If `self.corrrect_inputs` does not contain an entry for
...
@@ -2691,11 +2714,11 @@ class ChoiceTextResponse(LoncapaResponse):
...
@@ -2691,11 +2714,11 @@ class ChoiceTextResponse(LoncapaResponse):
correct_ans
=
complex
(
correct_ans
)
correct_ans
=
complex
(
correct_ans
)
except
ValueError
:
except
ValueError
:
log
.
debug
(
log
.
debug
(
"Content error--answer
"
+
"Content error--answer
'
%
s' is not a valid complex number"
,
"'{0}' is not a valid complex number"
.
format
(
correct_ans
)
correct_ans
)
)
raise
StudentInputError
(
raise
StudentInputError
(
"The Staff answer could not be interpreted as a number."
_
(
"The Staff answer could not be interpreted as a number."
)
)
)
# Compare the student answer to the staff answer/ or to 0
# Compare the student answer to the staff answer/ or to 0
# if all that is important is verifying numericality
# if all that is important is verifying numericality
...
@@ -2708,14 +2731,13 @@ class ChoiceTextResponse(LoncapaResponse):
...
@@ -2708,14 +2731,13 @@ class ChoiceTextResponse(LoncapaResponse):
except
:
except
:
# Use the traceback-preserving version of re-raising with a
# Use the traceback-preserving version of re-raising with a
# different type
# different type
_
,
_
,
trace
=
sys
.
exc_info
()
__
,
__
,
trace
=
sys
.
exc_info
()
msg
=
_
(
"Could not interpret '{given_answer}' as a number."
)
.
format
(
raise
StudentInputError
(
given_answer
=
cgi
.
escape
(
answer_value
)
"Could not interpret '{0}' as a number{1}"
.
format
(
cgi
.
escape
(
answer_value
),
trace
)
)
)
msg
+=
" ({0})"
.
format
(
trace
)
raise
StudentInputError
(
msg
)
# Ignore the results of the comparisons which were just for
# Ignore the results of the comparisons which were just for
# Numerical Validation.
# Numerical Validation.
if
answer_name
in
self
.
correct_inputs
and
not
partial_correct
:
if
answer_name
in
self
.
correct_inputs
and
not
partial_correct
:
...
...
common/lib/capa/capa/tests/test_inputtypes.py
View file @
7f742978
...
@@ -313,7 +313,7 @@ class FileSubmissionTest(unittest.TestCase):
...
@@ -313,7 +313,7 @@ class FileSubmissionTest(unittest.TestCase):
'STATIC_URL'
:
'/dummy-static/'
,
'STATIC_URL'
:
'/dummy-static/'
,
'id'
:
'prob_1_2'
,
'id'
:
'prob_1_2'
,
'status'
:
'queued'
,
'status'
:
'queued'
,
'msg'
:
input_class
.
submitted_msg
,
'msg'
:
the_input
.
submitted_msg
,
'value'
:
'BumbleBee.py'
,
'value'
:
'BumbleBee.py'
,
'queue_len'
:
'3'
,
'queue_len'
:
'3'
,
'allowed_files'
:
'["runme.py", "nooooo.rb", "ohai.java"]'
,
'allowed_files'
:
'["runme.py", "nooooo.rb", "ohai.java"]'
,
...
@@ -362,7 +362,7 @@ class CodeInputTest(unittest.TestCase):
...
@@ -362,7 +362,7 @@ class CodeInputTest(unittest.TestCase):
'id'
:
'prob_1_2'
,
'id'
:
'prob_1_2'
,
'value'
:
'print "good evening"'
,
'value'
:
'print "good evening"'
,
'status'
:
'queued'
,
'status'
:
'queued'
,
'msg'
:
input_class
.
submitted_msg
,
'msg'
:
the_input
.
submitted_msg
,
'mode'
:
mode
,
'mode'
:
mode
,
'linenumbers'
:
linenumbers
,
'linenumbers'
:
linenumbers
,
'rows'
:
rows
,
'rows'
:
rows
,
...
@@ -415,7 +415,7 @@ class MatlabTest(unittest.TestCase):
...
@@ -415,7 +415,7 @@ class MatlabTest(unittest.TestCase):
'id'
:
'prob_1_2'
,
'id'
:
'prob_1_2'
,
'value'
:
'print "good evening"'
,
'value'
:
'print "good evening"'
,
'status'
:
'queued'
,
'status'
:
'queued'
,
'msg'
:
self
.
input_class
.
submitted_msg
,
'msg'
:
self
.
the_input
.
submitted_msg
,
'mode'
:
self
.
mode
,
'mode'
:
self
.
mode
,
'rows'
:
self
.
rows
,
'rows'
:
self
.
rows
,
'cols'
:
self
.
cols
,
'cols'
:
self
.
cols
,
...
@@ -444,7 +444,7 @@ class MatlabTest(unittest.TestCase):
...
@@ -444,7 +444,7 @@ class MatlabTest(unittest.TestCase):
'id'
:
'prob_1_2'
,
'id'
:
'prob_1_2'
,
'value'
:
'print "good evening"'
,
'value'
:
'print "good evening"'
,
'status'
:
'queued'
,
'status'
:
'queued'
,
'msg'
:
self
.
input_class
.
submitted_msg
,
'msg'
:
the_input
.
submitted_msg
,
'mode'
:
self
.
mode
,
'mode'
:
self
.
mode
,
'rows'
:
self
.
rows
,
'rows'
:
self
.
rows
,
'cols'
:
self
.
cols
,
'cols'
:
self
.
cols
,
...
@@ -501,7 +501,7 @@ class MatlabTest(unittest.TestCase):
...
@@ -501,7 +501,7 @@ class MatlabTest(unittest.TestCase):
'id'
:
'prob_1_2'
,
'id'
:
'prob_1_2'
,
'value'
:
'print "good evening"'
,
'value'
:
'print "good evening"'
,
'status'
:
'queued'
,
'status'
:
'queued'
,
'msg'
:
self
.
input_class
.
plot_
submitted_msg
,
'msg'
:
the_input
.
submitted_msg
,
'mode'
:
self
.
mode
,
'mode'
:
self
.
mode
,
'rows'
:
self
.
rows
,
'rows'
:
self
.
rows
,
'cols'
:
self
.
cols
,
'cols'
:
self
.
cols
,
...
...
common/lib/capa/capa/tests/test_responsetypes.py
View file @
7f742978
...
@@ -1150,7 +1150,7 @@ class NumericalResponseTest(ResponseTest):
...
@@ -1150,7 +1150,7 @@ class NumericalResponseTest(ResponseTest):
"""A fake gettext.Translations object."""
"""A fake gettext.Translations object."""
def
ugettext
(
self
,
text
):
def
ugettext
(
self
,
text
):
"""Return the 'translation' of `text`."""
"""Return the 'translation' of `text`."""
if
text
==
"There was a problem with the staff answer to this problem"
:
if
text
==
"There was a problem with the staff answer to this problem
.
"
:
text
=
"TRANSLATED!"
text
=
"TRANSLATED!"
return
text
return
text
problem
.
capa_system
.
i18n
=
FakeTranslations
()
problem
.
capa_system
.
i18n
=
FakeTranslations
()
...
...
common/lib/capa/capa/util.py
View file @
7f742978
...
@@ -8,10 +8,10 @@ default_tolerance = '0.001%'
...
@@ -8,10 +8,10 @@ default_tolerance = '0.001%'
def
compare_with_tolerance
(
v1
,
v2
,
tol
=
default_tolerance
):
def
compare_with_tolerance
(
v1
,
v2
,
tol
=
default_tolerance
):
'''
"""
Compare v1 to v2 with maximum tolerance tol.
Compare v1 to v2 with maximum tolerance tol.
tol is relative if it ends in
%
; otherwise, it is absolute
tol is relative if it ends in
%
; otherwise, it is absolute
.
- v1 : student result (float complex number)
- v1 : student result (float complex number)
- v2 : instructor result (float complex number)
- v2 : instructor result (float complex number)
...
@@ -26,7 +26,7 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance):
...
@@ -26,7 +26,7 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance):
Out[183]: -3.3881317890172014e-21
Out[183]: -3.3881317890172014e-21
In [212]: 1.9e24 - 1.9*10**24
In [212]: 1.9e24 - 1.9*10**24
Out[212]: 268435456.0
Out[212]: 268435456.0
'''
"""
relative
=
tol
.
endswith
(
'
%
'
)
relative
=
tol
.
endswith
(
'
%
'
)
if
relative
:
if
relative
:
tolerance_rel
=
evaluator
(
dict
(),
dict
(),
tol
[:
-
1
])
*
0.01
tolerance_rel
=
evaluator
(
dict
(),
dict
(),
tol
[:
-
1
])
*
0.01
...
@@ -46,8 +46,10 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance):
...
@@ -46,8 +46,10 @@ def compare_with_tolerance(v1, v2, tol=default_tolerance):
def
contextualize_text
(
text
,
context
):
# private
def
contextualize_text
(
text
,
context
):
# private
''' Takes a string with variables. E.g. $a+$b.
"""
Does a substitution of those variables from the context '''
Takes a string with variables. E.g. $a+$b.
Does a substitution of those variables from the context
"""
if
not
text
:
if
not
text
:
return
text
return
text
for
key
in
sorted
(
context
,
lambda
x
,
y
:
cmp
(
len
(
y
),
len
(
x
))):
for
key
in
sorted
(
context
,
lambda
x
,
y
:
cmp
(
len
(
y
),
len
(
x
))):
...
@@ -66,10 +68,10 @@ def contextualize_text(text, context): # private
...
@@ -66,10 +68,10 @@ def contextualize_text(text, context): # private
def
convert_files_to_filenames
(
answers
):
def
convert_files_to_filenames
(
answers
):
'''
"""
Check for File objects in the dict of submitted answers,
Check for File objects in the dict of submitted answers,
convert File objects to their filename (string)
convert File objects to their filename (string)
'''
"""
new_answers
=
dict
()
new_answers
=
dict
()
for
answer_id
in
answers
.
keys
():
for
answer_id
in
answers
.
keys
():
answer
=
answers
[
answer_id
]
answer
=
answers
[
answer_id
]
...
@@ -86,9 +88,9 @@ def is_list_of_files(files):
...
@@ -86,9 +88,9 @@ def is_list_of_files(files):
def
is_file
(
file_to_test
):
def
is_file
(
file_to_test
):
'''
"""
Duck typing to check if 'file_to_test' is a File object
Duck typing to check if 'file_to_test' is a File object
'''
"""
return
all
(
hasattr
(
file_to_test
,
method
)
for
method
in
[
'read'
,
'name'
])
return
all
(
hasattr
(
file_to_test
,
method
)
for
method
in
[
'read'
,
'name'
])
...
...
common/lib/capa/capa/xqueue_interface.py
View file @
7f742978
...
@@ -12,9 +12,9 @@ dateformat = '%Y%m%d%H%M%S'
...
@@ -12,9 +12,9 @@ dateformat = '%Y%m%d%H%M%S'
def
make_hashkey
(
seed
):
def
make_hashkey
(
seed
):
'''
"""
Generate a string key by hashing
Generate a string key by hashing
'''
"""
h
=
hashlib
.
md5
()
h
=
hashlib
.
md5
()
h
.
update
(
str
(
seed
))
h
.
update
(
str
(
seed
))
return
h
.
hexdigest
()
return
h
.
hexdigest
()
...
@@ -57,9 +57,9 @@ def parse_xreply(xreply):
...
@@ -57,9 +57,9 @@ def parse_xreply(xreply):
class
XQueueInterface
(
object
):
class
XQueueInterface
(
object
):
'''
"""
Interface to the external grading system
Interface to the external grading system
'''
"""
def
__init__
(
self
,
url
,
django_auth
,
requests_auth
=
None
):
def
__init__
(
self
,
url
,
django_auth
,
requests_auth
=
None
):
self
.
url
=
url
self
.
url
=
url
...
@@ -106,8 +106,10 @@ class XQueueInterface(object):
...
@@ -106,8 +106,10 @@ class XQueueInterface(object):
return
self
.
_http_post
(
self
.
url
+
'/xqueue/login/'
,
payload
)
return
self
.
_http_post
(
self
.
url
+
'/xqueue/login/'
,
payload
)
def
_send_to_queue
(
self
,
header
,
body
,
files_to_upload
):
def
_send_to_queue
(
self
,
header
,
body
,
files_to_upload
):
payload
=
{
'xqueue_header'
:
header
,
payload
=
{
'xqueue_body'
:
body
}
'xqueue_header'
:
header
,
'xqueue_body'
:
body
}
files
=
{}
files
=
{}
if
files_to_upload
is
not
None
:
if
files_to_upload
is
not
None
:
for
f
in
files_to_upload
:
for
f
in
files_to_upload
:
...
...
common/lib/xmodule/xmodule/capa_base.py
View file @
7f742978
...
@@ -492,27 +492,31 @@ class CapaMixin(CapaFields):
...
@@ -492,27 +492,31 @@ class CapaMixin(CapaFields):
if
answer_id
.
find
(
hidden_state_keyword
)
>=
0
:
if
answer_id
.
find
(
hidden_state_keyword
)
>=
0
:
student_answers
.
pop
(
answer_id
)
student_answers
.
pop
(
answer_id
)
#
Next, generate a fresh LoncapaProblem
# Next, generate a fresh LoncapaProblem
self
.
lcp
=
self
.
new_lcp
(
None
)
self
.
lcp
=
self
.
new_lcp
(
None
)
self
.
set_state_from_lcp
()
self
.
set_state_from_lcp
()
# Prepend a scary warning to the student
# Prepend a scary warning to the student
warning
=
'<div class="capa_reset">'
\
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
'<h2>Warning: The problem has been reset to its initial state!</h2>'
\
warning_msg
=
_
(
"Warning: The problem has been reset to its initial state!"
)
'The problem
\'
s state was corrupted by an invalid submission. '
\
warning
=
'<div class="capa_reset"> <h2> '
+
warning_msg
+
'</h2>'
'The submission consisted of:'
\
'<ul>'
# Translators: Following this message, there will be a bulleted list of items.
warning_msg
=
_
(
"The problem's state was corrupted by an invalid submission. The submission consisted of:"
)
warning
+=
warning_msg
+
'<ul>'
for
student_answer
in
student_answers
.
values
():
for
student_answer
in
student_answers
.
values
():
if
student_answer
!=
''
:
if
student_answer
!=
''
:
warning
+=
'<li>'
+
cgi
.
escape
(
student_answer
)
+
'</li>'
warning
+=
'<li>'
+
cgi
.
escape
(
student_answer
)
+
'</li>'
warning
+=
'</ul>'
\
'If this error persists, please contact the course staff.'
\
warning_msg
=
_
(
'If this error persists, please contact the course staff.'
)
'</div>'
warning
+=
'</ul>'
+
warning_msg
+
'</div>'
html
=
warning
html
=
warning
try
:
try
:
html
+=
self
.
lcp
.
get_html
()
html
+=
self
.
lcp
.
get_html
()
except
Exception
:
# Couldn't do it. Give up
except
Exception
:
# pylint: disable=broad-except
# Couldn't do it. Give up.
log
.
exception
(
"Unable to generate html from LoncapaProblem"
)
log
.
exception
(
"Unable to generate html from LoncapaProblem"
)
raise
raise
...
@@ -541,20 +545,22 @@ class CapaMixin(CapaFields):
...
@@ -541,20 +545,22 @@ class CapaMixin(CapaFields):
else
:
else
:
check_button
=
False
check_button
=
False
content
=
{
'name'
:
self
.
display_name_with_default
,
content
=
{
'html'
:
html
,
'name'
:
self
.
display_name_with_default
,
'weight'
:
self
.
weight
,
'html'
:
html
,
}
'weight'
:
self
.
weight
,
}
context
=
{
'problem'
:
content
,
'id'
:
self
.
id
,
context
=
{
'check_button'
:
check_button
,
'problem'
:
content
,
'reset_button'
:
self
.
should_show_reset_button
(),
'id'
:
self
.
id
,
'save_button'
:
self
.
should_show_save_button
(),
'check_button'
:
check_button
,
'answer_available'
:
self
.
answer_available
(),
'reset_button'
:
self
.
should_show_reset_button
(),
'attempts_used'
:
self
.
attempts
,
'save_button'
:
self
.
should_show_save_button
(),
'attempts_allowed'
:
self
.
max_attempts
,
'answer_available'
:
self
.
answer_available
(),
}
'attempts_used'
:
self
.
attempts
,
'attempts_allowed'
:
self
.
max_attempts
,
}
html
=
self
.
runtime
.
render_template
(
'problem.html'
,
context
)
html
=
self
.
runtime
.
render_template
(
'problem.html'
,
context
)
...
@@ -563,7 +569,7 @@ class CapaMixin(CapaFields):
...
@@ -563,7 +569,7 @@ class CapaMixin(CapaFields):
id
=
self
.
location
.
html_id
(),
ajax_url
=
self
.
runtime
.
ajax_url
id
=
self
.
location
.
html_id
(),
ajax_url
=
self
.
runtime
.
ajax_url
)
+
html
+
"</div>"
)
+
html
+
"</div>"
#
n
ow do all the substitutions which the LMS module_render normally does, but
#
N
ow do all the substitutions which the LMS module_render normally does, but
# we need to do here explicitly since we can get called for our HTML via AJAX
# we need to do here explicitly since we can get called for our HTML via AJAX
html
=
self
.
runtime
.
replace_urls
(
html
)
html
=
self
.
runtime
.
replace_urls
(
html
)
if
self
.
runtime
.
replace_course_urls
:
if
self
.
runtime
.
replace_course_urls
:
...
@@ -855,17 +861,19 @@ class CapaMixin(CapaFields):
...
@@ -855,17 +861,19 @@ class CapaMixin(CapaFields):
answers
=
self
.
make_dict_of_responses
(
data
)
answers
=
self
.
make_dict_of_responses
(
data
)
event_info
[
'answers'
]
=
convert_files_to_filenames
(
answers
)
event_info
[
'answers'
]
=
convert_files_to_filenames
(
answers
)
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
# Too late. Cannot submit
# Too late. Cannot submit
if
self
.
closed
():
if
self
.
closed
():
event_info
[
'failure'
]
=
'closed'
event_info
[
'failure'
]
=
'closed'
self
.
runtime
.
track_function
(
'problem_check_fail'
,
event_info
)
self
.
runtime
.
track_function
(
'problem_check_fail'
,
event_info
)
raise
NotFoundError
(
'Problem is closed'
)
raise
NotFoundError
(
_
(
"Problem is closed."
)
)
# Problem submitted. Student should reset before checking again
# Problem submitted. Student should reset before checking again
if
self
.
done
and
self
.
rerandomize
==
"always"
:
if
self
.
done
and
self
.
rerandomize
==
"always"
:
event_info
[
'failure'
]
=
'unreset'
event_info
[
'failure'
]
=
'unreset'
self
.
runtime
.
track_function
(
'problem_check_fail'
,
event_info
)
self
.
runtime
.
track_function
(
'problem_check_fail'
,
event_info
)
raise
NotFoundError
(
'Problem must be reset before it can be checked again'
)
raise
NotFoundError
(
_
(
"Problem must be reset before it can be checked again."
)
)
# Problem queued. Students must wait a specified waittime before they are allowed to submit
# Problem queued. Students must wait a specified waittime before they are allowed to submit
if
self
.
lcp
.
is_queued
():
if
self
.
lcp
.
is_queued
():
...
@@ -873,7 +881,7 @@ class CapaMixin(CapaFields):
...
@@ -873,7 +881,7 @@ class CapaMixin(CapaFields):
prev_submit_time
=
self
.
lcp
.
get_recentmost_queuetime
()
prev_submit_time
=
self
.
lcp
.
get_recentmost_queuetime
()
waittime_between_requests
=
self
.
runtime
.
xqueue
[
'waittime'
]
waittime_between_requests
=
self
.
runtime
.
xqueue
[
'waittime'
]
if
(
current_time
-
prev_submit_time
)
.
total_seconds
()
<
waittime_between_requests
:
if
(
current_time
-
prev_submit_time
)
.
total_seconds
()
<
waittime_between_requests
:
msg
=
u'You must wait at least {wait} seconds between submissions'
.
format
(
msg
=
_
(
u"You must wait at least {wait} seconds between submissions."
)
.
format
(
wait
=
waittime_between_requests
)
wait
=
waittime_between_requests
)
return
{
'success'
:
msg
,
'html'
:
''
}
# Prompts a modal dialog in ajax callback
return
{
'success'
:
msg
,
'html'
:
''
}
# Prompts a modal dialog in ajax callback
...
@@ -899,7 +907,8 @@ class CapaMixin(CapaFields):
...
@@ -899,7 +907,8 @@ class CapaMixin(CapaFields):
# Otherwise, display just an error message,
# Otherwise, display just an error message,
# without a stack trace
# without a stack trace
else
:
else
:
msg
=
u"Error: {msg}"
.
format
(
msg
=
inst
.
message
)
# Translators: {msg} will be replaced with a problem's error message.
msg
=
_
(
u"Error: {msg}"
)
.
format
(
msg
=
inst
.
message
)
return
{
'success'
:
msg
}
return
{
'success'
:
msg
}
...
@@ -936,9 +945,10 @@ class CapaMixin(CapaFields):
...
@@ -936,9 +945,10 @@ class CapaMixin(CapaFields):
# render problem into HTML
# render problem into HTML
html
=
self
.
get_problem_html
(
encapsulate
=
False
)
html
=
self
.
get_problem_html
(
encapsulate
=
False
)
return
{
'success'
:
success
,
return
{
'contents'
:
html
,
'success'
:
success
,
}
'contents'
:
html
,
}
def
rescore_problem
(
self
):
def
rescore_problem
(
self
):
"""
"""
...
@@ -958,15 +968,18 @@ class CapaMixin(CapaFields):
...
@@ -958,15 +968,18 @@ class CapaMixin(CapaFields):
"""
"""
event_info
=
{
'state'
:
self
.
lcp
.
get_state
(),
'problem_id'
:
self
.
location
.
url
()}
event_info
=
{
'state'
:
self
.
lcp
.
get_state
(),
'problem_id'
:
self
.
location
.
url
()}
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
if
not
self
.
lcp
.
supports_rescoring
():
if
not
self
.
lcp
.
supports_rescoring
():
event_info
[
'failure'
]
=
'unsupported'
event_info
[
'failure'
]
=
'unsupported'
self
.
runtime
.
track_function
(
'problem_rescore_fail'
,
event_info
)
self
.
runtime
.
track_function
(
'problem_rescore_fail'
,
event_info
)
raise
NotImplementedError
(
"Problem's definition does not support rescoring"
)
# Translators: 'rescoring' refers to the act of re-submitting a student's solution so it can get a new score.
raise
NotImplementedError
(
_
(
"Problem's definition does not support rescoring."
))
if
not
self
.
done
:
if
not
self
.
done
:
event_info
[
'failure'
]
=
'unanswered'
event_info
[
'failure'
]
=
'unanswered'
self
.
runtime
.
track_function
(
'problem_rescore_fail'
,
event_info
)
self
.
runtime
.
track_function
(
'problem_rescore_fail'
,
event_info
)
raise
NotFoundError
(
'Problem must be answered before it can be graded again'
)
raise
NotFoundError
(
_
(
"Problem must be answered before it can be graded again."
)
)
# get old score, for comparison:
# get old score, for comparison:
orig_score
=
self
.
lcp
.
get_score
()
orig_score
=
self
.
lcp
.
get_score
()
...
@@ -1032,32 +1045,40 @@ class CapaMixin(CapaFields):
...
@@ -1032,32 +1045,40 @@ class CapaMixin(CapaFields):
answers
=
self
.
make_dict_of_responses
(
data
)
answers
=
self
.
make_dict_of_responses
(
data
)
event_info
[
'answers'
]
=
answers
event_info
[
'answers'
]
=
answers
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
# Too late. Cannot submit
# Too late. Cannot submit
if
self
.
closed
()
and
not
self
.
max_attempts
==
0
:
if
self
.
closed
()
and
not
self
.
max_attempts
==
0
:
event_info
[
'failure'
]
=
'closed'
event_info
[
'failure'
]
=
'closed'
self
.
runtime
.
track_function
(
'save_problem_fail'
,
event_info
)
self
.
runtime
.
track_function
(
'save_problem_fail'
,
event_info
)
return
{
'success'
:
False
,
return
{
'msg'
:
"Problem is closed"
}
'success'
:
False
,
# Translators: 'closed' means the problem's due date has passed. You may no longer attempt to solve the problem.
'msg'
:
_
(
"Problem is closed."
)
}
# Problem submitted. Student should reset before saving
# Problem submitted. Student should reset before saving
# again.
# again.
if
self
.
done
and
self
.
rerandomize
==
"always"
:
if
self
.
done
and
self
.
rerandomize
==
"always"
:
event_info
[
'failure'
]
=
'done'
event_info
[
'failure'
]
=
'done'
self
.
runtime
.
track_function
(
'save_problem_fail'
,
event_info
)
self
.
runtime
.
track_function
(
'save_problem_fail'
,
event_info
)
return
{
'success'
:
False
,
return
{
'msg'
:
"Problem needs to be reset prior to save"
}
'success'
:
False
,
'msg'
:
_
(
"Problem needs to be reset prior to save."
)
}
self
.
lcp
.
student_answers
=
answers
self
.
lcp
.
student_answers
=
answers
self
.
set_state_from_lcp
()
self
.
set_state_from_lcp
()
self
.
runtime
.
track_function
(
'save_problem_success'
,
event_info
)
self
.
runtime
.
track_function
(
'save_problem_success'
,
event_info
)
msg
=
"Your answers have been saved"
msg
=
_
(
"Your answers have been saved."
)
if
not
self
.
max_attempts
==
0
:
if
not
self
.
max_attempts
==
0
:
msg
+=
" but not graded. Hit 'Check' to grade them."
msg
=
_
(
"Your answers have been saved but not graded. Click 'Check' to grade them."
)
return
{
'success'
:
True
,
return
{
'msg'
:
msg
}
'success'
:
True
,
'msg'
:
msg
,
}
def
reset_problem
(
self
,
_data
):
def
reset_problem
(
self
,
_data
):
"""
"""
...
@@ -1074,18 +1095,24 @@ class CapaMixin(CapaFields):
...
@@ -1074,18 +1095,24 @@ class CapaMixin(CapaFields):
event_info
=
dict
()
event_info
=
dict
()
event_info
[
'old_state'
]
=
self
.
lcp
.
get_state
()
event_info
[
'old_state'
]
=
self
.
lcp
.
get_state
()
event_info
[
'problem_id'
]
=
self
.
location
.
url
()
event_info
[
'problem_id'
]
=
self
.
location
.
url
()
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
if
self
.
closed
():
if
self
.
closed
():
event_info
[
'failure'
]
=
'closed'
event_info
[
'failure'
]
=
'closed'
self
.
runtime
.
track_function
(
'reset_problem_fail'
,
event_info
)
self
.
runtime
.
track_function
(
'reset_problem_fail'
,
event_info
)
return
{
'success'
:
False
,
return
{
'error'
:
"Problem is closed"
}
'success'
:
False
,
# Translators: 'closed' means the problem's due date has passed. You may no longer attempt to solve the problem.
'error'
:
_
(
"Problem is closed."
),
}
if
not
self
.
done
:
if
not
self
.
done
:
event_info
[
'failure'
]
=
'not_done'
event_info
[
'failure'
]
=
'not_done'
self
.
runtime
.
track_function
(
'reset_problem_fail'
,
event_info
)
self
.
runtime
.
track_function
(
'reset_problem_fail'
,
event_info
)
return
{
'success'
:
False
,
return
{
'error'
:
"Refresh the page and make an attempt before resetting."
}
'success'
:
False
,
'error'
:
_
(
"Refresh the page and make an attempt before resetting."
),
}
if
self
.
rerandomize
in
[
"always"
,
"onreset"
]:
if
self
.
rerandomize
in
[
"always"
,
"onreset"
]:
# Reset random number generator seed.
# Reset random number generator seed.
...
@@ -1100,5 +1127,7 @@ class CapaMixin(CapaFields):
...
@@ -1100,5 +1127,7 @@ class CapaMixin(CapaFields):
event_info
[
'new_state'
]
=
self
.
lcp
.
get_state
()
event_info
[
'new_state'
]
=
self
.
lcp
.
get_state
()
self
.
runtime
.
track_function
(
'reset_problem'
,
event_info
)
self
.
runtime
.
track_function
(
'reset_problem'
,
event_info
)
return
{
'success'
:
True
,
return
{
'html'
:
self
.
get_problem_html
(
encapsulate
=
False
)}
'success'
:
True
,
'html'
:
self
.
get_problem_html
(
encapsulate
=
False
),
}
common/lib/xmodule/xmodule/capa_module.py
View file @
7f742978
...
@@ -62,12 +62,14 @@ class CapaModule(CapaMixin, XModule):
...
@@ -62,12 +62,14 @@ class CapaModule(CapaMixin, XModule):
'ungraded_response'
:
self
.
handle_ungraded_response
'ungraded_response'
:
self
.
handle_ungraded_response
}
}
generic_error_message
=
(
_
=
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
generic_error_message
=
_
(
"We're sorry, there was an error with processing your request. "
"We're sorry, there was an error with processing your request. "
"Please try reloading your page and trying again."
"Please try reloading your page and trying again."
)
)
not_found_error_message
=
(
not_found_error_message
=
_
(
"The state of this problem has changed since you loaded this page. "
"The state of this problem has changed since you loaded this page. "
"Please refresh your page."
"Please refresh your page."
)
)
...
...
lms/djangoapps/instructor_task/tests/test_integration.py
View file @
7f742978
...
@@ -265,10 +265,10 @@ class TestRescoringTask(TestIntegrationTask):
...
@@ -265,10 +265,10 @@ class TestRescoringTask(TestIntegrationTask):
self
.
assertEqual
(
instructor_task
.
task_state
,
FAILURE
)
self
.
assertEqual
(
instructor_task
.
task_state
,
FAILURE
)
status
=
json
.
loads
(
instructor_task
.
task_output
)
status
=
json
.
loads
(
instructor_task
.
task_output
)
self
.
assertEqual
(
status
[
'exception'
],
'NotImplementedError'
)
self
.
assertEqual
(
status
[
'exception'
],
'NotImplementedError'
)
self
.
assertEqual
(
status
[
'message'
],
"Problem's definition does not support rescoring"
)
self
.
assertEqual
(
status
[
'message'
],
"Problem's definition does not support rescoring
.
"
)
status
=
InstructorTaskModuleTestCase
.
get_task_status
(
instructor_task
.
task_id
)
status
=
InstructorTaskModuleTestCase
.
get_task_status
(
instructor_task
.
task_id
)
self
.
assertEqual
(
status
[
'message'
],
"Problem's definition does not support rescoring"
)
self
.
assertEqual
(
status
[
'message'
],
"Problem's definition does not support rescoring
.
"
)
def
define_randomized_custom_response_problem
(
self
,
problem_url_name
,
redefine
=
False
):
def
define_randomized_custom_response_problem
(
self
,
problem_url_name
,
redefine
=
False
):
"""
"""
...
...
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