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
d711d29d
Commit
d711d29d
authored
Sep 04, 2013
by
Sarina Canelake
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Clean up pep8/pylint
parent
e071ebb9
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
151 additions
and
108 deletions
+151
-108
common/lib/capa/capa/responsetypes.py
+151
-108
No files found.
common/lib/capa/capa/responsetypes.py
View file @
d711d29d
...
@@ -35,17 +35,17 @@ from calc import evaluator, UndefinedVariable
...
@@ -35,17 +35,17 @@ from calc import evaluator, UndefinedVariable
from
.
import
correctmap
from
.
import
correctmap
from
datetime
import
datetime
from
datetime
import
datetime
from
pytz
import
UTC
from
pytz
import
UTC
from
.util
import
*
from
.util
import
compare_with_tolerance
,
contextualize_text
,
convert_files_to_filenames
,
is_list_of_files
,
find_with_default
from
lxml
import
etree
from
lxml
import
etree
from
lxml.html.soupparser
import
fromstring
as
fromstring_bs
# uses Beautiful Soup!!! FIXME?
from
lxml.html.soupparser
import
fromstring
as
fromstring_bs
# uses Beautiful Soup!!! FIXME?
import
capa.xqueue_interface
as
xqueue_interface
import
capa.xqueue_interface
as
xqueue_interface
import
safe_exec
import
capa.safe_exec
as
safe_exec
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
CorrectMap
=
correctmap
.
CorrectMap
CorrectMap
=
correctmap
.
CorrectMap
# pylint: disable=C0103
CORRECTMAP_PY
=
None
CORRECTMAP_PY
=
None
...
@@ -1181,7 +1181,7 @@ class CustomResponse(LoncapaResponse):
...
@@ -1181,7 +1181,7 @@ class CustomResponse(LoncapaResponse):
fn
=
self
.
code
fn
=
self
.
code
answer_given
=
submission
[
0
]
if
(
len
(
idset
)
==
1
)
else
submission
answer_given
=
submission
[
0
]
if
(
len
(
idset
)
==
1
)
else
submission
kwnames
=
self
.
xml
.
get
(
"cfn_extra_args"
,
""
)
.
split
()
kwnames
=
self
.
xml
.
get
(
"cfn_extra_args"
,
""
)
.
split
()
kwargs
=
{
n
:
self
.
context
.
get
(
n
)
for
n
in
kwnames
}
kwargs
=
{
n
:
self
.
context
.
get
(
n
)
for
n
in
kwnames
}
log
.
debug
(
" submission =
%
s"
%
submission
)
log
.
debug
(
" submission =
%
s"
%
submission
)
try
:
try
:
ret
=
fn
(
self
.
expect
,
answer_given
,
**
kwargs
)
ret
=
fn
(
self
.
expect
,
answer_given
,
**
kwargs
)
...
@@ -1304,7 +1304,7 @@ class CustomResponse(LoncapaResponse):
...
@@ -1304,7 +1304,7 @@ class CustomResponse(LoncapaResponse):
# Notify student with a student input error
# Notify student with a student input error
_
,
_
,
traceback_obj
=
sys
.
exc_info
()
_
,
_
,
traceback_obj
=
sys
.
exc_info
()
raise
ResponseError
,
err
.
message
,
traceback_obj
raise
ResponseError
(
err
.
message
,
traceback_obj
)
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
...
@@ -1597,15 +1597,25 @@ class CodeResponse(LoncapaResponse):
...
@@ -1597,15 +1597,25 @@ class CodeResponse(LoncapaResponse):
class
ExternalResponse
(
LoncapaResponse
):
class
ExternalResponse
(
LoncapaResponse
):
'''
"""
Grade the students input using an external server.
Grade the students input using an external server.
Typically used by coding problems.
Typically used by coding problems.
'''
"""
response_tag
=
'externalresponse'
response_tag
=
'externalresponse'
allowed_inputfields
=
[
'textline'
,
'textbox'
]
allowed_inputfields
=
[
'textline'
,
'textbox'
]
awdmap
=
{
'EXACT_ANS'
:
'correct'
,
# TODO: handle other loncapa responses
'WRONG_FORMAT'
:
'incorrect'
,
}
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
url
=
''
self
.
tests
=
[]
self
.
code
=
''
super
(
ExternalResponse
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
setup_response
(
self
):
def
setup_response
(
self
):
xml
=
self
.
xml
xml
=
self
.
xml
...
@@ -1633,45 +1643,44 @@ class ExternalResponse(LoncapaResponse):
...
@@ -1633,45 +1643,44 @@ class ExternalResponse(LoncapaResponse):
self
.
tests
=
xml
.
get
(
'tests'
)
self
.
tests
=
xml
.
get
(
'tests'
)
def
do_external_request
(
self
,
cmd
,
extra_payload
):
def
do_external_request
(
self
,
cmd
,
extra_payload
):
'''
"""
Perform HTTP request / post to external server.
Perform HTTP request / post to external server.
cmd = remote command to perform (str)
cmd = remote command to perform (str)
extra_payload = dict of extra stuff to post.
extra_payload = dict of extra stuff to post.
Return XML tree of response (from response body)
Return XML tree of response (from response body)
'''
"""
xmlstr
=
etree
.
tostring
(
self
.
xml
,
pretty_print
=
True
)
xmlstr
=
etree
.
tostring
(
self
.
xml
,
pretty_print
=
True
)
payload
=
{
'xml'
:
xmlstr
,
payload
=
{
'edX_cmd'
:
cmd
,
'xml'
:
xmlstr
,
'edX_tests'
:
self
.
tests
,
'edX_cmd'
:
cmd
,
'processor'
:
self
.
code
,
'edX_tests'
:
self
.
tests
,
}
'processor'
:
self
.
code
,
}
payload
.
update
(
extra_payload
)
payload
.
update
(
extra_payload
)
try
:
try
:
# call external server. TODO: synchronous call, can block for a
# call external server. TODO: synchronous call, can block for a
# long time
# long time
r
=
requests
.
post
(
self
.
url
,
data
=
payload
)
r
eq
=
requests
.
post
(
self
.
url
,
data
=
payload
)
except
Exception
as
err
:
except
Exception
as
err
:
msg
=
'Error
%
s - cannot connect to external server url=
%
s'
%
(
msg
=
'Error {0} - cannot connect to external server url={1}'
.
format
(
err
,
self
.
url
)
err
,
self
.
url
)
log
.
error
(
msg
)
log
.
error
(
msg
)
raise
Exception
(
msg
)
raise
Exception
(
msg
)
if
self
.
system
.
DEBUG
:
if
self
.
system
.
DEBUG
:
log
.
info
(
'response =
%
s'
%
r
.
text
)
log
.
info
(
'response =
%
s'
,
req
.
text
)
if
(
not
r
.
text
)
or
(
not
r
.
text
.
strip
()):
if
(
not
r
eq
.
text
)
or
(
not
req
.
text
.
strip
()):
raise
Exception
(
raise
Exception
(
'Error: no response from external server url=
%
s'
%
self
.
url
)
'Error: no response from external server url=
%
s'
%
self
.
url
)
try
:
try
:
# response is XML; parse it
# response is XML; parse it
rxml
=
etree
.
fromstring
(
r
.
text
)
rxml
=
etree
.
fromstring
(
r
eq
.
text
)
except
Exception
as
err
:
except
Exception
as
err
:
msg
=
'Error
%
s - cannot parse response from external server r.text=
%
s'
%
(
msg
=
'Error {0} - cannot parse response from external server req.text={1}'
.
format
(
err
,
req
.
text
)
err
,
r
.
text
)
log
.
error
(
msg
)
log
.
error
(
msg
)
raise
Exception
(
msg
)
raise
Exception
(
msg
)
...
@@ -1682,9 +1691,13 @@ class ExternalResponse(LoncapaResponse):
...
@@ -1682,9 +1691,13 @@ class ExternalResponse(LoncapaResponse):
cmap
=
CorrectMap
()
cmap
=
CorrectMap
()
try
:
try
:
submission
=
[
student_answers
[
k
]
for
k
in
idset
]
submission
=
[
student_answers
[
k
]
for
k
in
idset
]
except
Exception
as
err
:
except
Exception
as
err
:
# pylint: disable=W0703
log
.
error
(
'Error
%
s: cannot get student answer for
%
s; student_answers=
%
s'
%
log
.
error
(
(
err
,
self
.
answer_ids
,
student_answers
))
'Error
%
s: cannot get student answer for
%
s; student_answers=
%
s'
,
err
,
self
.
answer_ids
,
student_answers
)
raise
Exception
(
err
)
raise
Exception
(
err
)
self
.
context
.
update
({
'submission'
:
submission
})
self
.
context
.
update
({
'submission'
:
submission
})
...
@@ -1693,8 +1706,8 @@ class ExternalResponse(LoncapaResponse):
...
@@ -1693,8 +1706,8 @@ class ExternalResponse(LoncapaResponse):
try
:
try
:
rxml
=
self
.
do_external_request
(
'get_score'
,
extra_payload
)
rxml
=
self
.
do_external_request
(
'get_score'
,
extra_payload
)
except
Exception
as
err
:
except
Exception
as
err
:
# pylint: disable=W0703
log
.
error
(
'Error
%
s'
%
err
)
log
.
error
(
'Error
%
s'
,
err
)
if
self
.
system
.
DEBUG
:
if
self
.
system
.
DEBUG
:
cmap
.
set_dict
(
dict
(
zip
(
sorted
(
cmap
.
set_dict
(
dict
(
zip
(
sorted
(
self
.
answer_ids
),
[
'incorrect'
]
*
len
(
idset
))))
self
.
answer_ids
),
[
'incorrect'
]
*
len
(
idset
))))
...
@@ -1703,13 +1716,11 @@ class ExternalResponse(LoncapaResponse):
...
@@ -1703,13 +1716,11 @@ class ExternalResponse(LoncapaResponse):
'<span class="inline-error">
%
s</span>'
%
str
(
err
)
.
replace
(
'<'
,
'<'
))
'<span class="inline-error">
%
s</span>'
%
str
(
err
)
.
replace
(
'<'
,
'<'
))
return
cmap
return
cmap
ad
=
rxml
.
find
(
'awarddetail'
)
.
text
awd
=
rxml
.
find
(
'awarddetail'
)
.
text
admap
=
{
'EXACT_ANS'
:
'correct'
,
# TODO: handle other loncapa responses
'WRONG_FORMAT'
:
'incorrect'
,
}
self
.
context
[
'correct'
]
=
[
'correct'
]
self
.
context
[
'correct'
]
=
[
'correct'
]
if
a
d
in
a
dmap
:
if
a
wd
in
self
.
aw
dmap
:
self
.
context
[
'correct'
][
0
]
=
admap
[
a
d
]
self
.
context
[
'correct'
][
0
]
=
self
.
awdmap
[
aw
d
]
# create CorrectMap
# create CorrectMap
for
key
in
idset
:
for
key
in
idset
:
...
@@ -1721,14 +1732,14 @@ class ExternalResponse(LoncapaResponse):
...
@@ -1721,14 +1732,14 @@ class ExternalResponse(LoncapaResponse):
return
cmap
return
cmap
def
get_answers
(
self
):
def
get_answers
(
self
):
'''
"""
Use external server to get expected answers
Use external server to get expected answers
'''
"""
try
:
try
:
rxml
=
self
.
do_external_request
(
'get_answers'
,
{})
rxml
=
self
.
do_external_request
(
'get_answers'
,
{})
exans
=
json
.
loads
(
rxml
.
find
(
'expected'
)
.
text
)
exans
=
json
.
loads
(
rxml
.
find
(
'expected'
)
.
text
)
except
Exception
as
err
:
except
Exception
as
err
:
# pylint: disable=W0703
log
.
error
(
'Error
%
s'
%
err
)
log
.
error
(
'Error
%
s'
,
err
)
if
self
.
system
.
DEBUG
:
if
self
.
system
.
DEBUG
:
msg
=
'<span class="inline-error">
%
s</span>'
%
str
(
msg
=
'<span class="inline-error">
%
s</span>'
%
str
(
err
)
.
replace
(
'<'
,
'<'
)
err
)
.
replace
(
'<'
,
'<'
)
...
@@ -1736,8 +1747,8 @@ class ExternalResponse(LoncapaResponse):
...
@@ -1736,8 +1747,8 @@ class ExternalResponse(LoncapaResponse):
exans
[
0
]
=
msg
exans
[
0
]
=
msg
if
not
(
len
(
exans
)
==
len
(
self
.
answer_ids
)):
if
not
(
len
(
exans
)
==
len
(
self
.
answer_ids
)):
log
.
error
(
'Expected
%
d answers from external server, only got
%
d!'
%
log
.
error
(
'Expected
%
s answers from external server, only got
%
s!'
,
(
len
(
self
.
answer_ids
),
len
(
exans
)
))
len
(
self
.
answer_ids
),
len
(
exans
))
raise
Exception
(
'Short response from external server'
)
raise
Exception
(
'Short response from external server'
)
return
dict
(
zip
(
self
.
answer_ids
,
exans
))
return
dict
(
zip
(
self
.
answer_ids
,
exans
))
...
@@ -1745,9 +1756,9 @@ class ExternalResponse(LoncapaResponse):
...
@@ -1745,9 +1756,9 @@ class ExternalResponse(LoncapaResponse):
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
class
FormulaResponse
(
LoncapaResponse
):
class
FormulaResponse
(
LoncapaResponse
):
'''
"""
Checking of symbolic math response using numerical sampling.
Checking of symbolic math response using numerical sampling.
'''
"""
response_tag
=
'formularesponse'
response_tag
=
'formularesponse'
hint_tag
=
'formulahint'
hint_tag
=
'formulahint'
...
@@ -1776,11 +1787,11 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1776,11 +1787,11 @@ class FormulaResponse(LoncapaResponse):
if
tolerance_xml
:
# If it isn't an empty list...
if
tolerance_xml
:
# If it isn't an empty list...
self
.
tolerance
=
contextualize_text
(
tolerance_xml
[
0
],
context
)
self
.
tolerance
=
contextualize_text
(
tolerance_xml
[
0
],
context
)
ts
=
xml
.
get
(
'type'
)
t
ype
s
=
xml
.
get
(
'type'
)
if
ts
is
None
:
if
t
ype
s
is
None
:
typeslist
=
[]
typeslist
=
[]
else
:
else
:
typeslist
=
ts
.
split
(
','
)
typeslist
=
t
ype
s
.
split
(
','
)
if
'ci'
in
typeslist
:
if
'ci'
in
typeslist
:
# Case insensitive
# Case insensitive
self
.
case_sensitive
=
False
self
.
case_sensitive
=
False
...
@@ -1812,30 +1823,33 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1812,30 +1823,33 @@ class FormulaResponse(LoncapaResponse):
answer
,
answer
,
case_sensitive
=
self
.
case_sensitive
,
case_sensitive
=
self
.
case_sensitive
,
))
))
except
UndefinedVariable
as
uv
:
except
UndefinedVariable
as
err
:
log
.
debug
(
log
.
debug
(
'formularesponse: undefined variable in formula=
%
s'
%
answer
)
'formularesponse: undefined variable in formula=
%
s'
,
cgi
.
escape
(
answer
)
)
raise
StudentInputError
(
raise
StudentInputError
(
"Invalid input: "
+
uv
.
message
+
" not permitted in answer"
"Invalid input: "
+
err
.
message
+
" not permitted in answer"
)
)
except
ValueError
as
ve
:
except
ValueError
as
err
:
if
'factorial'
in
ve
.
message
:
if
'factorial'
in
err
.
message
:
# This is thrown when fact() or factorial() is used in a formularesponse answer
# This is thrown when fact() or factorial() is used in a formularesponse answer
# that tests on negative and/or non-integer inputs
# that tests on negative and/or non-integer inputs
#
ve
.message will be: `factorial() only accepts integral values` or
#
err
.message will be: `factorial() only accepts integral values` or
# `factorial() not defined for negative values`
# `factorial() not defined for negative values`
log
.
debug
(
log
.
debug
(
(
'formularesponse: factorial function used in response '
(
'formularesponse: factorial function used in response '
'that tests negative and/or non-integer inputs. '
'that tests negative and/or non-integer inputs. '
'given={0}'
)
.
format
(
given
)
'Provided answer was:
%
s'
),
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
(
given
))
"{0}"
)
.
format
(
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
{0} in formula'
.
format
(
ve
)
)
log
.
debug
(
'formularesponse: error
%
s in formula'
,
err
)
raise
StudentInputError
(
"Invalid input: Could not parse '
%
s' as a formula"
%
raise
StudentInputError
(
"Invalid input: Could not parse '
%
s' as a formula"
%
cgi
.
escape
(
answer
))
cgi
.
escape
(
answer
))
except
Exception
as
err
:
except
Exception
as
err
:
...
@@ -1857,7 +1871,7 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1857,7 +1871,7 @@ class FormulaResponse(LoncapaResponse):
ranges
=
dict
(
zip
(
variables
,
sranges
))
ranges
=
dict
(
zip
(
variables
,
sranges
))
out
=
[]
out
=
[]
for
i
in
range
(
numsamples
):
for
_
in
range
(
numsamples
):
var_dict
=
{}
var_dict
=
{}
# ranges give numerical ranges for testing
# ranges give numerical ranges for testing
for
var
in
ranges
:
for
var
in
ranges
:
...
@@ -1902,15 +1916,17 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1902,15 +1916,17 @@ class FormulaResponse(LoncapaResponse):
except
StudentInputError
:
except
StudentInputError
:
return
False
return
False
def
strip_dict
(
self
,
d
):
def
strip_dict
(
self
,
inp_d
):
''' Takes a dict. Returns an identical dict, with all non-word
"""
Takes a dict. Returns an identical dict, with all non-word
keys and all non-numeric values stripped out. All values also
keys and all non-numeric values stripped out. All values also
converted to float. Used so we can safely use Python contexts.
converted to float. Used so we can safely use Python contexts.
'''
"""
d
=
dict
([(
k
,
numpy
.
complex
(
d
[
k
]))
for
k
in
d
if
type
(
k
)
==
str
and
inp_d
=
dict
([(
k
,
numpy
.
complex
(
inp_d
[
k
]))
k
.
isalnum
()
and
for
k
in
inp_d
if
type
(
k
)
==
str
and
isinstance
(
d
[
k
],
numbers
.
Number
)])
k
.
isalnum
()
and
return
d
isinstance
(
inp_d
[
k
],
numbers
.
Number
)])
return
inp_d
def
check_hint_condition
(
self
,
hxml_set
,
student_answers
):
def
check_hint_condition
(
self
,
hxml_set
,
student_answers
):
given
=
student_answers
[
self
.
answer_id
]
given
=
student_answers
[
self
.
answer_id
]
...
@@ -1920,14 +1936,18 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1920,14 +1936,18 @@ class FormulaResponse(LoncapaResponse):
name
=
hxml
.
get
(
'name'
)
name
=
hxml
.
get
(
'name'
)
correct_answer
=
contextualize_text
(
correct_answer
=
contextualize_text
(
hxml
.
get
(
'answer'
),
self
.
context
)
hxml
.
get
(
'answer'
),
self
.
context
)
# pylint: disable=W0703
try
:
try
:
correctness
=
self
.
check_formula
(
correctness
=
self
.
check_formula
(
correct_answer
,
given
,
samples
)
correct_answer
,
given
,
samples
)
except
Exception
:
except
Exception
:
correctness
=
'incorrect'
correctness
=
'incorrect'
if
correctness
==
'correct'
:
if
correctness
==
'correct'
:
hints_to_show
.
append
(
name
)
hints_to_show
.
append
(
name
)
log
.
debug
(
'hints_to_show =
%
s'
%
hints_to_show
)
log
.
debug
(
'hints_to_show =
%
s'
,
hints_to_show
)
return
hints_to_show
return
hints_to_show
def
get_answers
(
self
):
def
get_answers
(
self
):
...
@@ -1937,10 +1957,16 @@ class FormulaResponse(LoncapaResponse):
...
@@ -1937,10 +1957,16 @@ class FormulaResponse(LoncapaResponse):
class
SchematicResponse
(
LoncapaResponse
):
class
SchematicResponse
(
LoncapaResponse
):
"""
Circuit schematic response type.
"""
response_tag
=
'schematicresponse'
response_tag
=
'schematicresponse'
allowed_inputfields
=
[
'schematic'
]
allowed_inputfields
=
[
'schematic'
]
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
code
=
''
super
(
SchematicResponse
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
setup_response
(
self
):
def
setup_response
(
self
):
xml
=
self
.
xml
xml
=
self
.
xml
answer
=
xml
.
xpath
(
'//*[@id=$id]//answer'
,
id
=
xml
.
get
(
'id'
))[
0
]
answer
=
xml
.
xpath
(
'//*[@id=$id]//answer'
,
id
=
xml
.
get
(
'id'
))[
0
]
...
@@ -2010,6 +2036,10 @@ class ImageResponse(LoncapaResponse):
...
@@ -2010,6 +2036,10 @@ class ImageResponse(LoncapaResponse):
response_tag
=
'imageresponse'
response_tag
=
'imageresponse'
allowed_inputfields
=
[
'imageinput'
]
allowed_inputfields
=
[
'imageinput'
]
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
ielements
=
[]
super
(
ImageResponse
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
setup_response
(
self
):
def
setup_response
(
self
):
self
.
ielements
=
self
.
inputfields
self
.
ielements
=
self
.
inputfields
self
.
answer_ids
=
[
ie
.
get
(
'id'
)
for
ie
in
self
.
ielements
]
self
.
answer_ids
=
[
ie
.
get
(
'id'
)
for
ie
in
self
.
ielements
]
...
@@ -2018,40 +2048,39 @@ class ImageResponse(LoncapaResponse):
...
@@ -2018,40 +2048,39 @@ class ImageResponse(LoncapaResponse):
correct_map
=
CorrectMap
()
correct_map
=
CorrectMap
()
expectedset
=
self
.
get_mapped_answers
()
expectedset
=
self
.
get_mapped_answers
()
for
aid
in
self
.
answer_ids
:
# loop through IDs of <imageinput>
for
aid
in
self
.
answer_ids
:
# loop through IDs of <imageinput>
# fields in our stanza
# Fields in our stanza
given
=
student_answers
[
given
=
student_answers
[
aid
]
# This should be a string of the form '[x,y]'
aid
]
# this should be a string of the form '[x,y]'
correct_map
.
set
(
aid
,
'incorrect'
)
correct_map
.
set
(
aid
,
'incorrect'
)
if
not
given
:
# No answer to parse. Mark as incorrect and move on
if
not
given
:
# No answer to parse. Mark as incorrect and move on
continue
continue
#
p
arse given answer
#
P
arse given answer
m
=
re
.
match
(
r'\[([0-9]+),([0-9]+)]'
,
given
.
strip
()
.
replace
(
' '
,
''
))
acoords
=
re
.
match
(
r'\[([0-9]+),([0-9]+)]'
,
given
.
strip
()
.
replace
(
' '
,
''
))
if
not
m
:
if
not
acoords
:
raise
Exception
(
'[capamodule.capa.responsetypes.imageinput] '
raise
Exception
(
'[capamodule.capa.responsetypes.imageinput] '
'error grading
%
s (input=
%
s)'
%
(
aid
,
given
))
'error grading
{0} (input={1})'
.
format
(
aid
,
given
))
(
gx
,
gy
)
=
[
int
(
x
)
for
x
in
m
.
groups
()]
(
ans_x
,
ans_y
)
=
[
int
(
x
)
for
x
in
acoords
.
groups
()]
rectangles
,
regions
=
expectedset
rectangles
,
regions
=
expectedset
if
rectangles
[
aid
]:
#
r
ectangles part - for backward compatibility
if
rectangles
[
aid
]:
#
R
ectangles part - for backward compatibility
# Check whether given point lies in any of the solution
# Check whether given point lies in any of the solution
# rectangles
# rectangles
solution_rectangles
=
rectangles
[
aid
]
.
split
(
';'
)
solution_rectangles
=
rectangles
[
aid
]
.
split
(
';'
)
for
solution_rectangle
in
solution_rectangles
:
for
solution_rectangle
in
solution_rectangles
:
# parse expected answer
# parse expected answer
# TODO: Compile regexp on file load
# TODO: Compile regexp on file load
m
=
re
.
match
(
sr_coords
=
re
.
match
(
r'[\(\[]([0-9]+),([0-9]+)[\)\]]-[\(\[]([0-9]+),([0-9]+)[\)\]]'
,
r'[\(\[]([0-9]+),([0-9]+)[\)\]]-[\(\[]([0-9]+),([0-9]+)[\)\]]'
,
solution_rectangle
.
strip
()
.
replace
(
' '
,
''
))
solution_rectangle
.
strip
()
.
replace
(
' '
,
''
))
if
not
m
:
if
not
sr_coords
:
msg
=
'Error in problem specification! cannot parse rectangle in
%
s'
%
(
msg
=
'Error in problem specification! cannot parse rectangle in
%
s'
%
(
etree
.
tostring
(
self
.
ielements
[
aid
],
pretty_print
=
True
))
etree
.
tostring
(
self
.
ielements
[
aid
],
pretty_print
=
True
))
raise
Exception
(
raise
Exception
(
'[capamodule.capa.responsetypes.imageinput] '
+
msg
)
'[capamodule.capa.responsetypes.imageinput] '
+
msg
)
(
llx
,
lly
,
urx
,
ury
)
=
[
int
(
x
)
for
x
in
m
.
groups
()]
(
llx
,
lly
,
urx
,
ury
)
=
[
int
(
x
)
for
x
in
sr_coords
.
groups
()]
# answer is correct if (x,y) is within the specified
# answer is correct if (x,y) is within the specified
# rectangle
# rectangle
if
(
llx
<=
gx
<=
urx
)
and
(
lly
<=
g
y
<=
ury
):
if
(
llx
<=
ans_x
<=
urx
)
and
(
lly
<=
ans_
y
<=
ury
):
correct_map
.
set
(
aid
,
'correct'
)
correct_map
.
set
(
aid
,
'correct'
)
break
break
if
correct_map
[
aid
][
'correctness'
]
!=
'correct'
and
regions
[
aid
]:
if
correct_map
[
aid
][
'correctness'
]
!=
'correct'
and
regions
[
aid
]:
...
@@ -2065,13 +2094,13 @@ class ImageResponse(LoncapaResponse):
...
@@ -2065,13 +2094,13 @@ class ImageResponse(LoncapaResponse):
for
region
in
parsed_region
:
for
region
in
parsed_region
:
polygon
=
MultiPoint
(
region
)
.
convex_hull
polygon
=
MultiPoint
(
region
)
.
convex_hull
if
(
polygon
.
type
==
'Polygon'
and
if
(
polygon
.
type
==
'Polygon'
and
polygon
.
contains
(
Point
(
gx
,
g
y
))):
polygon
.
contains
(
Point
(
ans_x
,
ans_
y
))):
correct_map
.
set
(
aid
,
'correct'
)
correct_map
.
set
(
aid
,
'correct'
)
break
break
return
correct_map
return
correct_map
def
get_mapped_answers
(
self
):
def
get_mapped_answers
(
self
):
'''
"""
Returns the internal representation of the answers
Returns the internal representation of the answers
Input:
Input:
...
@@ -2080,7 +2109,7 @@ class ImageResponse(LoncapaResponse):
...
@@ -2080,7 +2109,7 @@ class ImageResponse(LoncapaResponse):
tuple (dict, dict) -
tuple (dict, dict) -
rectangles (dict) - a map of inputs to the defined rectangle for that input
rectangles (dict) - a map of inputs to the defined rectangle for that input
regions (dict) - a map of inputs to the defined region for that input
regions (dict) - a map of inputs to the defined region for that input
'''
"""
answers
=
(
answers
=
(
dict
([(
ie
.
get
(
'id'
),
ie
.
get
(
dict
([(
ie
.
get
(
'id'
),
ie
.
get
(
'rectangle'
))
for
ie
in
self
.
ielements
]),
'rectangle'
))
for
ie
in
self
.
ielements
]),
...
@@ -2088,7 +2117,7 @@ class ImageResponse(LoncapaResponse):
...
@@ -2088,7 +2117,7 @@ class ImageResponse(LoncapaResponse):
return
answers
return
answers
def
get_answers
(
self
):
def
get_answers
(
self
):
'''
"""
Returns the external representation of the answers
Returns the external representation of the answers
Input:
Input:
...
@@ -2096,11 +2125,11 @@ class ImageResponse(LoncapaResponse):
...
@@ -2096,11 +2125,11 @@ class ImageResponse(LoncapaResponse):
Returns:
Returns:
dict (str, (str, str)) - a map of inputs to a tuple of their rectange
dict (str, (str, str)) - a map of inputs to a tuple of their rectange
and their regions
and their regions
'''
"""
answers
=
{}
answers
=
{}
for
ie
in
self
.
ielements
:
for
ie
lt
in
self
.
ielements
:
ie_id
=
ie
.
get
(
'id'
)
ie_id
=
ie
lt
.
get
(
'id'
)
answers
[
ie_id
]
=
(
ie
.
get
(
'rectangle'
),
ie
.
get
(
'regions'
))
answers
[
ie_id
]
=
(
ie
lt
.
get
(
'rectangle'
),
ielt
.
get
(
'regions'
))
return
answers
return
answers
...
@@ -2108,26 +2137,32 @@ class ImageResponse(LoncapaResponse):
...
@@ -2108,26 +2137,32 @@ class ImageResponse(LoncapaResponse):
class
AnnotationResponse
(
LoncapaResponse
):
class
AnnotationResponse
(
LoncapaResponse
):
'''
"""
Checking of annotation responses.
Checking of annotation responses.
The response contains both a comment (student commentary) and an option (student tag).
The response contains both a comment (student commentary) and an option (student tag).
Only the tag is currently graded. Answers may be incorrect, partially correct, or correct.
Only the tag is currently graded. Answers may be incorrect, partially correct, or correct.
'''
"""
response_tag
=
'annotationresponse'
response_tag
=
'annotationresponse'
allowed_inputfields
=
[
'annotationinput'
]
allowed_inputfields
=
[
'annotationinput'
]
max_inputfields
=
1
max_inputfields
=
1
default_scoring
=
{
'incorrect'
:
0
,
'partially-correct'
:
1
,
'correct'
:
2
}
default_scoring
=
{
'incorrect'
:
0
,
'partially-correct'
:
1
,
'correct'
:
2
}
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
scoring_map
=
{}
self
.
answer_map
=
{}
super
(
AnnotationResponse
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
setup_response
(
self
):
def
setup_response
(
self
):
xml
=
self
.
xml
self
.
scoring_map
=
self
.
_get_scoring_map
()
self
.
scoring_map
=
self
.
_get_scoring_map
()
self
.
answer_map
=
self
.
_get_answer_map
()
self
.
answer_map
=
self
.
_get_answer_map
()
self
.
maxpoints
=
self
.
_get_max_points
()
self
.
maxpoints
=
self
.
_get_max_points
()
def
get_score
(
self
,
student_answers
):
def
get_score
(
self
,
student_answers
):
''' Returns a CorrectMap for the student answer, which may include
"""
partially correct answers.'''
Returns a CorrectMap for the student answer, which may include
partially correct answers.
"""
student_answer
=
student_answers
[
self
.
answer_id
]
student_answer
=
student_answers
[
self
.
answer_id
]
student_option
=
self
.
_get_submitted_option_id
(
student_answer
)
student_option
=
self
.
_get_submitted_option_id
(
student_answer
)
...
@@ -2146,23 +2181,26 @@ class AnnotationResponse(LoncapaResponse):
...
@@ -2146,23 +2181,26 @@ class AnnotationResponse(LoncapaResponse):
return
self
.
answer_map
return
self
.
answer_map
def
_get_scoring_map
(
self
):
def
_get_scoring_map
(
self
):
''' Returns a dict of option->scoring for each input. '''
"""Returns a dict of option->scoring for each input."""
scoring
=
self
.
default_scoring
scoring
=
self
.
default_scoring
choices
=
dict
([(
choice
,
choice
)
for
choice
in
scoring
])
choices
=
dict
([(
choice
,
choice
)
for
choice
in
scoring
])
scoring_map
=
{}
scoring_map
=
{}
for
inputfield
in
self
.
inputfields
:
for
inputfield
in
self
.
inputfields
:
option_scoring
=
dict
([(
option
[
'id'
],
{
option_scoring
=
dict
([(
option
[
'id'
],
{
'correctness'
:
choices
.
get
(
option
[
'choice'
]),
'correctness'
:
choices
.
get
(
option
[
'choice'
]),
'points'
:
scoring
.
get
(
option
[
'choice'
])
'points'
:
scoring
.
get
(
option
[
'choice'
])
})
for
option
in
self
.
_find_options
(
inputfield
)])
}
)
for
option
in
self
.
_find_options
(
inputfield
)])
scoring_map
[
inputfield
.
get
(
'id'
)]
=
option_scoring
scoring_map
[
inputfield
.
get
(
'id'
)]
=
option_scoring
return
scoring_map
return
scoring_map
def
_get_answer_map
(
self
):
def
_get_answer_map
(
self
):
''' Returns a dict of answers for each input.'''
"""Returns a dict of answers for each input."""
answer_map
=
{}
answer_map
=
{}
for
inputfield
in
self
.
inputfields
:
for
inputfield
in
self
.
inputfields
:
correct_option
=
self
.
_find_option_with_choice
(
correct_option
=
self
.
_find_option_with_choice
(
...
@@ -2173,13 +2211,13 @@ class AnnotationResponse(LoncapaResponse):
...
@@ -2173,13 +2211,13 @@ class AnnotationResponse(LoncapaResponse):
return
answer_map
return
answer_map
def
_get_max_points
(
self
):
def
_get_max_points
(
self
):
''' Returns a dict of the max points for each input: input id -> maxpoints. '''
"""Returns a dict of the max points for each input: input id -> maxpoints."""
scoring
=
self
.
default_scoring
scoring
=
self
.
default_scoring
correct_points
=
scoring
.
get
(
'correct'
)
correct_points
=
scoring
.
get
(
'correct'
)
return
dict
([(
inputfield
.
get
(
'id'
),
correct_points
)
for
inputfield
in
self
.
inputfields
])
return
dict
([(
inputfield
.
get
(
'id'
),
correct_points
)
for
inputfield
in
self
.
inputfields
])
def
_find_options
(
self
,
inputfield
):
def
_find_options
(
self
,
inputfield
):
''' Returns an array of dicts where each dict represents an option. '''
"""Returns an array of dicts where each dict represents an option. """
elements
=
inputfield
.
findall
(
'./options/option'
)
elements
=
inputfield
.
findall
(
'./options/option'
)
return
[{
return
[{
'id'
:
index
,
'id'
:
index
,
...
@@ -2188,22 +2226,22 @@ class AnnotationResponse(LoncapaResponse):
...
@@ -2188,22 +2226,22 @@ class AnnotationResponse(LoncapaResponse):
}
for
(
index
,
option
)
in
enumerate
(
elements
)]
}
for
(
index
,
option
)
in
enumerate
(
elements
)]
def
_find_option_with_choice
(
self
,
inputfield
,
choice
):
def
_find_option_with_choice
(
self
,
inputfield
,
choice
):
''' Returns the option with the given choice value, otherwise None. '''
"""Returns the option with the given choice value, otherwise None. """
for
option
in
self
.
_find_options
(
inputfield
):
for
option
in
self
.
_find_options
(
inputfield
):
if
option
[
'choice'
]
==
choice
:
if
option
[
'choice'
]
==
choice
:
return
option
return
option
def
_unpack
(
self
,
json_value
):
def
_unpack
(
self
,
json_value
):
''' Unpacks a student response value submitted as JSON.'''
"""Unpacks a student response value submitted as JSON."""
d
=
json
.
loads
(
json_value
)
json_
d
=
json
.
loads
(
json_value
)
if
type
(
d
)
!=
dict
:
if
type
(
json_
d
)
!=
dict
:
d
=
{}
json_
d
=
{}
comment_value
=
d
.
get
(
'comment'
,
''
)
comment_value
=
json_
d
.
get
(
'comment'
,
''
)
if
not
isinstance
(
d
,
basestring
):
if
not
isinstance
(
json_
d
,
basestring
):
comment_value
=
''
comment_value
=
''
options_value
=
d
.
get
(
'options'
,
[])
options_value
=
json_
d
.
get
(
'options'
,
[])
if
not
isinstance
(
options_value
,
list
):
if
not
isinstance
(
options_value
,
list
):
options_value
=
[]
options_value
=
[]
...
@@ -2213,7 +2251,7 @@ class AnnotationResponse(LoncapaResponse):
...
@@ -2213,7 +2251,7 @@ class AnnotationResponse(LoncapaResponse):
}
}
def
_get_submitted_option_id
(
self
,
student_answer
):
def
_get_submitted_option_id
(
self
,
student_answer
):
''' Return the single option that was selected, otherwise None.'''
"""Return the single option that was selected, otherwise None."""
submitted
=
self
.
_unpack
(
student_answer
)
submitted
=
self
.
_unpack
(
student_answer
)
option_ids
=
submitted
[
'options_value'
]
option_ids
=
submitted
[
'options_value'
]
if
len
(
option_ids
)
==
1
:
if
len
(
option_ids
)
==
1
:
...
@@ -2235,6 +2273,12 @@ class ChoiceTextResponse(LoncapaResponse):
...
@@ -2235,6 +2273,12 @@ class ChoiceTextResponse(LoncapaResponse):
'radiotextgroup'
'radiotextgroup'
]
]
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
correct_inputs
=
{}
self
.
answer_values
=
{}
self
.
correct_choices
=
{}
super
(
ChoiceTextResponse
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
def
setup_response
(
self
):
def
setup_response
(
self
):
"""
"""
Sets up three dictionaries for use later:
Sets up three dictionaries for use later:
...
@@ -2250,10 +2294,8 @@ class ChoiceTextResponse(LoncapaResponse):
...
@@ -2250,10 +2294,8 @@ class ChoiceTextResponse(LoncapaResponse):
"""
"""
context
=
self
.
context
context
=
self
.
context
self
.
correct_choices
=
{}
self
.
assign_choice_names
()
self
.
correct_inputs
=
{}
self
.
answer_values
=
{
self
.
answer_id
:
[]}
self
.
answer_values
=
{
self
.
answer_id
:
[]}
self
.
assign_choice_names
()
correct_xml
=
self
.
xml
.
xpath
(
'//*[@id=$id]//choice[@correct="true"]'
,
correct_xml
=
self
.
xml
.
xpath
(
'//*[@id=$id]//choice[@correct="true"]'
,
id
=
self
.
xml
.
get
(
'id'
))
id
=
self
.
xml
.
get
(
'id'
))
for
node
in
correct_xml
:
for
node
in
correct_xml
:
...
@@ -2552,6 +2594,7 @@ class ChoiceTextResponse(LoncapaResponse):
...
@@ -2552,6 +2594,7 @@ class ChoiceTextResponse(LoncapaResponse):
# TEMPORARY: List of all response subclasses
# TEMPORARY: List of all response subclasses
# FIXME: To be replaced by auto-registration
# FIXME: To be replaced by auto-registration
# pylint: disable=E0604
__all__
=
[
CodeResponse
,
__all__
=
[
CodeResponse
,
NumericalResponse
,
NumericalResponse
,
FormulaResponse
,
FormulaResponse
,
...
...
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