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
2ad5d52b
Commit
2ad5d52b
authored
Jul 19, 2012
by
ichuang
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #226 from MITx/arjun/choiceresponse
Arjun/choiceresponse
parents
be2928f5
083ea17d
Hide whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
301 additions
and
12 deletions
+301
-12
common/lib/capa/capa/capa_problem.py
+1
-1
common/lib/capa/capa/inputtypes.py
+67
-4
common/lib/capa/capa/responsetypes.py
+91
-3
common/lib/capa/capa/templates/choicegroup.html
+6
-3
common/lib/xmodule/tests/__init__.py
+27
-0
common/lib/xmodule/tests/test_files/choiceresponse_checkbox.xml
+59
-0
common/lib/xmodule/tests/test_files/choiceresponse_radio.xml
+40
-0
common/lib/xmodule/xmodule/capa_module.py
+10
-1
No files found.
common/lib/capa/capa/capa_problem.py
View file @
2ad5d52b
...
...
@@ -39,7 +39,7 @@ import responsetypes
# dict of tagname, Response Class -- this should come from auto-registering
response_tag_dict
=
dict
([(
x
.
response_tag
,
x
)
for
x
in
responsetypes
.
__all__
])
entry_types
=
[
'textline'
,
'schematic'
,
'
choicegroup'
,
'textbox'
,
'imageinput'
,
'optioninput
'
]
entry_types
=
[
'textline'
,
'schematic'
,
'
textbox'
,
'imageinput'
,
'optioninput'
,
'choicegroup'
,
'radiogroup'
,
'checkboxgroup
'
]
solution_types
=
[
'solution'
]
# extra things displayed after "show answers" is pressed
response_properties
=
[
"responseparam"
,
"answer"
]
# these get captured as student responses
...
...
common/lib/capa/capa/inputtypes.py
View file @
2ad5d52b
...
...
@@ -8,7 +8,9 @@ Module containing the problem elements which render into input objects
- textline
- textbox (change this to textarea?)
- schemmatic
- choicegroup (for multiplechoice: checkbox, radio, or select option)
- choicegroup
- radiogroup
- checkboxgroup
- imageinput (for clickable image)
- optioninput (for option list)
...
...
@@ -146,6 +148,9 @@ def optioninput(element, value, status, render_template, msg=''):
return
etree
.
XML
(
html
)
#-----------------------------------------------------------------------------
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
def
choicegroup
(
element
,
value
,
status
,
render_template
,
msg
=
''
):
'''
...
...
@@ -161,7 +166,7 @@ def choicegroup(element, value, status, render_template, msg=''):
type
=
"checkbox"
else
:
type
=
"radio"
choices
=
{}
choices
=
[]
for
choice
in
element
:
if
not
choice
.
tag
==
'choice'
:
raise
Exception
(
"[courseware.capa.inputtypes.choicegroup] Error only <choice> tags should be immediate children of a <choicegroup>, found
%
s instead"
%
choice
.
tag
)
...
...
@@ -169,8 +174,66 @@ def choicegroup(element, value, status, render_template, msg=''):
ctext
+=
''
.
join
([
etree
.
tostring
(
x
)
for
x
in
choice
])
# TODO: what if choice[0] has math tags in it?
if
choice
.
text
is
not
None
:
ctext
+=
choice
.
text
# TODO: fix order?
choices
[
choice
.
get
(
"name"
)]
=
ctext
context
=
{
'id'
:
eid
,
'value'
:
value
,
'state'
:
status
,
'type'
:
type
,
'choices'
:
choices
}
choices
.
append
((
choice
.
get
(
"name"
),
ctext
))
context
=
{
'id'
:
eid
,
'value'
:
value
,
'state'
:
status
,
'input_type'
:
type
,
'choices'
:
choices
,
'inline'
:
True
,
'name_array_suffix'
:
''
}
html
=
render_template
(
"choicegroup.html"
,
context
)
return
etree
.
XML
(
html
)
#-----------------------------------------------------------------------------
def
extract_choices
(
element
):
'''
Extracts choices for a few input types, such as radiogroup and
checkboxgroup.
TODO: allow order of choices to be randomized, following lon-capa spec. Use "location" attribute,
ie random, top, bottom.
'''
choices
=
[]
for
choice
in
element
:
if
not
choice
.
tag
==
'choice'
:
raise
Exception
(
"[courseware.capa.inputtypes.extract_choices]
\
Expected a <choice> tag; got
%
s instead"
%
choice
.
tag
)
choice_text
=
''
.
join
([
etree
.
tostring
(
x
)
for
x
in
choice
])
choices
.
append
((
choice
.
get
(
"name"
),
choice_text
))
return
choices
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
def
radiogroup
(
element
,
value
,
status
,
render_template
,
msg
=
''
):
'''
Radio button inputs: (multiple choice)
'''
eid
=
element
.
get
(
'id'
)
choices
=
extract_choices
(
element
)
context
=
{
'id'
:
eid
,
'value'
:
value
,
'state'
:
status
,
'input_type'
:
'radio'
,
'choices'
:
choices
,
'inline'
:
False
,
'name_array_suffix'
:
'[]'
}
html
=
render_template
(
"choicegroup.html"
,
context
)
return
etree
.
XML
(
html
)
# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.
@register_render_function
def
checkboxgroup
(
element
,
value
,
status
,
render_template
,
msg
=
''
):
'''
Checkbox inputs: (select one or more choices)
'''
eid
=
element
.
get
(
'id'
)
choices
=
extract_choices
(
element
)
context
=
{
'id'
:
eid
,
'value'
:
value
,
'state'
:
status
,
'input_type'
:
'checkbox'
,
'choices'
:
choices
,
'inline'
:
False
,
'name_array_suffix'
:
'[]'
}
html
=
render_template
(
"choicegroup.html"
,
context
)
return
etree
.
XML
(
html
)
...
...
common/lib/capa/capa/responsetypes.py
View file @
2ad5d52b
...
...
@@ -267,6 +267,94 @@ class LoncapaResponse(object):
return
u'LoncapaProblem Response
%
s'
%
self
.
xml
.
tag
#-----------------------------------------------------------------------------
class
ChoiceResponse
(
LoncapaResponse
):
'''
This Response type is used when the student chooses from a discrete set of
choices. Currently, to be marked correct, all "correct" choices must be
supplied by the student, and no extraneous choices may be included.
This response type allows for two inputtypes: radiogroups and checkbox
groups. radiogroups are used when the student should select a single answer,
and checkbox groups are used when the student may supply 0+ answers.
Note: it is suggested to include a "None of the above" choice when no
answer is correct for a checkboxgroup inputtype; this ensures that a student
must actively mark something to get credit.
If two choices are marked as correct with a radiogroup, the student will
have no way to get the answer right.
TODO: Allow for marking choices as 'optional' and 'required', which would
not penalize a student for including optional answers and would also allow
for questions in which the student can supply one out of a set of correct
answers.This would also allow for survey-style questions in which all
answers are correct.
Example:
<choiceresponse>
<radiogroup>
<choice correct="false">
<text>This is a wrong answer.</text>
</choice>
<choice correct="true">
<text>This is the right answer.</text>
</choice>
<choice correct="false">
<text>This is another wrong answer.</text>
</choice>
</radiogroup>
</choiceresponse>
In the above example, radiogroup can be replaced with checkboxgroup to allow
the student to select more than one choice.
'''
response_tag
=
'choiceresponse'
max_inputfields
=
1
allowed_inputfields
=
[
'checkboxgroup'
,
'radiogroup'
]
def
setup_response
(
self
):
self
.
assign_choice_names
()
correct_xml
=
self
.
xml
.
xpath
(
'//*[@id=$id]//choice[@correct="true"]'
,
id
=
self
.
xml
.
get
(
'id'
))
self
.
correct_choices
=
set
([
choice
.
get
(
'name'
)
for
choice
in
correct_xml
])
def
assign_choice_names
(
self
):
'''
Initialize name attributes in <choice> tags for this response.
'''
for
index
,
choice
in
enumerate
(
self
.
xml
.
xpath
(
'//*[@id=$id]//choice'
,
id
=
self
.
xml
.
get
(
'id'
))):
choice
.
set
(
"name"
,
"choice_"
+
str
(
index
))
def
get_score
(
self
,
student_answers
):
student_answer
=
student_answers
.
get
(
self
.
answer_id
,
[])
if
not
isinstance
(
student_answer
,
list
):
student_answer
=
[
student_answer
]
student_answer
=
set
(
student_answer
)
required_selected
=
len
(
self
.
correct_choices
-
student_answer
)
==
0
no_extra_selected
=
len
(
student_answer
-
self
.
correct_choices
)
==
0
correct
=
required_selected
&
no_extra_selected
if
correct
:
return
CorrectMap
(
self
.
answer_id
,
'correct'
)
else
:
return
CorrectMap
(
self
.
answer_id
,
'incorrect'
)
def
get_answers
(
self
):
return
{
self
.
answer_id
:
self
.
correct_choices
}
#-----------------------------------------------------------------------------
class
MultipleChoiceResponse
(
LoncapaResponse
):
# TODO: handle direction and randomize
...
...
@@ -470,13 +558,13 @@ class CustomResponse(LoncapaResponse):
or in a <script>...</script>
'''
snippets
=
[{
'snippet'
:
'''<customresponse>
<
startouttext/
>
<
text
>
<br/>
Suppose that
\
(I(t)
\
) rises from
\
(0
\
) to
\
(I_S
\
) at a time
\
(t_0
\n
eq 0
\
)
In the space provided below write an algebraic expression for
\
(I(t)
\
).
<br/>
<textline size="5" correct_answer="IS*u(t-t0)" />
<
endouttext/
>
<
/text
>
<answer type="loncapa/python">
correct=['correct']
try:
...
...
@@ -1211,5 +1299,5 @@ class ImageResponse(LoncapaResponse):
# TEMPORARY: List of all response subclasses
# FIXME: To be replaced by auto-registration
__all__
=
[
CodeResponse
,
NumericalResponse
,
FormulaResponse
,
CustomResponse
,
SchematicResponse
,
MultipleChoiceResponse
,
TrueFalseResponse
,
ExternalResponse
,
ImageResponse
,
OptionResponse
,
SymbolicResponse
,
String
Response
]
__all__
=
[
CodeResponse
,
NumericalResponse
,
FormulaResponse
,
CustomResponse
,
SchematicResponse
,
ExternalResponse
,
ImageResponse
,
OptionResponse
,
SymbolicResponse
,
StringResponse
,
ChoiceResponse
,
MultipleChoiceResponse
,
TrueFalse
Response
]
common/lib/capa/capa/templates/choicegroup.html
View file @
2ad5d52b
<form
class=
"
multiple-choice
"
>
<form
class=
"
choicegroup
"
>
% for choice_id, choice_description in choices
.items()
:
<label
for=
"input_${id}_${choice_id}"
>
<input
type=
"${
type}"
name=
"input_${id
}"
id=
"input_${id}_${choice_id}"
value=
"${choice_id}"
% for choice_id, choice_description in choices:
<label
for=
"input_${id}_${choice_id}"
>
<input
type=
"${
input_type}"
name=
"input_${id}${name_array_suffix
}"
id=
"input_${id}_${choice_id}"
value=
"${choice_id}"
%
if
choice_id
in
value:
checked=
"true"
%
endif
/>
${choice_description}
</label>
% if not inline:
<br/>
% endif
% endfor
<span
id=
"answer_${id}"
></span>
...
...
common/lib/xmodule/tests/__init__.py
View file @
2ad5d52b
...
...
@@ -325,6 +325,33 @@ class CodeResponseTest(unittest.TestCase):
else
:
self
.
assertTrue
(
test_lcp
.
correct_map
.
is_queued
(
answer_ids
[
j
]))
# Should be queued, message undelivered
class
ChoiceResponseTest
(
unittest
.
TestCase
):
def
test_cr_rb_grade
(
self
):
problem_file
=
os
.
path
.
dirname
(
__file__
)
+
"/test_files/choiceresponse_radio.xml"
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
problem_file
)
.
read
(),
'1'
,
system
=
i4xs
)
correct_answers
=
{
'1_2_1'
:
'choice_2'
,
'1_3_1'
:[
'choice_2'
,
'choice_3'
]}
test_answers
=
{
'1_2_1'
:
'choice_2'
,
'1_3_1'
:
'choice_2'
,
}
self
.
assertEquals
(
test_lcp
.
grade_answers
(
test_answers
)
.
get_correctness
(
'1_2_1'
),
'correct'
)
self
.
assertEquals
(
test_lcp
.
grade_answers
(
test_answers
)
.
get_correctness
(
'1_3_1'
),
'incorrect'
)
def
test_cr_cb_grade
(
self
):
problem_file
=
os
.
path
.
dirname
(
__file__
)
+
"/test_files/choiceresponse_checkbox.xml"
test_lcp
=
lcp
.
LoncapaProblem
(
open
(
problem_file
)
.
read
(),
'1'
,
system
=
i4xs
)
correct_answers
=
{
'1_2_1'
:
'choice_2'
,
'1_3_1'
:[
'choice_2'
,
'choice_3'
],
'1_4_1'
:[
'choice_2'
,
'choice_3'
]}
test_answers
=
{
'1_2_1'
:
'choice_2'
,
'1_3_1'
:
'choice_2'
,
'1_4_1'
:[
'choice_2'
,
'choice_3'
],
}
self
.
assertEquals
(
test_lcp
.
grade_answers
(
test_answers
)
.
get_correctness
(
'1_2_1'
),
'correct'
)
self
.
assertEquals
(
test_lcp
.
grade_answers
(
test_answers
)
.
get_correctness
(
'1_3_1'
),
'incorrect'
)
self
.
assertEquals
(
test_lcp
.
grade_answers
(
test_answers
)
.
get_correctness
(
'1_4_1'
),
'correct'
)
#-----------------------------------------------------------------------------
# Grading tests
...
...
common/lib/xmodule/tests/test_files/choiceresponse_checkbox.xml
0 → 100644
View file @
2ad5d52b
<problem>
<choiceresponse>
<checkboxgroup>
<choice
correct=
"false"
>
<startouttext
/>
This is foil One.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Two.
<endouttext
/>
</choice>
<choice
correct=
"true"
>
<startouttext
/>
This is foil Three.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Four.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Five.
<endouttext
/>
</choice>
</checkboxgroup>
</choiceresponse>
<choiceresponse>
<checkboxgroup>
<choice
correct=
"false"
>
<startouttext
/>
This is foil One.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Two.
<endouttext
/>
</choice>
<choice
correct=
"true"
>
<startouttext
/>
This is foil Three.
<endouttext
/>
</choice>
<choice
correct=
"true"
>
<startouttext
/>
This is foil Four.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Five.
<endouttext
/>
</choice>
</checkboxgroup>
</choiceresponse>
<choiceresponse>
<checkboxgroup>
<choice
correct=
"false"
>
<startouttext
/>
This is foil One.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Two.
<endouttext
/>
</choice>
<choice
correct=
"true"
>
<startouttext
/>
This is foil Three.
<endouttext
/>
</choice>
<choice
correct=
"true"
>
<startouttext
/>
This is foil Four.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Five.
<endouttext
/>
</choice>
</checkboxgroup>
</choiceresponse>
</problem>
common/lib/xmodule/tests/test_files/choiceresponse_radio.xml
0 → 100644
View file @
2ad5d52b
<problem>
<choiceresponse>
<radiogroup>
<choice
correct=
"false"
>
<startouttext
/>
This is foil One.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Two.
<endouttext
/>
</choice>
<choice
correct=
"true"
>
<startouttext
/>
This is foil Three.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Four.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Five.
<endouttext
/>
</choice>
</radiogroup>
</choiceresponse>
<choiceresponse>
<radiogroup>
<choice
correct=
"false"
>
<startouttext
/>
This is foil One.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Two.
<endouttext
/>
</choice>
<choice
correct=
"true"
>
<startouttext
/>
This is foil Three.
<endouttext
/>
</choice>
<choice
correct=
"true"
>
<startouttext
/>
This is foil Four.
<endouttext
/>
</choice>
<choice
correct=
"false"
>
<startouttext
/>
This is foil Five.
<endouttext
/>
</choice>
</radiogroup>
</choiceresponse>
</problem>
common/lib/xmodule/xmodule/capa_module.py
View file @
2ad5d52b
...
...
@@ -371,7 +371,16 @@ class CapaModule(XModule):
for
key
in
get
:
# e.g. input_resistor_1 ==> resistor_1
_
,
_
,
name
=
key
.
partition
(
'_'
)
answers
[
name
]
=
get
[
key
]
# This allows for answers which require more than one value for
# the same form input (e.g. checkbox inputs). The convention is that
# if the name ends with '[]' (which looks like an array), then the
# answer will be an array.
if
not
name
.
endswith
(
'[]'
):
answers
[
name
]
=
get
[
key
]
else
:
name
=
name
[:
-
2
]
answers
[
name
]
=
get
.
getlist
(
key
)
return
answers
...
...
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