Commit fd0e2f85 by Arjun Singh

Merged two multiple-choice-like response types into a single response; created…

Merged two multiple-choice-like response types into a single response; created inputtypes for checkboxes and radio buttons adn cleaned up the code a bit. Fixed checkbox responses.
parent defdc611
...@@ -39,7 +39,7 @@ import responsetypes ...@@ -39,7 +39,7 @@ import responsetypes
# dict of tagname, Response Class -- this should come from auto-registering # dict of tagname, Response Class -- this should come from auto-registering
response_tag_dict = dict([(x.response_tag,x) for x in responsetypes.__all__]) 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', 'radiogroup', 'checkboxgroup']
solution_types = ['solution'] # extra things displayed after "show answers" is pressed solution_types = ['solution'] # extra things displayed after "show answers" is pressed
response_properties = ["responseparam", "answer"] # these get captured as student responses response_properties = ["responseparam", "answer"] # these get captured as student responses
......
...@@ -8,7 +8,8 @@ Module containing the problem elements which render into input objects ...@@ -8,7 +8,8 @@ Module containing the problem elements which render into input objects
- textline - textline
- textbox (change this to textarea?) - textbox (change this to textarea?)
- schemmatic - schemmatic
- choicegroup (for multiplechoice: checkbox, radio, or select option) - radiogroup
- checkboxgroup
- imageinput (for clickable image) - imageinput (for clickable image)
- optioninput (for option list) - optioninput (for option list)
...@@ -145,31 +146,55 @@ def optioninput(element, value, status, render_template, msg=''): ...@@ -145,31 +146,55 @@ def optioninput(element, value, status, render_template, msg=''):
return etree.XML(html) return etree.XML(html)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
@register_render_function def extract_choices(element):
def choicegroup(element, value, status, render_template, msg=''):
''' '''
Radio button inputs: multiple choice or true/false 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, TODO: allow order of choices to be randomized, following lon-capa spec. Use "location" attribute,
ie random, top, bottom. ie random, top, bottom.
''' '''
eid=element.get('id')
if element.get('type') == "MultipleChoice": choices = []
type="radio"
elif element.get('type') == "TrueFalse":
type="checkbox"
else:
type="radio"
choices={}
for choice in element: for choice in element:
if not choice.tag=='choice': 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) raise Exception("[courseware.capa.inputtypes.extract_choices] \
ctext = "" Expected a <choice> tag; got %s instead"
ctext += ''.join([etree.tostring(x) for x in choice]) # TODO: what if choice[0] has math tags in it? % choice.tag)
if choice.text is not None: choice_text = ''.join([etree.tostring(x) for x in choice])
ctext += choice.text # TODO: fix order?
choices[choice.get("name")] = ctext choices.append((choice.get("name"), choice_text))
context={'id':eid, 'value':value, 'state':status, 'type':type, 'choices':choices}
return choices
@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 }
html = render_template("choicegroup.html", context)
return etree.XML(html)
@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 }
html = render_template("choicegroup.html", context) html = render_template("choicegroup.html", context)
return etree.XML(html) return etree.XML(html)
......
...@@ -267,83 +267,51 @@ class LoncapaResponse(object): ...@@ -267,83 +267,51 @@ class LoncapaResponse(object):
return u'LoncapaProblem Response %s' % self.xml.tag return u'LoncapaProblem Response %s' % self.xml.tag
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
class ChoiceResponse(LoncapaResponse):
class MultipleChoiceResponse(LoncapaResponse): response_tag = 'choiceresponse'
# TODO: handle direction and randomize max_inputfields = 1
snippets = [{'snippet': '''<multiplechoiceresponse direction="vertical" randomize="yes"> allowed_inputfields = ['checkboxgroup', 'radiogroup']
<choicegroup type="MultipleChoice">
<choice location="random" correct="false"><span>`a+b`<br/></span></choice>
<choice location="random" correct="true"><span><math>a+b^2</math><br/></span></choice>
<choice location="random" correct="false"><math>a+b+c</math></choice>
<choice location="bottom" correct="false"><math>a+b+d</math></choice>
</choicegroup>
</multiplechoiceresponse>
'''}]
response_tag = 'multiplechoiceresponse'
max_inputfields = 1
allowed_inputfields = ['choicegroup']
def setup_response(self): def setup_response(self):
self.mc_setup_response() # call secondary setup for MultipleChoice questions, to set name attributes
# define correct choices (after calling secondary setup) self.assign_choice_names()
xml = self.xml
cxml = xml.xpath('//*[@id=$id]//choice[@correct="true"]',id=xml.get('id')) correct_xml = self.xml.xpath('//*[@id=$id]//choice[@correct="true"]',
self.correct_choices = [choice.get('name') for choice in cxml] id=self.xml.get('id'))
def mc_setup_response(self): self.correct_choices = set([choice.get('name') for choice in correct_xml])
def assign_choice_names(self):
''' '''
Initialize name attributes in <choice> stanzas in the <choicegroup> in this response. Initialize name attributes in <choice> tags for his response.
''' '''
i=0
for response in self.xml.xpath("choicegroup"): for index, choice in enumerate(self.xml.xpath('//*[@id=$id]//choice',
rtype = response.get('type') id=self.xml.get('id'))):
if rtype not in ["MultipleChoice"]: choice.set("name", "choice_"+str(index))
response.set("type", "MultipleChoice") # force choicegroup to be MultipleChoice if not valid
for choice in list(response):
if choice.get("name") is None:
choice.set("name", "choice_"+str(i))
i+=1
else:
choice.set("name", "choice_"+choice.get("name"))
def get_score(self, student_answers): def get_score(self, student_answers):
'''
grade student response. student_answer = student_answers.get(self.answer_id, [])
'''
# log.debug('%s: student_answers=%s, correct_choices=%s' % (unicode(self),student_answers,self.correct_choices)) if not isinstance(student_answer, list):
if self.answer_id in student_answers and student_answers[self.answer_id] in self.correct_choices: 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') return CorrectMap(self.answer_id,'correct')
else: else:
return CorrectMap(self.answer_id,'incorrect') return CorrectMap(self.answer_id,'incorrect')
def get_answers(self): def get_answers(self):
return {self.answer_id:self.correct_choices} return { self.answer_id : self.correct_choices }
class TrueFalseResponse(MultipleChoiceResponse):
response_tag = 'truefalseresponse'
def mc_setup_response(self):
i=0
for response in self.xml.xpath("choicegroup"):
response.set("type", "TrueFalse")
for choice in list(response):
if choice.get("name") is None:
choice.set("name", "choice_"+str(i))
i+=1
else:
choice.set("name", "choice_"+choice.get("name"))
def get_score(self, student_answers):
correct = set(self.correct_choices)
answers = set(student_answers.get(self.answer_id, []))
if correct == answers:
return CorrectMap( self.answer_id , 'correct')
return CorrectMap(self.answer_id ,'incorrect')
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
...@@ -1211,5 +1179,5 @@ class ImageResponse(LoncapaResponse): ...@@ -1211,5 +1179,5 @@ class ImageResponse(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
__all__ = [ CodeResponse, NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, MultipleChoiceResponse, TrueFalseResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse, StringResponse ] __all__ = [ CodeResponse, NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse, StringResponse, ChoiceResponse ]
<form class="multiple-choice"> <form class="choicegroup">
% for choice_id, choice_description in choices.items(): % for choice_id, choice_description in choices:
<label for="input_${id}_${choice_id}"> <input type="${type}" name="input_${id}" id="input_${id}_${choice_id}" value="${choice_id}" <label for="input_${id}_${choice_id}"> <input type="${input_type}" name="input_${id}[]" id="input_${id}_${choice_id}" value="${choice_id}"
% if choice_id in value: % if choice_id in value:
checked="true" checked="true"
% endif % endif
/> ${choice_description} </label> /> ${choice_description} </label>
<br/>
% endfor % endfor
<span id="answer_${id}"></span> <span id="answer_${id}"></span>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment