From a23c51e39c2671847e9b2ec5be9f8e5fc7671d56 Mon Sep 17 00:00:00 2001 From: Arjun Singh Date: Thu, 19 Jul 2012 08:26:48 -0700 Subject: [PATCH] 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. --- common/lib/capa/capa/capa_problem.py | 2 +- common/lib/capa/capa/inputtypes.py | 63 ++++++++---- common/lib/capa/capa/responsetypes.py | 96 +++++++------------ .../lib/capa/capa/templates/choicegroup.html | 7 +- 4 files changed, 81 insertions(+), 87 deletions(-) diff --git a/common/lib/capa/capa/capa_problem.py b/common/lib/capa/capa/capa_problem.py index dc0a3f285d..1709d987ac 100644 --- a/common/lib/capa/capa/capa_problem.py +++ b/common/lib/capa/capa/capa_problem.py @@ -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', 'radiogroup', 'checkboxgroup'] solution_types = ['solution'] # extra things displayed after "show answers" is pressed response_properties = ["responseparam", "answer"] # these get captured as student responses diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index c7a4da4ab4..351d51f6c1 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -8,7 +8,8 @@ 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) +- radiogroup +- checkboxgroup - imageinput (for clickable image) - optioninput (for option list) @@ -146,31 +147,55 @@ def optioninput(element, value, status, render_template, msg=''): return etree.XML(html) #----------------------------------------------------------------------------- -@register_render_function -def choicegroup(element, value, status, render_template, msg=''): +def extract_choices(element): ''' - 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, ie random, top, bottom. ''' - eid=element.get('id') - if element.get('type') == "MultipleChoice": - type="radio" - elif element.get('type') == "TrueFalse": - type="checkbox" - else: - type="radio" - choices={} + + choices = [] + for choice in element: if not choice.tag=='choice': - raise Exception("[courseware.capa.inputtypes.choicegroup] Error only tags should be immediate children of a , found %s instead" % choice.tag) - ctext = "" - 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} + raise Exception("[courseware.capa.inputtypes.extract_choices] \ + Expected a 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 + +@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) return etree.XML(html) diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index 86c6a08959..46cb8a7ad7 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -267,83 +267,51 @@ class LoncapaResponse(object): return u'LoncapaProblem Response %s' % self.xml.tag #----------------------------------------------------------------------------- +class ChoiceResponse(LoncapaResponse): -class MultipleChoiceResponse(LoncapaResponse): - # TODO: handle direction and randomize - snippets = [{'snippet': ''' - - `a+b`
- a+b^2
- a+b+c - a+b+d -
-
- '''}] - - response_tag = 'multiplechoiceresponse' - max_inputfields = 1 - allowed_inputfields = ['choicegroup'] + response_tag = 'choiceresponse' + max_inputfields = 1 + allowed_inputfields = ['checkboxgroup', 'radiogroup'] 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) - xml = self.xml - cxml = xml.xpath('//*[@id=$id]//choice[@correct="true"]',id=xml.get('id')) - self.correct_choices = [choice.get('name') for choice in cxml] + self.assign_choice_names() - def mc_setup_response(self): + 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 stanzas in the in this response. + Initialize name attributes in tags for his response. ''' - i=0 - for response in self.xml.xpath("choicegroup"): - rtype = response.get('type') - if rtype not in ["MultipleChoice"]: - 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")) + + 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): - ''' - grade student response. - ''' - # log.debug('%s: student_answers=%s, correct_choices=%s' % (unicode(self),student_answers,self.correct_choices)) - if self.answer_id in student_answers and student_answers[self.answer_id] in self.correct_choices: + + 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 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') + return { self.answer_id : self.correct_choices } #----------------------------------------------------------------------------- @@ -1211,5 +1179,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, StringResponse ] +__all__ = [ CodeResponse, NumericalResponse, FormulaResponse, CustomResponse, SchematicResponse, ExternalResponse, ImageResponse, OptionResponse, SymbolicResponse, StringResponse, ChoiceResponse ] diff --git a/common/lib/capa/capa/templates/choicegroup.html b/common/lib/capa/capa/templates/choicegroup.html index 938ce5b535..ec0c5a3d1a 100644 --- a/common/lib/capa/capa/templates/choicegroup.html +++ b/common/lib/capa/capa/templates/choicegroup.html @@ -1,11 +1,12 @@ -
+ - % for choice_id, choice_description in choices.items(): - +
% endfor