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
48cc87e9
Commit
48cc87e9
authored
Feb 07, 2014
by
Matt Drayer
Committed by
Xavier Antoviaque
Feb 12, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added hint/feedback feature to multiple choice response
parent
f96e73b1
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
150 additions
and
6 deletions
+150
-6
common/lib/capa/capa/responsetypes.py
+27
-3
common/lib/capa/capa/templates/choicegroup.html
+49
-2
common/lib/capa/capa/tests/response_xml_factory.py
+38
-1
common/lib/capa/capa/tests/test_input_templates.py
+28
-0
common/lib/capa/capa/tests/test_responsetypes.py
+8
-0
No files found.
common/lib/capa/capa/responsetypes.py
View file @
48cc87e9
...
@@ -344,12 +344,15 @@ class LoncapaResponse(object):
...
@@ -344,12 +344,15 @@ class LoncapaResponse(object):
hintmode
=
hintgroup
.
get
(
'mode'
,
'always'
)
hintmode
=
hintgroup
.
get
(
'mode'
,
'always'
)
for
hintpart
in
hintgroup
.
findall
(
'hintpart'
):
for
hintpart
in
hintgroup
.
findall
(
'hintpart'
):
if
hintpart
.
get
(
'on'
)
in
hints_to_show
:
if
hintpart
.
get
(
'on'
)
in
hints_to_show
:
if
hintpart
.
find
(
'text'
)
is
not
None
:
hint_text
=
hintpart
.
find
(
'text'
)
.
text
hint_text
=
hintpart
.
find
(
'text'
)
.
text
elif
hintpart
.
text
:
hint_text
=
hintpart
.
text
# make the hint appear after the last answer box in this
# make the hint appear after the last answer box in this
# response
# response
aid
=
self
.
answer_ids
[
-
1
]
aid
=
self
.
answer_ids
[
-
1
]
new_cmap
.
set_hint_and_mode
(
aid
,
hint_text
,
hintmode
)
new_cmap
.
set_hint_and_mode
(
aid
,
hint_text
,
hintmode
)
log
.
debug
(
'after hint: new_cmap =
%
s'
,
new_cmap
)
@abc.abstractmethod
@abc.abstractmethod
def
get_score
(
self
,
student_answers
):
def
get_score
(
self
,
student_answers
):
...
@@ -718,6 +721,7 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -718,6 +721,7 @@ class MultipleChoiceResponse(LoncapaResponse):
# TODO: handle direction and randomize
# TODO: handle direction and randomize
tags
=
[
'multiplechoiceresponse'
]
tags
=
[
'multiplechoiceresponse'
]
hint_tag
=
'choicehint'
max_inputfields
=
1
max_inputfields
=
1
allowed_inputfields
=
[
'choicegroup'
]
allowed_inputfields
=
[
'choicegroup'
]
correct_choices
=
None
correct_choices
=
None
...
@@ -727,6 +731,10 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -727,6 +731,10 @@ class MultipleChoiceResponse(LoncapaResponse):
# attributes
# attributes
self
.
mc_setup_response
()
self
.
mc_setup_response
()
# These two fields are used for hint matching
self
.
regexp
=
True
self
.
case_insensitive
=
True
# define correct choices (after calling secondary setup)
# define correct choices (after calling secondary setup)
xml
=
self
.
xml
xml
=
self
.
xml
cxml
=
xml
.
xpath
(
'//*[@id=$id]//choice'
,
id
=
xml
.
get
(
'id'
))
cxml
=
xml
.
xpath
(
'//*[@id=$id]//choice'
,
id
=
xml
.
get
(
'id'
))
...
@@ -771,6 +779,24 @@ class MultipleChoiceResponse(LoncapaResponse):
...
@@ -771,6 +779,24 @@ class MultipleChoiceResponse(LoncapaResponse):
def
get_answers
(
self
):
def
get_answers
(
self
):
return
{
self
.
answer_id
:
self
.
correct_choices
}
return
{
self
.
answer_id
:
self
.
correct_choices
}
def
check_string
(
self
,
expected
,
given
):
flags
=
re
.
IGNORECASE
if
self
.
case_insensitive
else
0
regexp
=
re
.
compile
(
'^'
+
'|'
.
join
(
expected
)
+
'$'
,
flags
=
flags
|
re
.
UNICODE
)
result
=
re
.
search
(
regexp
,
given
)
return
bool
(
result
)
def
check_hint_condition
(
self
,
hxml_set
,
student_answers
):
# stolen from StringResponse.check_hint_condition
given
=
student_answers
[
self
.
answer_id
]
.
strip
()
hints_to_show
=
[]
for
hxml
in
hxml_set
:
name
=
hxml
.
get
(
'name'
)
hinted_answer
=
contextualize_text
(
hxml
.
get
(
'answer'
),
self
.
context
)
.
strip
()
if
self
.
check_string
([
hinted_answer
],
given
):
hints_to_show
.
append
(
name
)
return
hints_to_show
@registry.register
@registry.register
class
TrueFalseResponse
(
MultipleChoiceResponse
):
class
TrueFalseResponse
(
MultipleChoiceResponse
):
...
@@ -1050,7 +1076,6 @@ class StringResponse(LoncapaResponse):
...
@@ -1050,7 +1076,6 @@ class StringResponse(LoncapaResponse):
]
]
def
setup_response
(
self
):
def
setup_response
(
self
):
self
.
backward
=
'_or_'
in
self
.
xml
.
get
(
'answer'
)
.
lower
()
self
.
backward
=
'_or_'
in
self
.
xml
.
get
(
'answer'
)
.
lower
()
self
.
regexp
=
False
self
.
regexp
=
False
self
.
case_insensitive
=
False
self
.
case_insensitive
=
False
...
@@ -1147,7 +1172,6 @@ class CustomResponse(LoncapaResponse):
...
@@ -1147,7 +1172,6 @@ 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'
]
allowed_inputfields
=
[
'textline'
,
'textbox'
,
'crystallography'
,
allowed_inputfields
=
[
'textline'
,
'textbox'
,
'crystallography'
,
...
...
common/lib/capa/capa/templates/choicegroup.html
View file @
48cc87e9
<form
class=
"choicegroup capa_inputtype"
id=
"inputtype_${id}"
>
<form
class=
"choicegroup capa_inputtype"
id=
"inputtype_${id}"
>
<style>
#fieldset_container
{
margin
:
0
auto
;
overflow
:
hidden
;
position
:
relative
;
width
:
100%
;
}
#fieldset
{
float
:
left
;
margin
:
0
auto
;
width
:
50%
;
}
#fieldset_message
{
background
:
#99CCFF
;
color
:
#FFFFFF
;
font-family
:
arial
;
float
:
right
;
height
:
100%
;
margin
:
0
auto
;
padding
:
10px
;
position
:
absolute
;
right
:
0
;
width
:
50%
;
}
#fieldset_message_title
{
color
:
#FFFFFF
;
font-family
:
arial
;
font-size
:
18pt
;
}
</style>
<div
class=
"indicator_container"
>
<div
class=
"indicator_container"
>
% if input_type == 'checkbox' or not value:
% if input_type == 'checkbox' or not value:
% if status == 'unsubmitted' or show_correctness == 'never':
% if status == 'unsubmitted' or show_correctness == 'never':
...
@@ -12,9 +46,9 @@
...
@@ -12,9 +46,9 @@
% endif
% endif
% endif
% endif
</div>
</div>
<div
id=
"fieldset_container"
>
<div
id=
"fieldset"
>
<fieldset>
<fieldset>
% for choice_id, choice_description in choices:
% for choice_id, choice_description in choices:
<label
for=
"input_${id}_${choice_id}"
<label
for=
"input_${id}_${choice_id}"
##
If
the
student
has
selected
this
choice
...
##
If
the
student
has
selected
this
choice
...
...
@@ -59,6 +93,19 @@
...
@@ -59,6 +93,19 @@
% endfor
% endfor
<span
id=
"answer_${id}"
></span>
<span
id=
"answer_${id}"
></span>
</fieldset>
</fieldset>
</div>
## Message/hint display block -- empty/invisible unless msg is populated
<div
%
if
(
not
msg
is
UNDEFINED
)
and
(
len
(
msg
)
>
0):
id="fieldset_message"
% endif
>
% if (not msg is UNDEFINED) and (len(msg) > 0):
<span
id=
"fieldset_message_title"
>
Feedback
</span>
${msg}
% endif
</div>
</div>
% if show_correctness == "never" and (value or status not in ['unsubmitted']):
% if show_correctness == "never" and (value or status not in ['unsubmitted']):
<div
class=
"capa_alert"
>
${submitted_message}
</div>
<div
class=
"capa_alert"
>
${submitted_message}
</div>
...
...
common/lib/capa/capa/tests/response_xml_factory.py
View file @
48cc87e9
...
@@ -58,6 +58,7 @@ class ResponseXMLFactory(object):
...
@@ -58,6 +58,7 @@ class ResponseXMLFactory(object):
script
=
kwargs
.
get
(
'script'
,
None
)
script
=
kwargs
.
get
(
'script'
,
None
)
num_responses
=
kwargs
.
get
(
'num_responses'
,
1
)
num_responses
=
kwargs
.
get
(
'num_responses'
,
1
)
num_inputs
=
kwargs
.
get
(
'num_inputs'
,
1
)
num_inputs
=
kwargs
.
get
(
'num_inputs'
,
1
)
hints
=
kwargs
.
get
(
'hints'
,
None
)
# The root is <problem>
# The root is <problem>
root
=
etree
.
Element
(
"problem"
)
root
=
etree
.
Element
(
"problem"
)
...
@@ -83,6 +84,12 @@ class ResponseXMLFactory(object):
...
@@ -83,6 +84,12 @@ class ResponseXMLFactory(object):
if
not
(
None
==
input_element
):
if
not
(
None
==
input_element
):
response_element
.
append
(
input_element
)
response_element
.
append
(
input_element
)
# Add hintgroup, if specified
if
hints
is
not
None
and
hasattr
(
self
,
'create_hintgroup_element'
):
hintgroup_element
=
self
.
create_hintgroup_element
(
**
kwargs
)
if
hintgroup_element
is
not
None
:
response_element
.
append
(
hintgroup_element
)
# The problem has an explanation of the solution
# The problem has an explanation of the solution
if
explanation_text
:
if
explanation_text
:
explanation
=
etree
.
SubElement
(
root
,
"solution"
)
explanation
=
etree
.
SubElement
(
root
,
"solution"
)
...
@@ -161,6 +168,28 @@ class ResponseXMLFactory(object):
...
@@ -161,6 +168,28 @@ class ResponseXMLFactory(object):
return
group_element
return
group_element
@staticmethod
def
hintgroup_input_xml
(
**
kwargs
):
""" Create a <hintgroup> XML element"""
# Gather the troops
choice_names
=
kwargs
.
get
(
"choice_names"
)
hints
=
kwargs
.
get
(
"hints"
)
# Build the <hintgroup> child tree
group_element
=
etree
.
Element
(
"hintgroup"
)
for
(
choice_name
,
hint
)
in
zip
(
choice_names
,
hints
):
choicehint_element
=
etree
.
SubElement
(
group_element
,
"choicehint"
)
choicehint_answer
=
"choice_"
+
choice_name
choicehint_element
.
set
(
"answer"
,
choicehint_answer
)
choicehint_name
=
choice_name
+
"_hint"
choicehint_element
.
set
(
"name"
,
choicehint_name
)
hintpart_element
=
etree
.
SubElement
(
group_element
,
"hintpart"
)
hintpart_element
.
set
(
"on"
,
choicehint_name
)
hintpart_element
.
text
=
hint
return
group_element
class
NumericalResponseXMLFactory
(
ResponseXMLFactory
):
class
NumericalResponseXMLFactory
(
ResponseXMLFactory
):
""" Factory for producing <numericalresponse> XML trees """
""" Factory for producing <numericalresponse> XML trees """
...
@@ -618,7 +647,15 @@ class MultipleChoiceResponseXMLFactory(ResponseXMLFactory):
...
@@ -618,7 +647,15 @@ class MultipleChoiceResponseXMLFactory(ResponseXMLFactory):
def
create_input_element
(
self
,
**
kwargs
):
def
create_input_element
(
self
,
**
kwargs
):
""" Create the <choicegroup> element"""
""" Create the <choicegroup> element"""
kwargs
[
'choice_type'
]
=
'multiple'
kwargs
[
'choice_type'
]
=
'multiple'
return
ResponseXMLFactory
.
choicegroup_input_xml
(
**
kwargs
)
choice_group
=
ResponseXMLFactory
.
choicegroup_input_xml
(
**
kwargs
)
return
choice_group
def
create_hintgroup_element
(
self
,
**
kwargs
):
""" Create the <hintgroup> element"""
hintgroup_element
=
ResponseXMLFactory
.
hintgroup_input_xml
(
**
kwargs
)
return
hintgroup_element
class
TrueFalseResponseXMLFactory
(
ResponseXMLFactory
):
class
TrueFalseResponseXMLFactory
(
ResponseXMLFactory
):
...
...
common/lib/capa/capa/tests/test_input_templates.py
View file @
48cc87e9
...
@@ -255,6 +255,34 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
...
@@ -255,6 +255,34 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
xpath
=
"//div[@class='indicator_container']/span"
xpath
=
"//div[@class='indicator_container']/span"
self
.
assert_no_xpath
(
xml
,
xpath
,
self
.
context
)
self
.
assert_no_xpath
(
xml
,
xpath
,
self
.
context
)
def
test_option_marked_incorrect_with_feedback
(
self
):
"""
Test conditions under which a particular option
(not the entire problem) is marked incorrect, with feedback.
"""
conditions
=
[
{
'input_type'
:
'radio'
,
'value'
:
'2'
},
{
'input_type'
:
'radio'
,
'value'
:
[
'2'
]}]
self
.
context
[
'status'
]
=
'incorrect'
self
.
context
[
'msg'
]
=
"This is the feedback"
for
test_conditions
in
conditions
:
self
.
context
.
update
(
test_conditions
)
xml
=
self
.
render_to_xml
(
self
.
context
)
# Should include a choicegroup_incorrect class
xpath
=
"//label[@class='choicegroup_incorrect']"
self
.
assert_has_xpath
(
xml
,
xpath
,
self
.
context
)
# Should include a fieldset_message type
xpath
=
"//div[@id='fieldset_message']"
self
.
assert_has_xpath
(
xml
,
xpath
,
self
.
context
)
# Should include a fieldset_message_title type
xpath
=
"//span[@id='fieldset_message_title']"
self
.
assert_has_xpath
(
xml
,
xpath
,
self
.
context
)
def
test_never_show_correctness
(
self
):
def
test_never_show_correctness
(
self
):
"""
"""
Test conditions under which we tell the template to
Test conditions under which we tell the template to
...
...
common/lib/capa/capa/tests/test_responsetypes.py
View file @
48cc87e9
...
@@ -93,6 +93,14 @@ class MultiChoiceResponseTest(ResponseTest):
...
@@ -93,6 +93,14 @@ class MultiChoiceResponseTest(ResponseTest):
self
.
assert_grade
(
problem
,
'choice_foil_2'
,
'correct'
)
self
.
assert_grade
(
problem
,
'choice_foil_2'
,
'correct'
)
self
.
assert_grade
(
problem
,
'choice_foil_3'
,
'incorrect'
)
self
.
assert_grade
(
problem
,
'choice_foil_3'
,
'incorrect'
)
def
test_named_multiple_choice_grade_with_hint
(
self
):
problem
=
self
.
build_problem
(
choices
=
[
False
],
choice_names
=
[
"foil_1"
],
hints
=
[
"h1"
])
# Ensure that we get the expected hint
self
.
assert_grade
(
problem
,
'choice_foil_1'
,
'incorrect'
,
'h1'
)
class
TrueFalseResponseTest
(
ResponseTest
):
class
TrueFalseResponseTest
(
ResponseTest
):
from
capa.tests.response_xml_factory
import
TrueFalseResponseXMLFactory
from
capa.tests.response_xml_factory
import
TrueFalseResponseXMLFactory
...
...
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