From f82662356fb3509fe4d0cd1513f0cf45c2333712 Mon Sep 17 00:00:00 2001 From: Syed Hassan Raza Date: Tue, 1 Sep 2015 10:57:17 -0700 Subject: [PATCH] Revert "OSPR-535 Partial Credit" This reverts commit 7624c633628bf8c837806f21ff2d845e2d32268f. --- common/lib/capa/capa/correctmap.py | 15 +- common/lib/capa/capa/inputtypes.py | 4 +- common/lib/capa/capa/responsetypes.py | 629 ++---------------- .../capa/templates/chemicalequationinput.html | 2 +- .../lib/capa/capa/templates/choicegroup.html | 6 +- .../lib/capa/capa/templates/choicetext.html | 2 - .../capa/capa/templates/crystallography.html | 4 +- .../capa/templates/designprotein2dinput.html | 4 +- .../capa/templates/drag_and_drop_input.html | 4 +- .../capa/capa/templates/editageneinput.html | 4 +- .../capa/capa/templates/editamolecule.html | 4 +- common/lib/capa/capa/templates/jsinput.html | 4 +- common/lib/capa/capa/templates/textline.html | 4 +- .../lib/capa/capa/templates/vsepr_input.html | 4 +- .../capa/capa/tests/response_xml_factory.py | 71 +- common/lib/capa/capa/tests/test_correctmap.py | 38 +- .../lib/capa/capa/tests/test_responsetypes.py | 482 +------------- .../lib/xmodule/xmodule/css/capa/display.scss | 80 +-- .../static/images/partially-correct-icon.png | Bin 224 -> 1230 bytes common/test/acceptance/pages/lms/problem.py | 24 - .../acceptance/tests/lms/test_lms_problems.py | 32 - 21 files changed, 99 insertions(+), 1318 deletions(-) diff --git a/common/lib/capa/capa/correctmap.py b/common/lib/capa/capa/correctmap.py index 26a6b60fba..9af728552a 100644 --- a/common/lib/capa/capa/correctmap.py +++ b/common/lib/capa/capa/correctmap.py @@ -10,7 +10,7 @@ class CorrectMap(object): in a capa problem. The response evaluation result for each answer_id includes (correctness, npoints, msg, hint, hintmode). - - correctness : 'correct', 'incorrect', or 'partially-correct' + - correctness : either 'correct' or 'incorrect' - npoints : None, or integer specifying number of points awarded for this answer_id - msg : string (may have HTML) giving extra message response (displayed below textline or textbox) @@ -101,23 +101,10 @@ class CorrectMap(object): self.set(k, **correct_map[k]) def is_correct(self, answer_id): - """ - Takes an answer_id - Returns true if the problem is correct OR partially correct. - """ if answer_id in self.cmap: return self.cmap[answer_id]['correctness'] in ['correct', 'partially-correct'] return None - def is_partially_correct(self, answer_id): - """ - Takes an answer_id - Returns true if the problem is partially correct. - """ - if answer_id in self.cmap: - return self.cmap[answer_id]['correctness'] == 'partially-correct' - return None - def is_queued(self, answer_id): return answer_id in self.cmap and self.cmap[answer_id]['queuestate'] is not None diff --git a/common/lib/capa/capa/inputtypes.py b/common/lib/capa/capa/inputtypes.py index 4c0517852a..c487efed69 100644 --- a/common/lib/capa/capa/inputtypes.py +++ b/common/lib/capa/capa/inputtypes.py @@ -85,7 +85,6 @@ class Status(object): names = { 'correct': _('correct'), 'incorrect': _('incorrect'), - 'partially-correct': _('partially correct'), 'incomplete': _('incomplete'), 'unanswered': _('unanswered'), 'unsubmitted': _('unanswered'), @@ -95,7 +94,6 @@ class Status(object): # Translators: these are tooltips that indicate the state of an assessment question 'correct': _('This is correct.'), 'incorrect': _('This is incorrect.'), - 'partially-correct': _('This is partially correct.'), 'unanswered': _('This is unanswered.'), 'unsubmitted': _('This is unanswered.'), 'queued': _('This is being processed.'), @@ -898,7 +896,7 @@ class MatlabInput(CodeInput): Right now, we only want this button to show up when a problem has not been checked. """ - if self.status in ['correct', 'incorrect', 'partially-correct']: + if self.status in ['correct', 'incorrect']: return False else: return True diff --git a/common/lib/capa/capa/responsetypes.py b/common/lib/capa/capa/responsetypes.py index f1d79daea2..0d7b830540 100644 --- a/common/lib/capa/capa/responsetypes.py +++ b/common/lib/capa/capa/responsetypes.py @@ -139,8 +139,6 @@ class LoncapaResponse(object): tags = None hint_tag = None - has_partial_credit = False - credit_type = [] max_inputfields = None allowed_inputfields = [] @@ -215,18 +213,6 @@ class LoncapaResponse(object): self.default_answer_map[entry.get( 'id')] = contextualize_text(answer, self.context) - # Does this problem have partial credit? - # If so, what kind? Get it as a list of strings. - partial_credit = xml.xpath('.')[0].get('partial_credit', default=False) - - if str(partial_credit).lower().strip() == 'false': - self.has_partial_credit = False - self.credit_type = [] - else: - self.has_partial_credit = True - self.credit_type = partial_credit.split(',') - self.credit_type = [word.strip().lower() for word in self.credit_type] - if hasattr(self, 'setup_response'): self.setup_response() @@ -275,6 +261,7 @@ class LoncapaResponse(object): new_cmap = self.get_score(student_answers) self.get_hints(convert_files_to_filenames( student_answers), new_cmap, old_cmap) + # log.debug('new_cmap = %s' % new_cmap) return new_cmap def make_hint_div(self, hint_node, correct, student_answer, question_tag, @@ -826,23 +813,11 @@ class ChoiceResponse(LoncapaResponse): def setup_response(self): self.assign_choice_names() - correct_xml = self.xml.xpath( - '//*[@id=$id]//choice[@correct="true"]', - id=self.xml.get('id') - ) + 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 - ]) - - incorrect_xml = self.xml.xpath( - '//*[@id=$id]//choice[@correct="false"]', - id=self.xml.get('id') - ) - - self.incorrect_choices = set([ - choice.get('name') for choice in incorrect_xml - ]) + self.correct_choices = set([choice.get( + 'name') for choice in correct_xml]) def assign_choice_names(self): """ @@ -856,153 +831,25 @@ class ChoiceResponse(LoncapaResponse): if not choice.get('id'): choice.set("id", chr(ord("A") + index)) - def grade_via_every_decision_counts(self, **kwargs): - """ - Calculates partial credit on the Every Decision Counts scheme. - For each correctly selected or correctly blank choice, score 1 point. - Divide by total number of choices. - Arguments: - all_choices, the full set of checkboxes - student_answer, what the student actually chose - student_non_answers, what the student didn't choose - Returns a CorrectMap. - """ - - all_choices = kwargs['all_choices'] - student_answer = kwargs['student_answer'] - student_non_answers = kwargs['student_non_answers'] - - edc_max_grade = len(all_choices) - edc_current_grade = 0 - - good_answers = sum([1 for answer in student_answer if answer in self.correct_choices]) - good_non_answers = sum([1 for blank in student_non_answers if blank in self.incorrect_choices]) - edc_current_grade = good_answers + good_non_answers - - return_grade = round(self.get_max_score() * float(edc_current_grade) / float(edc_max_grade), 2) - - if edc_current_grade == edc_max_grade: - return CorrectMap(self.answer_id, correctness='correct') - elif edc_current_grade > 0: - return CorrectMap(self.answer_id, correctness='partially-correct', npoints=return_grade) - else: - return CorrectMap(self.answer_id, correctness='incorrect', npoints=0) - - def grade_via_halves(self, **kwargs): - """ - Calculates partial credit on the Halves scheme. - If no errors, full credit. - If one error, half credit as long as there are 3+ choices - If two errors, 1/4 credit as long as there are 5+ choices - (If not enough choices, no credit.) - Arguments: - all_choices, the full set of checkboxes - student_answer, what the student actually chose - student_non_answers, what the student didn't choose - Returns a CorrectMap - """ - - all_choices = kwargs['all_choices'] - student_answer = kwargs['student_answer'] - student_non_answers = kwargs['student_non_answers'] - - halves_error_count = 0 - - incorrect_answers = sum([1 for answer in student_answer if answer in self.incorrect_choices]) - missed_answers = sum([1 for blank in student_non_answers if blank in self.correct_choices]) - halves_error_count = incorrect_answers + missed_answers - - if halves_error_count == 0: - return_grade = self.get_max_score() - return CorrectMap(self.answer_id, correctness='correct', npoints=return_grade) - elif halves_error_count == 1 and len(all_choices) > 2: - return_grade = round(self.get_max_score() / 2.0, 2) - return CorrectMap(self.answer_id, correctness='partially-correct', npoints=return_grade) - elif halves_error_count == 2 and len(all_choices) > 4: - return_grade = round(self.get_max_score() / 4.0, 2) - return CorrectMap(self.answer_id, correctness='partially-correct', npoints=return_grade) - else: - return CorrectMap(self.answer_id, 'incorrect') - - def grade_without_partial_credit(self, **kwargs): - """ - Standard grading for checkbox problems. - 100% credit if all choices are correct; 0% otherwise - Arguments: student_answer, which is the items the student actually chose - """ - - student_answer = kwargs['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_score(self, student_answers): - # Setting up answer sets: - # all_choices: the full set of checkboxes - # student_answer: what the student actually chose (note no "s") - # student_non_answers: what they didn't choose - # self.correct_choices: boxes that should be checked - # self.incorrect_choices: boxes that should NOT be checked - - all_choices = self.correct_choices.union(self.incorrect_choices) - student_answer = student_answers.get(self.answer_id, []) if not isinstance(student_answer, list): student_answer = [student_answer] - # When a student leaves all the boxes unmarked, edX throws an error. - # This line checks for blank answers so that we can throw "false". - # This is not ideal. "None apply" should be a valid choice. - # Sadly, this is not the place where we can fix that problem. - empty_answer = student_answer == [] - - if empty_answer: - return CorrectMap(self.answer_id, 'incorrect') - + no_empty_answer = student_answer != [] student_answer = set(student_answer) - student_non_answers = all_choices - student_answer + required_selected = len(self.correct_choices - student_answer) == 0 + no_extra_selected = len(student_answer - self.correct_choices) == 0 - # No partial credit? Get grade right now. - if not self.has_partial_credit: - return self.grade_without_partial_credit(student_answer=student_answer) + correct = required_selected & no_extra_selected & no_empty_answer - # This below checks to see whether we're using an alternate grading scheme. - # Set partial_credit="false" (or remove it) to require an exact answer for any credit. - # Set partial_credit="EDC" to count each choice for equal points (Every Decision Counts). - # Set partial_credit="halves" to take half credit off for each error. - - # Translators: 'partial_credit' and the items in the 'graders' object - # are attribute names or values and should not be translated. - graders = { - 'edc': self.grade_via_every_decision_counts, - 'halves': self.grade_via_halves, - 'false': self.grade_without_partial_credit - } - - # Only one type of credit at a time. - if len(self.credit_type) > 1: - raise LoncapaProblemError('Only one type of partial credit is allowed for Checkbox problems.') - - # Make sure we're using an approved style. - if self.credit_type[0] not in graders: - raise LoncapaProblemError('partial_credit attribute should be one of: ' + ','.join(graders)) - - # Run the appropriate grader. - return graders[self.credit_type[0]]( - all_choices=all_choices, - student_answer=student_answer, - student_non_answers=student_non_answers - ) + if correct: + return CorrectMap(self.answer_id, 'correct') + else: + return CorrectMap(self.answer_id, 'incorrect') def get_answers(self): return {self.answer_id: list(self.correct_choices)} @@ -1149,14 +996,6 @@ class MultipleChoiceResponse(LoncapaResponse): multi_device_support = True def setup_response(self): - """ - Collects information from the XML for later use. - - correct_choices is a list of the correct choices. - partial_choices is a list of the partially-correct choices. - partial_values is a list of the scores that go with those - choices, defaulting to 0.5 if no value is specified. - """ # call secondary setup for MultipleChoice questions, to set name # attributes self.mc_setup_response() @@ -1171,19 +1010,8 @@ class MultipleChoiceResponse(LoncapaResponse): contextualize_text(choice.get('name'), self.context) for choice in cxml if contextualize_text(choice.get('correct'), self.context).upper() == "TRUE" - ] - if self.has_partial_credit: - self.partial_choices = [ - contextualize_text(choice.get('name'), self.context) - for choice in cxml - if contextualize_text(choice.get('correct'), self.context).lower() == 'partial' - ] - self.partial_values = [ - float(choice.get('point_value', default='0.5')) # Default partial credit: 50% - for choice in cxml - if contextualize_text(choice.get('correct'), self.context).lower() == 'partial' - ] + ] def get_extended_hints(self, student_answer_dict, new_cmap): """ @@ -1254,79 +1082,15 @@ class MultipleChoiceResponse(LoncapaResponse): self.do_shuffle(self.xml, problem) self.do_answer_pool(self.xml, problem) - def grade_via_points(self, **kwargs): - """ - Calculates partial credit based on the Points scheme. - Answer choices marked "partial" are given partial credit. - Default is 50%; other amounts may be set in point_value attributes. - Arguments: student_answers - Returns: a CorrectMap - """ - - student_answers = kwargs['student_answers'] - - if (self.answer_id in student_answers - and student_answers[self.answer_id] in self.correct_choices): - return CorrectMap(self.answer_id, correctness='correct') - - elif ( - self.answer_id in student_answers - and student_answers[self.answer_id] in self.partial_choices - ): - choice_index = self.partial_choices.index(student_answers[self.answer_id]) - credit_amount = self.partial_values[choice_index] - return CorrectMap(self.answer_id, correctness='partially-correct', npoints=credit_amount) - else: - return CorrectMap(self.answer_id, 'incorrect') - - def grade_without_partial_credit(self, **kwargs): - """ - Standard grading for multiple-choice problems. - 100% credit if choices are correct; 0% otherwise - Arguments: student_answers - Returns: a CorrectMap - """ - - student_answers = kwargs['student_answers'] - - if (self.answer_id in student_answers - and student_answers[self.answer_id] in self.correct_choices): - return CorrectMap(self.answer_id, correctness='correct') - else: - return CorrectMap(self.answer_id, 'incorrect') - def get_score(self, student_answers): """ grade student response. """ - - # No partial credit? Grade it right away. - if not self.has_partial_credit: - return self.grade_without_partial_credit(student_answers=student_answers) - - # This below checks to see whether we're using an alternate grading scheme. - # Set partial_credit="false" (or remove it) to require an exact answer for any credit. - # Set partial_credit="points" to set specific point values for specific choices. - - # Translators: 'partial_credit' and the items in the 'graders' object - # are attribute names or values and should not be translated. - graders = { - 'points': self.grade_via_points, - 'false': self.grade_without_partial_credit - } - - # Only one type of credit at a time. - if len(self.credit_type) > 1: - raise LoncapaProblemError('Only one type of partial credit is allowed for Multiple Choice problems.') - - # Make sure we're using an approved style. - if self.credit_type[0] not in graders: - raise LoncapaProblemError('partial_credit attribute should be one of: ' + ','.join(graders)) - - # Run the appropriate grader. - return graders[self.credit_type[0]]( - student_answers=student_answers - ) + if (self.answer_id in student_answers + and student_answers[self.answer_id] in self.correct_choices): + return CorrectMap(self.answer_id, 'correct') + else: + return CorrectMap(self.answer_id, 'incorrect') def get_answers(self): return {self.answer_id: self.correct_choices} @@ -1587,163 +1351,23 @@ class OptionResponse(LoncapaResponse): def setup_response(self): self.answer_fields = self.inputfields - def grade_via_points(self, problem_map, student_answers): - """ - Grades dropdown problems with "points"-style partial credit. - Full credit for any fully correct answer. - Partial credit for any partially correct answer. - Amount is set by point_values attribute, defaults to 50%. - Returns a CorrectMap. - """ - - answer_map = problem_map['correct'] - cmap = CorrectMap() - - for aid in answer_map: - # Set correct/incorrect first, check for partial credit later. - for word in answer_map[aid]: - if aid in student_answers and student_answers[aid] == word: - cmap.set(aid, 'correct') - break - else: - cmap.set(aid, 'incorrect') - - # For partial credit: - partial_map = problem_map['partial'] - points_map = problem_map['point_values'] - - if not cmap.is_correct(aid) and partial_map[aid] is not None: - for index, word in enumerate(partial_map[aid]): - # Set the correctness and point value - # for each answer id independently. - if aid in student_answers and student_answers[aid] == word: - cmap.set(aid, 'partially-correct') - cmap.set_property(aid, 'npoints', points_map[aid][index]) - break - else: - cmap.set(aid, 'incorrect') - - answer_variable = self.get_student_answer_variable_name(student_answers, aid) - if answer_variable: - cmap.set_property(aid, 'answervariable', answer_variable) - - return cmap - - def grade_without_partial_credit(self, problem_map, student_answers): - """ - Grades dropdown problems without partial credit. - Full credit for any correct answer, no credit otherwise. - Returns a CorrectMap. - """ - - answer_map = problem_map['correct'] - cmap = CorrectMap() - - for aid in answer_map: - for word in answer_map[aid]: - if aid in student_answers and student_answers[aid] == word: - cmap.set(aid, 'correct') - break - else: - cmap.set(aid, 'incorrect') - - answer_variable = self.get_student_answer_variable_name(student_answers, aid) - if answer_variable: - cmap.set_property(aid, 'answervariable', answer_variable) - - return cmap - def get_score(self, student_answers): - - problem_map = self.get_problem_attributes() - - # If no partial credit, grade it right now. - if not self.has_partial_credit: - return self.grade_without_partial_credit(problem_map, student_answers) - - # This below checks to see whether we're using an alternate grading scheme. - # Set partial_credit="false" (or remove it) to require an exact answer for any credit. - # Set partial_credit="points" to allow credit for listed alternative answers. - - # Translators: 'partial_credit' and the items in the 'graders' object - # are attribute names or values and should not be translated. - graders = { - 'points': self.grade_via_points, - 'false': self.grade_without_partial_credit - } - - # Only one type of credit at a time. - if len(self.credit_type) > 1: - raise LoncapaProblemError('Only one type of partial credit is allowed for Dropdown problems.') - # Make sure we're using an approved style. - if self.credit_type[0] not in graders: - raise LoncapaProblemError('partial_credit attribute should be one of: ' + ','.join(graders)) - - # Run the appropriate grader. - return graders[self.credit_type[0]]( - problem_map=problem_map, - student_answers=student_answers - ) - - def get_problem_attributes(self): - """ - This returns a dict built of of three smaller dictionaries. - Keys are: - "correct": - A dictionary with problem ids as keys. - Entries are lists of the correct answers for that id. - "partial": - A dictionary with problem ids as keys. - Entries are lists of the partially-correct answers for that id. - "point_values": - Matches the "partial" one, but gives point values instead. - Defaults to 50% credit. - """ - - default_credit = 0.5 - - problem_map = dict() - - for target in ['correct', 'partial', 'point_values']: - - small_map = dict([ - (af.get('id'), contextualize_text( - af.get(target, default=None), - self.context - )) - for af in self.answer_fields - ]) - - for answer_id in small_map: - if small_map[answer_id] is not None: - # Split on commas and strip whitespace - # to allow for multiple options. - small_map[answer_id] = small_map[answer_id].split(',') - for index, word in enumerate(small_map[answer_id]): - # Pick out whether we're getting numbers or strings. - if target in ['point_values']: - small_map[answer_id][index] = float(word.strip()) - else: - small_map[answer_id][index] = str(word.strip()) - # If we find nothing and we're looking for points, return the default. - elif target == 'point_values': - if problem_map['partial'][answer_id] is not None: - num_partial = len(problem_map['partial'][answer_id]) - small_map[answer_id] = [default_credit] * num_partial - else: - small_map[answer_id] = [] - - # Add a copy of the in-loop map to the big map. - problem_map[target] = dict(small_map) - - return problem_map + cmap = CorrectMap() + amap = self.get_answers() + for aid in amap: + if aid in student_answers and student_answers[aid] == amap[aid]: + cmap.set(aid, 'correct') + else: + cmap.set(aid, 'incorrect') + answer_variable = self.get_student_answer_variable_name(student_answers, aid) + if answer_variable: + cmap.set_property(aid, 'answervariable', answer_variable) + return cmap def get_answers(self): - """ - Returns a dictionary with problem ids as keys. - Each entry is a list of the correct answers for that id. - """ - return self.get_problem_attributes()['correct'] + amap = dict([(af.get('id'), contextualize_text(af.get( + 'correct'), self.context)) for af in self.answer_fields]) + return amap def get_student_answer_variable_name(self, student_answers, aid): """ @@ -1867,14 +1491,6 @@ class NumericalResponse(LoncapaResponse): if self.answer_id not in student_answers: return CorrectMap(self.answer_id, 'incorrect') - # Make sure we're using an approved partial credit style. - # Currently implemented: 'close' and 'list' - if self.has_partial_credit: - graders = ['list', 'close'] - for style in self.credit_type: - if style not in graders: - raise LoncapaProblemError('partial_credit attribute should be one of: ' + ','.join(graders)) - student_answer = student_answers[self.answer_id] _ = self.capa_system.i18n.ugettext @@ -1909,30 +1525,6 @@ class NumericalResponse(LoncapaResponse): except Exception: raise general_exception # End `evaluator` block -- we figured out the student's answer! - - tree = self.xml - - # What multiple of the tolerance is worth partial credit? - has_partial_range = tree.xpath('responseparam[@partial_range]') - if has_partial_range: - partial_range = float(has_partial_range[0].get('partial_range', default='2')) - else: - partial_range = 2 - - # Take in alternative answers that are worth partial credit. - has_partial_answers = tree.xpath('responseparam[@partial_answers]') - if has_partial_answers: - partial_answers = has_partial_answers[0].get('partial_answers').split(',') - for index, word in enumerate(partial_answers): - partial_answers[index] = word.strip() - partial_answers[index] = self.get_staff_ans(partial_answers[index]) - - else: - partial_answers = False - - partial_score = 0.5 - is_correct = 'incorrect' - if self.range_tolerance: if isinstance(student_float, complex): raise StudentInputError(_(u"You may not use complex numbers in range tolerance problems")) @@ -1954,71 +1546,19 @@ class NumericalResponse(LoncapaResponse): tolerance=float_info.epsilon, relative_tolerance=True ): - is_correct = 'correct' if inclusion else 'incorrect' + correct = inclusion break else: - if boundaries[0] < student_float < boundaries[1]: - is_correct = 'correct' - else: - if self.has_partial_credit is False: - pass - elif 'close' in self.credit_type: - # Partial credit: 50% if the student is outside the specified boundaries, - # but within an extended set of boundaries. - - extended_boundaries = [] - boundary_range = boundaries[1] - boundaries[0] - extended_boundaries.append(boundaries[0] - partial_range * boundary_range) - extended_boundaries.append(boundaries[1] + partial_range * boundary_range) - if extended_boundaries[0] < student_float < extended_boundaries[1]: - is_correct = 'partially-correct' - + correct = boundaries[0] < student_float < boundaries[1] else: correct_float = self.get_staff_ans(self.correct_answer) - - # Partial credit is available in three cases: - # If the student answer is within expanded tolerance of the actual answer, - # the student gets 50% credit. (Currently set as the default.) - # Set via partial_credit="close" in the numericalresponse tag. - # - # If the student answer is within regular tolerance of an alternative answer, - # the student gets 50% credit. (Same default.) - # Set via partial_credit="list" - # - # If the student answer is within expanded tolerance of an alternative answer, - # the student gets 25%. (We take the 50% and square it, at the moment.) - # Set via partial_credit="list,close" or "close, list" or the like. - - if str(self.tolerance).endswith('%'): - expanded_tolerance = str(partial_range * float(str(self.tolerance)[:-1])) + '%' - else: - expanded_tolerance = partial_range * float(self.tolerance) - - if compare_with_tolerance(student_float, correct_float, self.tolerance): - is_correct = 'correct' - elif self.has_partial_credit is False: - pass - elif 'list' in self.credit_type: - for value in partial_answers: - if compare_with_tolerance(student_float, value, self.tolerance): - is_correct = 'partially-correct' - break - elif 'close' in self.credit_type: - if compare_with_tolerance(student_float, correct_float, expanded_tolerance): - is_correct = 'partially-correct' - break - elif compare_with_tolerance(student_float, value, expanded_tolerance): - is_correct = 'partially-correct' - partial_score = partial_score * partial_score - break - elif 'close' in self.credit_type: - if compare_with_tolerance(student_float, correct_float, expanded_tolerance): - is_correct = 'partially-correct' - - if is_correct == 'partially-correct': - return CorrectMap(self.answer_id, is_correct, npoints=partial_score) + correct = compare_with_tolerance( + student_float, correct_float, self.tolerance + ) + if correct: + return CorrectMap(self.answer_id, 'correct') else: - return CorrectMap(self.answer_id, is_correct) + return CorrectMap(self.answer_id, 'incorrect') def compare_answer(self, ans1, ans2): """ @@ -2327,9 +1867,6 @@ class CustomResponse(LoncapaResponse): code = None expect = None - # Standard amount for partial credit if not otherwise specified: - default_pc = 0.5 - def setup_response(self): xml = self.xml @@ -2506,12 +2043,7 @@ class CustomResponse(LoncapaResponse): if grade_decimals: npoints = max_points * grade_decimals[k] else: - if correct[k] == 'correct': - npoints = max_points - elif correct[k] == 'partially-correct': - npoints = max_points * self.default_pc - else: - npoints = 0 + npoints = max_points if correct[k] == 'correct' else 0 correct_map.set(idset[k], correct[k], msg=messages[k], npoints=npoints) return correct_map @@ -2552,30 +2084,13 @@ class CustomResponse(LoncapaResponse): ) if isinstance(ret, dict): # One kind of dictionary the check function can return has the - # form {'ok': BOOLEAN or STRING, 'msg': STRING, 'grade_decimal' (optional): FLOAT (between 0.0 and 1.0)} + # form {'ok': BOOLEAN, 'msg': STRING, 'grade_decimal' (optional): FLOAT (between 0.0 and 1.0)} # 'ok' will control the checkmark, while grade_decimal, if present, will scale # the score the student receives on the response. # If there are multiple inputs, they all get marked # to the same correct/incorrect value if 'ok' in ret: - - # Returning any falsy value or the "false" string for "ok" gives incorrect. - # Returning any string that includes "partial" for "ok" gives partial credit. - # Returning any other truthy value for "ok" gives correct - - ok_val = str(ret['ok']).lower().strip() if bool(ret['ok']) else 'false' - - if ok_val == 'false': - correct = 'incorrect' - elif 'partial' in ok_val: - correct = 'partially-correct' - else: - correct = 'correct' - correct = [correct] * len(idset) # All inputs share the same mark. - - # old version, no partial credit: - # correct = ['correct' if ret['ok'] else 'incorrect'] * len(idset) - + correct = ['correct' if ret['ok'] else 'incorrect'] * len(idset) msg = ret.get('msg', None) msg = self.clean_message_html(msg) @@ -2587,14 +2102,9 @@ class CustomResponse(LoncapaResponse): self.context['messages'][0] = msg if 'grade_decimal' in ret: - decimal = float(ret['grade_decimal']) + decimal = ret['grade_decimal'] else: - if correct[0] == 'correct': - decimal = 1.0 - elif correct[0] == 'partially-correct': - decimal = self.default_pc - else: - decimal = 0.0 + decimal = 1.0 if ret['ok'] else 0.0 grade_decimals = [decimal] * len(idset) self.context['grade_decimals'] = grade_decimals @@ -2602,11 +2112,7 @@ class CustomResponse(LoncapaResponse): # the form: # { 'overall_message': STRING, # 'input_list': [ - # { - # 'ok': BOOLEAN or STRING, - # 'msg': STRING, - # 'grade_decimal' (optional): FLOAT (between 0.0 and 1.0) - # }, + # { 'ok': BOOLEAN, 'msg': STRING, 'grade_decimal' (optional): FLOAT (between 0.0 and 1.0)}, # ... # ] # } @@ -2623,35 +2129,16 @@ class CustomResponse(LoncapaResponse): correct = [] messages = [] grade_decimals = [] - - # Returning any falsy value or the "false" string for "ok" gives incorrect. - # Returning any string that includes "partial" for "ok" gives partial credit. - # Returning any other truthy value for "ok" gives correct - for input_dict in input_list: - if str(input_dict['ok']).lower().strip() == "false" or not input_dict['ok']: - correct.append('incorrect') - elif 'partial' in str(input_dict['ok']).lower().strip(): - correct.append('partially-correct') - else: - correct.append('correct') - - # old version, no partial credit - # correct.append('correct' - # if input_dict['ok'] else 'incorrect') - + correct.append('correct' + if input_dict['ok'] else 'incorrect') msg = (self.clean_message_html(input_dict['msg']) if 'msg' in input_dict else None) messages.append(msg) if 'grade_decimal' in input_dict: decimal = input_dict['grade_decimal'] else: - if str(input_dict['ok']).lower().strip() == 'true': - decimal = 1.0 - elif 'partial' in str(input_dict['ok']).lower().strip(): - decimal = self.default_pc - else: - decimal = 0.0 + decimal = 1.0 if input_dict['ok'] else 0.0 grade_decimals.append(decimal) self.context['messages'] = messages @@ -2668,21 +2155,7 @@ class CustomResponse(LoncapaResponse): ) else: - - # Returning any falsy value or the "false" string for "ok" gives incorrect. - # Returning any string that includes "partial" for "ok" gives partial credit. - # Returning any other truthy value for "ok" gives correct - - if str(ret).lower().strip() == "false" or not bool(ret): - correct = 'incorrect' - elif 'partial' in str(ret).lower().strip(): - correct = 'partially-correct' - else: - correct = 'correct' - correct = [correct] * len(idset) - - # old version, no partial credit: - # correct = ['correct' if ret else 'incorrect'] * len(idset) + correct = ['correct' if ret else 'incorrect'] * len(idset) self.context['correct'] = correct diff --git a/common/lib/capa/capa/templates/chemicalequationinput.html b/common/lib/capa/capa/templates/chemicalequationinput.html index 6a6c727bde..2ae29406b6 100644 --- a/common/lib/capa/capa/templates/chemicalequationinput.html +++ b/common/lib/capa/capa/templates/chemicalequationinput.html @@ -17,7 +17,7 @@

-% if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: +% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']: % endif diff --git a/common/lib/capa/capa/templates/choicegroup.html b/common/lib/capa/capa/templates/choicegroup.html index 1b16470de3..da5d372401 100644 --- a/common/lib/capa/capa/templates/choicegroup.html +++ b/common/lib/capa/capa/templates/choicegroup.html @@ -7,8 +7,6 @@ <% if status == 'correct': correctness = 'correct' - elif status == 'partially-correct': - correctness = 'partially-correct' elif status == 'incorrect': correctness = 'incorrect' else: @@ -33,7 +31,7 @@ /> ${choice_description} % if input_type == 'radio' and ( (isinstance(value, basestring) and (choice_id == value)) or (not isinstance(value, basestring) and choice_id in value) ): - % if status in ('correct', 'partially-correct', 'incorrect') and not show_correctness=='never': + % if status in ('correct', 'incorrect') and not show_correctness=='never': ${choice_description|h} - ${status.display_name} % endif % endif @@ -62,4 +60,4 @@ % if msg: ${msg|n} % endif - \ No newline at end of file + diff --git a/common/lib/capa/capa/templates/choicetext.html b/common/lib/capa/capa/templates/choicetext.html index f07fa22665..448205c2aa 100644 --- a/common/lib/capa/capa/templates/choicetext.html +++ b/common/lib/capa/capa/templates/choicetext.html @@ -20,8 +20,6 @@ correctness = 'correct' elif status == 'incorrect': correctness = 'incorrect' - elif status == 'partially-correct': - correctness = 'partially-correct' else: correctness = None %> diff --git a/common/lib/capa/capa/templates/crystallography.html b/common/lib/capa/capa/templates/crystallography.html index 0442fb0fa6..3255046309 100644 --- a/common/lib/capa/capa/templates/crystallography.html +++ b/common/lib/capa/capa/templates/crystallography.html @@ -9,7 +9,7 @@
- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif @@ -25,7 +25,7 @@ ${msg|n} % endif - % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif diff --git a/common/lib/capa/capa/templates/designprotein2dinput.html b/common/lib/capa/capa/templates/designprotein2dinput.html index bb3490f5af..066d796db3 100644 --- a/common/lib/capa/capa/templates/designprotein2dinput.html +++ b/common/lib/capa/capa/templates/designprotein2dinput.html @@ -2,7 +2,7 @@
- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif @@ -15,7 +15,7 @@

- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif diff --git a/common/lib/capa/capa/templates/drag_and_drop_input.html b/common/lib/capa/capa/templates/drag_and_drop_input.html index d9bb8da90f..29eb629d93 100644 --- a/common/lib/capa/capa/templates/drag_and_drop_input.html +++ b/common/lib/capa/capa/templates/drag_and_drop_input.html @@ -8,7 +8,7 @@
- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif @@ -26,7 +26,7 @@ ${msg|n} % endif - % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif
diff --git a/common/lib/capa/capa/templates/editageneinput.html b/common/lib/capa/capa/templates/editageneinput.html index 11c947a955..4ee3cc5d7e 100644 --- a/common/lib/capa/capa/templates/editageneinput.html +++ b/common/lib/capa/capa/templates/editageneinput.html @@ -2,7 +2,7 @@
- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif @@ -16,7 +16,7 @@

- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif diff --git a/common/lib/capa/capa/templates/editamolecule.html b/common/lib/capa/capa/templates/editamolecule.html index c51101dc9d..e5131b21d9 100644 --- a/common/lib/capa/capa/templates/editamolecule.html +++ b/common/lib/capa/capa/templates/editamolecule.html @@ -1,7 +1,7 @@
- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif @@ -23,7 +23,7 @@ - % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif
diff --git a/common/lib/capa/capa/templates/jsinput.html b/common/lib/capa/capa/templates/jsinput.html index 9a9b03742d..b798d5800a 100644 --- a/common/lib/capa/capa/templates/jsinput.html +++ b/common/lib/capa/capa/templates/jsinput.html @@ -20,7 +20,7 @@
- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif @@ -47,7 +47,7 @@ - % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif diff --git a/common/lib/capa/capa/templates/textline.html b/common/lib/capa/capa/templates/textline.html index ec172c37d5..b37fb0d67e 100644 --- a/common/lib/capa/capa/templates/textline.html +++ b/common/lib/capa/capa/templates/textline.html @@ -7,7 +7,7 @@
% endif - % if status in ('unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete'): + % if status in ('unsubmitted', 'correct', 'incorrect', 'incomplete'):
% endif % if hidden: @@ -50,7 +50,7 @@ % endif -% if status in ('unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete'): +% if status in ('unsubmitted', 'correct', 'incorrect', 'incomplete'):
% endif diff --git a/common/lib/capa/capa/templates/vsepr_input.html b/common/lib/capa/capa/templates/vsepr_input.html index 49812e3fb2..72ad94654e 100644 --- a/common/lib/capa/capa/templates/vsepr_input.html +++ b/common/lib/capa/capa/templates/vsepr_input.html @@ -11,7 +11,7 @@
- % if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: + % if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif @@ -28,7 +28,7 @@ % if msg: ${msg|n} % endif -% if status in ['unsubmitted', 'correct', 'incorrect', 'partially-correct', 'incomplete']: +% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif diff --git a/common/lib/capa/capa/tests/response_xml_factory.py b/common/lib/capa/capa/tests/response_xml_factory.py index 619d7c2a02..a1b8f53554 100644 --- a/common/lib/capa/capa/tests/response_xml_factory.py +++ b/common/lib/capa/capa/tests/response_xml_factory.py @@ -49,9 +49,6 @@ class ResponseXMLFactory(object): *num_inputs*: The number of input elements to create [DEFAULT: 1] - *credit_type*: String of comma-separated words specifying the - partial credit grading scheme. - Returns a string representation of the XML tree. """ @@ -61,7 +58,6 @@ class ResponseXMLFactory(object): script = kwargs.get('script', None) num_responses = kwargs.get('num_responses', 1) num_inputs = kwargs.get('num_inputs', 1) - credit_type = kwargs.get('credit_type', None) # The root is root = etree.Element("problem") @@ -79,11 +75,6 @@ class ResponseXMLFactory(object): # Add the response(s) for __ in range(int(num_responses)): response_element = self.create_response_element(**kwargs) - - # Set partial credit - if credit_type is not None: - response_element.set('partial_credit', str(credit_type)) - root.append(response_element) # Add input elements @@ -141,10 +132,6 @@ class ResponseXMLFactory(object): *choice_names": List of strings identifying the choices. If specified, you must ensure that len(choice_names) == len(choices) - - *points*: List of strings giving partial credit values (0-1) - for each choice. Interpreted as floats in problem. - If specified, ensure len(points) == len(choices) """ # Names of group elements group_element_names = { @@ -157,23 +144,15 @@ class ResponseXMLFactory(object): choices = kwargs.get('choices', [True]) choice_type = kwargs.get('choice_type', 'multiple') choice_names = kwargs.get('choice_names', [None] * len(choices)) - points = kwargs.get('points', [None] * len(choices)) # Create the , , or element assert choice_type in group_element_names group_element = etree.Element(group_element_names[choice_type]) # Create the elements - for (correct_val, name, pointval) in zip(choices, choice_names, points): + for (correct_val, name) in zip(choices, choice_names): choice_element = etree.SubElement(group_element, "choice") - if correct_val is True: - correctness = 'true' - elif correct_val is False: - correctness = 'false' - elif 'partial' in correct_val: - correctness = 'partial' - - choice_element.set('correct', correctness) + choice_element.set("correct", "true" if correct_val else "false") # Add a name identifying the choice, if one exists # For simplicity, we use the same string as both the @@ -182,10 +161,6 @@ class ResponseXMLFactory(object): choice_element.text = str(name) choice_element.set("name", str(name)) - # Add point values for partially-correct choices. - if pointval: - choice_element.set("point_value", str(pointval)) - return group_element @@ -201,22 +176,10 @@ class NumericalResponseXMLFactory(ResponseXMLFactory): *tolerance*: The tolerance within which a response is considered correct. Can be a decimal (e.g. "0.01") or percentage (e.g. "2%") - - *credit_type*: String of comma-separated words specifying the - partial credit grading scheme. - - *partial_range*: The multiplier for the tolerance that will - still provide partial credit in the "close" grading style - - *partial_answers*: A string of comma-separated alternate - answers that will receive partial credit in the "list" style """ answer = kwargs.get('answer', None) tolerance = kwargs.get('tolerance', None) - credit_type = kwargs.get('credit_type', None) - partial_range = kwargs.get('partial_range', None) - partial_answers = kwargs.get('partial_answers', None) response_element = etree.Element('numericalresponse') @@ -230,13 +193,6 @@ class NumericalResponseXMLFactory(ResponseXMLFactory): responseparam_element = etree.SubElement(response_element, 'responseparam') responseparam_element.set('type', 'tolerance') responseparam_element.set('default', str(tolerance)) - if partial_range is not None and 'close' in credit_type: - responseparam_element.set('partial_range', str(partial_range)) - - if partial_answers is not None and 'list' in credit_type: - # The line below throws a false positive pylint violation, so it's excepted. - responseparam_element = etree.SubElement(response_element, 'responseparam') # pylint: disable=E1101 - responseparam_element.set('partial_answers', partial_answers) return response_element @@ -673,25 +629,15 @@ class OptionResponseXMLFactory(ResponseXMLFactory): *options*: a list of possible options the user can choose from [REQUIRED] You must specify at least 2 options. - *correct_option*: a string with comma-separated correct choices [REQUIRED] - *partial_option*: a string with comma-separated partially-correct choices - *point_values*: a string with comma-separated values (0-1) that give the - partial credit values in the "points" grading scheme. - Must have one per partial option. - *credit_type*: String of comma-separated words specifying the - partial credit grading scheme. + *correct_option*: the correct choice from the list of options [REQUIRED] """ options_list = kwargs.get('options', None) correct_option = kwargs.get('correct_option', None) - partial_option = kwargs.get('partial_option', None) - point_values = kwargs.get('point_values', None) - credit_type = kwargs.get('credit_type', None) assert options_list and correct_option assert len(options_list) > 1 - for option in correct_option.split(','): - assert option.strip() in options_list + assert correct_option in options_list # Create the element optioninput_element = etree.Element("optioninput") @@ -705,15 +651,6 @@ class OptionResponseXMLFactory(ResponseXMLFactory): # Set the "correct" attribute optioninput_element.set('correct', str(correct_option)) - # If we have 'points'-style partial credit... - if 'points' in str(credit_type): - # Set the "partial" attribute - optioninput_element.set('partial', str(partial_option)) - - # Set the "point_values" attribute, if it's specified. - if point_values is not None: - optioninput_element.set('point_values', str(point_values)) - return optioninput_element diff --git a/common/lib/capa/capa/tests/test_correctmap.py b/common/lib/capa/capa/tests/test_correctmap.py index 168351aa9d..f1aad56c79 100644 --- a/common/lib/capa/capa/tests/test_correctmap.py +++ b/common/lib/capa/capa/tests/test_correctmap.py @@ -17,7 +17,7 @@ class CorrectMapTest(unittest.TestCase): self.cmap = CorrectMap() def test_set_input_properties(self): - # Set the correctmap properties for three inputs + # Set the correctmap properties for two inputs self.cmap.set( answer_id='1_2_1', correctness='correct', @@ -41,34 +41,15 @@ class CorrectMapTest(unittest.TestCase): queuestate=None ) - self.cmap.set( - answer_id='3_2_1', - correctness='partially-correct', - npoints=3, - msg=None, - hint=None, - hintmode=None, - queuestate=None - ) - # Assert that each input has the expected properties self.assertTrue(self.cmap.is_correct('1_2_1')) self.assertFalse(self.cmap.is_correct('2_2_1')) - self.assertTrue(self.cmap.is_correct('3_2_1')) - - self.assertTrue(self.cmap.is_partially_correct('3_2_1')) - self.assertFalse(self.cmap.is_partially_correct('2_2_1')) - - # Intentionally testing an item that's not in cmap. - self.assertFalse(self.cmap.is_partially_correct('9_2_1')) self.assertEqual(self.cmap.get_correctness('1_2_1'), 'correct') self.assertEqual(self.cmap.get_correctness('2_2_1'), 'incorrect') - self.assertEqual(self.cmap.get_correctness('3_2_1'), 'partially-correct') self.assertEqual(self.cmap.get_npoints('1_2_1'), 5) self.assertEqual(self.cmap.get_npoints('2_2_1'), 0) - self.assertEqual(self.cmap.get_npoints('3_2_1'), 3) self.assertEqual(self.cmap.get_msg('1_2_1'), 'Test message') self.assertEqual(self.cmap.get_msg('2_2_1'), None) @@ -102,8 +83,6 @@ class CorrectMapTest(unittest.TestCase): # 3) incorrect, 5 points # 4) incorrect, None points # 5) correct, 0 points - # 4) partially correct, 2.5 points - # 5) partially correct, None points self.cmap.set( answer_id='1_2_1', correctness='correct', @@ -134,30 +113,15 @@ class CorrectMapTest(unittest.TestCase): npoints=0 ) - self.cmap.set( - answer_id='6_2_1', - correctness='partially-correct', - npoints=2.5 - ) - - self.cmap.set( - answer_id='7_2_1', - correctness='partially-correct', - npoints=None - ) - # Assert that we get the expected points # If points assigned --> npoints # If no points assigned and correct --> 1 point - # If no points assigned and partially correct --> 1 point # If no points assigned and incorrect --> 0 points self.assertEqual(self.cmap.get_npoints('1_2_1'), 5.3) self.assertEqual(self.cmap.get_npoints('2_2_1'), 1) self.assertEqual(self.cmap.get_npoints('3_2_1'), 5) self.assertEqual(self.cmap.get_npoints('4_2_1'), 0) self.assertEqual(self.cmap.get_npoints('5_2_1'), 0) - self.assertEqual(self.cmap.get_npoints('6_2_1'), 2.5) - self.assertEqual(self.cmap.get_npoints('7_2_1'), 1) def test_set_overall_message(self): diff --git a/common/lib/capa/capa/tests/test_responsetypes.py b/common/lib/capa/capa/tests/test_responsetypes.py index 9ce056a687..751d6c08d6 100644 --- a/common/lib/capa/capa/tests/test_responsetypes.py +++ b/common/lib/capa/capa/tests/test_responsetypes.py @@ -82,23 +82,6 @@ class ResponseTest(unittest.TestCase): result = problem.grade_answers({'1_2_1': input_str}).get_correctness('1_2_1') self.assertEqual(result, 'incorrect') - def assert_multiple_partial(self, problem, correct_answers, incorrect_answers, partial_answers): - """ - Runs multiple asserts for varying correct, incorrect, - and partially correct answers, all passed as lists. - """ - for input_str in correct_answers: - result = problem.grade_answers({'1_2_1': input_str}).get_correctness('1_2_1') - self.assertEqual(result, 'correct') - - for input_str in incorrect_answers: - result = problem.grade_answers({'1_2_1': input_str}).get_correctness('1_2_1') - self.assertEqual(result, 'incorrect') - - for input_str in partial_answers: - result = problem.grade_answers({'1_2_1': input_str}).get_correctness('1_2_1') - self.assertEqual(result, 'partially-correct') - def _get_random_number_code(self): """Returns code to be used to generate a random result.""" return "str(random.randint(0, 1e9))" @@ -120,14 +103,6 @@ class MultiChoiceResponseTest(ResponseTest): self.assert_grade(problem, 'choice_1', 'correct') self.assert_grade(problem, 'choice_2', 'incorrect') - def test_partial_multiple_choice_grade(self): - problem = self.build_problem(choices=[False, True, 'partial'], credit_type='points') - - # Ensure that we get the expected grades - self.assert_grade(problem, 'choice_0', 'incorrect') - self.assert_grade(problem, 'choice_1', 'correct') - self.assert_grade(problem, 'choice_2', 'partially-correct') - def test_named_multiple_choice_grade(self): problem = self.build_problem(choices=[False, True, False], choice_names=["foil_1", "foil_2", "foil_3"]) @@ -137,38 +112,6 @@ class MultiChoiceResponseTest(ResponseTest): self.assert_grade(problem, 'choice_foil_2', 'correct') self.assert_grade(problem, 'choice_foil_3', 'incorrect') - def test_multiple_choice_valid_grading_schemes(self): - # Multiple Choice problems only allow one partial credit scheme. - # Change this test if that changes. - problem = self.build_problem(choices=[False, True, 'partial'], credit_type='points,points') - with self.assertRaises(LoncapaProblemError): - input_dict = {'1_2_1': 'choice_1'} - problem.grade_answers(input_dict) - - # 'bongo' is not a valid grading scheme. - problem = self.build_problem(choices=[False, True, 'partial'], credit_type='bongo') - with self.assertRaises(LoncapaProblemError): - input_dict = {'1_2_1': 'choice_1'} - problem.grade_answers(input_dict) - - def test_partial_points_multiple_choice_grade(self): - problem = self.build_problem( - choices=['partial', 'partial', 'partial'], - credit_type='points', - points=['1', '0.6', '0'] - ) - - # Ensure that we get the expected number of points - # Using assertAlmostEqual to avoid floating point issues - correct_map = problem.grade_answers({'1_2_1': 'choice_0'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 1) - - correct_map = problem.grade_answers({'1_2_1': 'choice_1'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0.6) - - correct_map = problem.grade_answers({'1_2_1': 'choice_2'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0) - class TrueFalseResponseTest(ResponseTest): xml_factory_class = TrueFalseResponseXMLFactory @@ -409,77 +352,6 @@ class OptionResponseTest(ResponseTest): # Options not in the list should be marked incorrect self.assert_grade(problem, "invalid_option", "incorrect") - def test_grade_multiple_correct(self): - problem = self.build_problem( - options=["first", "second", "third"], - correct_option="second,third" - ) - - # Assert that we get the expected grades - self.assert_grade(problem, "first", "incorrect") - self.assert_grade(problem, "second", "correct") - self.assert_grade(problem, "third", "correct") - - def test_grade_partial_credit(self): - # Testing the "points" style. - problem = self.build_problem( - options=["first", "second", "third"], - correct_option="second", - credit_type="points", - partial_option="third" - ) - - # Assert that we get the expected grades - self.assert_grade(problem, "first", "incorrect") - self.assert_grade(problem, "second", "correct") - self.assert_grade(problem, "third", "partially-correct") - - def test_grade_partial_credit_with_points(self): - # Testing the "points" style with specified point values. - problem = self.build_problem( - options=["first", "second", "third"], - correct_option="second", - credit_type="points", - partial_option="third", - point_values="0.3" - ) - - # Assert that we get the expected grades and scores - self.assert_grade(problem, "first", "incorrect") - correct_map = problem.grade_answers({'1_2_1': 'first'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0) - - self.assert_grade(problem, "second", "correct") - correct_map = problem.grade_answers({'1_2_1': 'second'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 1) - - self.assert_grade(problem, "third", "partially-correct") - correct_map = problem.grade_answers({'1_2_1': 'third'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0.3) - - def test_grade_partial_credit_valid_scheme(self): - # Only one type of partial credit currently allowed. - problem = self.build_problem( - options=["first", "second", "third"], - correct_option="second", - credit_type="points,points", - partial_option="third" - ) - with self.assertRaises(LoncapaProblemError): - input_dict = {'1_2_1': 'second'} - problem.grade_answers(input_dict) - - # 'bongo' is not a valid grading scheme. - problem = self.build_problem( - options=["first", "second", "third"], - correct_option="second", - credit_type="bongo", - partial_option="third" - ) - with self.assertRaises(LoncapaProblemError): - input_dict = {'1_2_1': 'second'} - problem.grade_answers(input_dict) - def test_quote_option(self): # Test that option response properly escapes quotes inside options strings problem = self.build_problem(options=["hasnot", "hasn't", "has'nt"], @@ -511,29 +383,6 @@ class OptionResponseTest(ResponseTest): self.assertEqual(correct_map.get_correctness('1_2_1'), 'correct') self.assertEqual(correct_map.get_property('1_2_1', 'answervariable'), '$a') - def test_variable_options_partial_credit(self): - """ - Test that if variable are given in option response then correct map must contain answervariable value. - This is the partial-credit version. - """ - script = textwrap.dedent("""\ - a = 1000 - b = a*2 - c = a*3 - """) - problem = self.build_problem( - options=['$a', '$b', '$c'], - correct_option='$a', - partial_option='$b', - script=script, - credit_type='points', - ) - - input_dict = {'1_2_1': '2000'} - correct_map = problem.grade_answers(input_dict) - self.assertEqual(correct_map.get_correctness('1_2_1'), 'partially-correct') - self.assertEqual(correct_map.get_property('1_2_1', 'answervariable'), '$b') - class FormulaResponseTest(ResponseTest): """ @@ -1262,112 +1111,6 @@ class ChoiceResponseTest(ResponseTest): # No choice 3 exists --> mark incorrect self.assert_grade(problem, 'choice_3', 'incorrect') - def test_checkbox_group_valid_grading_schemes(self): - # Checkbox-type problems only allow one partial credit scheme. - # Change this test if that changes. - problem = self.build_problem( - choice_type='checkbox', - choices=[False, False, True, True], - credit_type='edc,halves,bongo' - ) - with self.assertRaises(LoncapaProblemError): - input_dict = {'1_2_1': 'choice_1'} - problem.grade_answers(input_dict) - - # 'bongo' is not a valid grading scheme. - problem = self.build_problem( - choice_type='checkbox', - choices=[False, False, True, True], - credit_type='bongo' - ) - with self.assertRaises(LoncapaProblemError): - input_dict = {'1_2_1': 'choice_1'} - problem.grade_answers(input_dict) - - def test_checkbox_group_partial_credit_grade(self): - # First: Every Decision Counts grading style - problem = self.build_problem( - choice_type='checkbox', - choices=[False, False, True, True], - credit_type='edc' - ) - - # Check that we get the expected results - # (correct if and only if BOTH correct choices chosen) - # (partially correct if at least one choice is right) - # (incorrect if totally wrong) - self.assert_grade(problem, ['choice_0', 'choice_1'], 'incorrect') - self.assert_grade(problem, ['choice_2', 'choice_3'], 'correct') - self.assert_grade(problem, 'choice_0', 'partially-correct') - self.assert_grade(problem, 'choice_2', 'partially-correct') - self.assert_grade(problem, ['choice_0', 'choice_1', 'choice_2', 'choice_3'], 'partially-correct') - - # Second: Halves grading style - problem = self.build_problem( - choice_type='checkbox', - choices=[False, False, True, True], - credit_type='halves' - ) - - # Check that we get the expected results - # (correct if and only if BOTH correct choices chosen) - # (partially correct on one error) - # (incorrect for more errors, at least with this # of choices.) - self.assert_grade(problem, ['choice_0', 'choice_1'], 'incorrect') - self.assert_grade(problem, ['choice_2', 'choice_3'], 'correct') - self.assert_grade(problem, 'choice_2', 'partially-correct') - self.assert_grade(problem, ['choice_1', 'choice_2', 'choice_3'], 'partially-correct') - self.assert_grade(problem, ['choice_0', 'choice_1', 'choice_2', 'choice_3'], 'incorrect') - - # Third: Halves grading style with more options - problem = self.build_problem( - choice_type='checkbox', - choices=[False, False, True, True, False], - credit_type='halves' - ) - - # Check that we get the expected results - # (2 errors allowed with 5+ choices) - self.assert_grade(problem, ['choice_0', 'choice_1', 'choice_4'], 'incorrect') - self.assert_grade(problem, ['choice_2', 'choice_3'], 'correct') - self.assert_grade(problem, 'choice_2', 'partially-correct') - self.assert_grade(problem, ['choice_1', 'choice_2', 'choice_3'], 'partially-correct') - self.assert_grade(problem, ['choice_0', 'choice_1', 'choice_2', 'choice_3'], 'partially-correct') - self.assert_grade(problem, ['choice_0', 'choice_1', 'choice_2', 'choice_3', 'choice_4'], 'incorrect') - - def test_checkbox_group_partial_points_grade(self): - # Ensure that we get the expected number of points - # Using assertAlmostEqual to avoid floating point issues - # First: Every Decision Counts grading style - problem = self.build_problem( - choice_type='checkbox', - choices=[False, False, True, True], - credit_type='edc' - ) - - correct_map = problem.grade_answers({'1_2_1': 'choice_2'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0.75) - - # Second: Halves grading style - problem = self.build_problem( - choice_type='checkbox', - choices=[False, False, True, True], - credit_type='halves' - ) - - correct_map = problem.grade_answers({'1_2_1': 'choice_2'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0.5) - - # Third: Halves grading style with more options - problem = self.build_problem( - choice_type='checkbox', - choices=[False, False, True, True, False], - credit_type='halves' - ) - - correct_map = problem.grade_answers({'1_2_1': 'choice_2,choice4'}) - self.assertAlmostEqual(correct_map.get_npoints('1_2_1'), 0.25) - def test_grade_with_no_checkbox_selected(self): """ Test that answer marked as incorrect if no checkbox selected. @@ -1428,7 +1171,7 @@ class NumericalResponseTest(ResponseTest): # For simple things its not worth the effort. def test_grade_range_tolerance(self): problem_setup = [ - # [given_answer, [list of correct responses], [list of incorrect responses]] + # [given_asnwer, [list of correct responses], [list of incorrect responses]] ['[5, 7)', ['5', '6', '6.999'], ['4.999', '7']], ['[1.6e-5, 1.9e24)', ['0.000016', '1.6*10^-5', '1.59e24'], ['1.59e-5', '1.9e24', '1.9*10^24']], ['[0, 1.6e-5]', ['1.6*10^-5'], ["2"]], @@ -1438,41 +1181,6 @@ class NumericalResponseTest(ResponseTest): problem = self.build_problem(answer=given_answer) self.assert_multiple_grade(problem, correct_responses, incorrect_responses) - def test_grade_range_tolerance_partial_credit(self): - problem_setup = [ - # [given_answer, - # [list of correct responses], - # [list of incorrect responses], - # [list of partially correct responses]] - [ - '[5, 7)', - ['5', '6', '6.999'], - ['0', '100'], - ['4', '8'] - ], - [ - '[1.6e-5, 1.9e24)', - ['0.000016', '1.6*10^-5', '1.59e24'], - ['-1e26', '1.9e26', '1.9*10^26'], - ['0', '2e24'] - ], - [ - '[0, 1.6e-5]', - ['1.6*10^-5'], - ['2'], - ['1.9e-5', '-1e-6'] - ], - [ - '(1.6e-5, 10]', - ['2'], - ['-20', '30'], - ['-1', '12'] - ], - ] - for given_answer, correct_responses, incorrect_responses, partial_responses in problem_setup: - problem = self.build_problem(answer=given_answer, credit_type='close') - self.assert_multiple_partial(problem, correct_responses, incorrect_responses, partial_responses) - def test_grade_range_tolerance_exceptions(self): # no complex number in range tolerance staff answer problem = self.build_problem(answer='[1j, 5]') @@ -1510,61 +1218,6 @@ class NumericalResponseTest(ResponseTest): incorrect_responses = ["", "3.9", "4.1", "0"] self.assert_multiple_grade(problem, correct_responses, incorrect_responses) - def test_grade_partial(self): - # First: "list"-style grading scheme. - problem = self.build_problem( - answer=4, - credit_type='list', - partial_answers='2,8,-4' - ) - correct_responses = ["4", "4.0"] - incorrect_responses = ["1", "3", "4.1", "0", "-2"] - partial_responses = ["2", "2.0", "-4", "-4.0", "8", "8.0"] - self.assert_multiple_partial(problem, correct_responses, incorrect_responses, partial_responses) - - # Second: "close"-style grading scheme. Default range is twice tolerance. - problem = self.build_problem( - answer=4, - tolerance=0.2, - credit_type='close' - ) - correct_responses = ["4", "4.1", "3.9"] - incorrect_responses = ["1", "3", "4.5", "0", "-2"] - partial_responses = ["4.3", "3.7"] - self.assert_multiple_partial(problem, correct_responses, incorrect_responses, partial_responses) - - # Third: "close"-style grading scheme with partial_range set. - problem = self.build_problem( - answer=4, - tolerance=0.2, - partial_range=3, - credit_type='close' - ) - correct_responses = ["4", "4.1"] - incorrect_responses = ["1", "3", "0", "-2"] - partial_responses = ["4.5", "3.5"] - self.assert_multiple_partial(problem, correct_responses, incorrect_responses, partial_responses) - - # Fourth: both "list"- and "close"-style grading schemes at once. - problem = self.build_problem( - answer=4, - tolerance=0.2, - partial_range=3, - credit_type='close,list', - partial_answers='2,8,-4' - ) - correct_responses = ["4", "4.0"] - incorrect_responses = ["1", "3", "0", "-2"] - partial_responses = ["2", "2.1", "1.5", "8", "7.5", "8.1", "-4", "-4.15", "-3.5", "4.5", "3.5"] - self.assert_multiple_partial(problem, correct_responses, incorrect_responses, partial_responses) - - def test_numerical_valid_grading_schemes(self): - # 'bongo' is not a valid grading scheme. - problem = self.build_problem(answer=4, tolerance=0.1, credit_type='bongo') - input_dict = {'1_2_1': '4'} - with self.assertRaises(LoncapaProblemError): - problem.grade_answers(input_dict) - def test_grade_decimal_tolerance(self): problem = self.build_problem(answer=4, tolerance=0.1) correct_responses = ["4.0", "4.00", "4.09", "3.91"] @@ -1791,18 +1444,11 @@ class CustomResponseTest(ResponseTest): # or an ordered list of answers (if there are multiple inputs) # # The function should return a dict of the form - # { 'ok': BOOL or STRING, 'msg': STRING } (no 'grade_decimal' key to test that it's optional) + # { 'ok': BOOL, 'msg': STRING } (no 'grade_decimal' key to test that it's optional) # script = textwrap.dedent(""" def check_func(expect, answer_given): - partial_credit = '21' - if answer_given == expect: - retval = True - elif answer_given == partial_credit: - retval = 'partial' - else: - retval = False - return {'ok': retval, 'msg': 'Message text'} + return {'ok': answer_given == expect, 'msg': 'Message text'} """) problem = self.build_problem(script=script, cfn="check_func", expect="42") @@ -1819,18 +1465,6 @@ class CustomResponseTest(ResponseTest): self.assertEqual(msg, "Message text") self.assertEqual(npoints, 1) - # Partially Credit answer - input_dict = {'1_2_1': '21'} - correct_map = problem.grade_answers(input_dict) - - correctness = correct_map.get_correctness('1_2_1') - msg = correct_map.get_msg('1_2_1') - npoints = correct_map.get_npoints('1_2_1') - - self.assertEqual(correctness, 'partially-correct') - self.assertEqual(msg, "Message text") - self.assertTrue(0 <= npoints <= 1) - # Incorrect answer input_dict = {'1_2_1': '0'} correct_map = problem.grade_answers(input_dict) @@ -1852,24 +1486,14 @@ class CustomResponseTest(ResponseTest): # or an ordered list of answers (if there are multiple inputs) # # The function should return a dict of the form - # { 'ok': BOOL or STRING, 'msg': STRING, 'grade_decimal': FLOAT } + # { 'ok': BOOL, 'msg': STRING, 'grade_decimal': FLOAT } # script = textwrap.dedent(""" def check_func(expect, answer_given): - partial_credit = '21' - if answer_given == expect: - retval = True - score = 0.9 - elif answer_given == partial_credit: - retval = 'partial' - score = 0.5 - else: - retval = False - score = 0.1 return { - 'ok': retval, + 'ok': answer_given == expect, 'msg': 'Message text', - 'grade_decimal': score, + 'grade_decimal': 0.9 if answer_given == expect else 0.1, } """) @@ -1887,28 +1511,16 @@ class CustomResponseTest(ResponseTest): self.assertEqual(correct_map.get_npoints('1_2_1'), 0.1) self.assertEqual(correct_map.get_correctness('1_2_1'), 'incorrect') - # Partially Correct answer - input_dict = {'1_2_1': '21'} - correct_map = problem.grade_answers(input_dict) - self.assertEqual(correct_map.get_npoints('1_2_1'), 0.5) - self.assertEqual(correct_map.get_correctness('1_2_1'), 'partially-correct') - def test_function_code_multiple_input_no_msg(self): # Check functions also have the option of returning - # a single boolean or string value + # a single boolean value # If true, mark all the inputs correct - # If one is true but not the other, mark all partially correct # If false, mark all the inputs incorrect script = textwrap.dedent(""" def check_func(expect, answer_given): - if answer_given[0] == expect and answer_given[1] == expect: - retval = True - elif answer_given[0] == expect or answer_given[1] == expect: - retval = 'partial' - else: - retval = False - return retval + return (answer_given[0] == expect and + answer_given[1] == expect) """) problem = self.build_problem(script=script, cfn="check_func", @@ -1924,22 +1536,10 @@ class CustomResponseTest(ResponseTest): correctness = correct_map.get_correctness('1_2_2') self.assertEqual(correctness, 'correct') - # One answer incorrect -- expect both inputs marked partially correct + # One answer incorrect -- expect both inputs marked incorrect input_dict = {'1_2_1': '0', '1_2_2': '42'} correct_map = problem.grade_answers(input_dict) - correctness = correct_map.get_correctness('1_2_1') - self.assertEqual(correctness, 'partially-correct') - self.assertTrue(0 <= correct_map.get_npoints('1_2_1') <= 1) - - correctness = correct_map.get_correctness('1_2_2') - self.assertEqual(correctness, 'partially-correct') - self.assertTrue(0 <= correct_map.get_npoints('1_2_2') <= 1) - - # Both answers incorrect -- expect both inputs marked incorrect - input_dict = {'1_2_1': '0', '1_2_2': '0'} - correct_map = problem.grade_answers(input_dict) - correctness = correct_map.get_correctness('1_2_1') self.assertEqual(correctness, 'incorrect') @@ -1952,8 +1552,7 @@ class CustomResponseTest(ResponseTest): # the check function can return a dict of the form: # # {'overall_message': STRING, - # 'input_list': [{'ok': BOOL or STRING, 'msg': STRING}, ...] } - # (no grade_decimal to test it's optional) + # 'input_list': [{'ok': BOOL, 'msg': STRING}, ...] } (no grade_decimal to test it's optional) # # 'overall_message' is displayed at the end of the response # @@ -1964,20 +1563,18 @@ class CustomResponseTest(ResponseTest): check1 = (int(answer_given[0]) == 1) check2 = (int(answer_given[1]) == 2) check3 = (int(answer_given[2]) == 3) - check4 = 'partial' if answer_given[3] == 'four' else False return {'overall_message': 'Overall message', 'input_list': [ {'ok': check1, 'msg': 'Feedback 1'}, {'ok': check2, 'msg': 'Feedback 2'}, - {'ok': check3, 'msg': 'Feedback 3'}, - {'ok': check4, 'msg': 'Feedback 4'} ] } + {'ok': check3, 'msg': 'Feedback 3'} ] } """) problem = self.build_problem(script=script, - cfn="check_func", num_inputs=4) + cfn="check_func", num_inputs=3) # Grade the inputs (one input incorrect) - input_dict = {'1_2_1': '-999', '1_2_2': '2', '1_2_3': '3', '1_2_4': 'four'} + input_dict = {'1_2_1': '-999', '1_2_2': '2', '1_2_3': '3'} correct_map = problem.grade_answers(input_dict) # Expect that we receive the overall message (for the whole response) @@ -1987,19 +1584,16 @@ class CustomResponseTest(ResponseTest): self.assertEqual(correct_map.get_correctness('1_2_1'), 'incorrect') self.assertEqual(correct_map.get_correctness('1_2_2'), 'correct') self.assertEqual(correct_map.get_correctness('1_2_3'), 'correct') - self.assertEqual(correct_map.get_correctness('1_2_4'), 'partially-correct') # Expect that the inputs were given correct npoints self.assertEqual(correct_map.get_npoints('1_2_1'), 0) self.assertEqual(correct_map.get_npoints('1_2_2'), 1) self.assertEqual(correct_map.get_npoints('1_2_3'), 1) - self.assertTrue(0 <= correct_map.get_npoints('1_2_4') <= 1) # Expect that we received messages for each individual input self.assertEqual(correct_map.get_msg('1_2_1'), 'Feedback 1') self.assertEqual(correct_map.get_msg('1_2_2'), 'Feedback 2') self.assertEqual(correct_map.get_msg('1_2_3'), 'Feedback 3') - self.assertEqual(correct_map.get_msg('1_2_4'), 'Feedback 4') def test_function_code_multiple_inputs_decimal_score(self): @@ -2007,8 +1601,7 @@ class CustomResponseTest(ResponseTest): # the check function can return a dict of the form: # # {'overall_message': STRING, - # 'input_list': [{'ok': BOOL or STRING, - # 'msg': STRING, 'grade_decimal': FLOAT}, ...] } + # 'input_list': [{'ok': BOOL, 'msg': STRING, 'grade_decimal': FLOAT}, ...] } # # # 'input_list' contains dictionaries representing the correctness # and message for each input. @@ -2017,51 +1610,39 @@ class CustomResponseTest(ResponseTest): check1 = (int(answer_given[0]) == 1) check2 = (int(answer_given[1]) == 2) check3 = (int(answer_given[2]) == 3) - check4 = 'partial' if answer_given[3] == 'four' else False score1 = 0.9 if check1 else 0.1 score2 = 0.9 if check2 else 0.1 score3 = 0.9 if check3 else 0.1 - score4 = 0.7 if check4 == 'partial' else 0.1 return { 'input_list': [ {'ok': check1, 'grade_decimal': score1, 'msg': 'Feedback 1'}, {'ok': check2, 'grade_decimal': score2, 'msg': 'Feedback 2'}, {'ok': check3, 'grade_decimal': score3, 'msg': 'Feedback 3'}, - {'ok': check4, 'grade_decimal': score4, 'msg': 'Feedback 4'}, ] } """) - problem = self.build_problem(script=script, cfn="check_func", num_inputs=4) + problem = self.build_problem(script=script, cfn="check_func", num_inputs=3) # Grade the inputs (one input incorrect) - input_dict = {'1_2_1': '-999', '1_2_2': '2', '1_2_3': '3', '1_2_4': 'four'} + input_dict = {'1_2_1': '-999', '1_2_2': '2', '1_2_3': '3'} correct_map = problem.grade_answers(input_dict) # Expect that the inputs were graded individually self.assertEqual(correct_map.get_correctness('1_2_1'), 'incorrect') self.assertEqual(correct_map.get_correctness('1_2_2'), 'correct') self.assertEqual(correct_map.get_correctness('1_2_3'), 'correct') - self.assertEqual(correct_map.get_correctness('1_2_4'), 'partially-correct') # Expect that the inputs were given correct npoints self.assertEqual(correct_map.get_npoints('1_2_1'), 0.1) self.assertEqual(correct_map.get_npoints('1_2_2'), 0.9) self.assertEqual(correct_map.get_npoints('1_2_3'), 0.9) - self.assertEqual(correct_map.get_npoints('1_2_4'), 0.7) def test_function_code_with_extra_args(self): script = textwrap.dedent("""\ def check_func(expect, answer_given, options, dynamath): assert options == "xyzzy", "Options was %r" % options - partial_credit = '21' - if answer_given == expect: - retval = True - elif answer_given == partial_credit: - retval = 'partial' - else: - retval = False - return {'ok': retval, 'msg': 'Message text'} + return {'ok': answer_given == expect, 'msg': 'Message text'} """) problem = self.build_problem(script=script, cfn="check_func", expect="42", options="xyzzy", cfn_extra_args="options dynamath") @@ -2076,16 +1657,6 @@ class CustomResponseTest(ResponseTest): self.assertEqual(correctness, 'correct') self.assertEqual(msg, "Message text") - # Partially Correct answer - input_dict = {'1_2_1': '21'} - correct_map = problem.grade_answers(input_dict) - - correctness = correct_map.get_correctness('1_2_1') - msg = correct_map.get_msg('1_2_1') - - self.assertEqual(correctness, 'partially-correct') - self.assertEqual(msg, "Message text") - # Incorrect answer input_dict = {'1_2_1': '0'} correct_map = problem.grade_answers(input_dict) @@ -2112,12 +1683,8 @@ class CustomResponseTest(ResponseTest): check1 = (int(answer_given[0]) == 1) check2 = (int(answer_given[1]) == 2) check3 = (int(answer_given[2]) == 3) - if (int(answer_given[0]) == -1) and check2 and check3: - return {'ok': 'partial', - 'msg': 'Message text'} - else: - return {'ok': (check1 and check2 and check3), - 'msg': 'Message text'} + return {'ok': (check1 and check2 and check3), + 'msg': 'Message text'} """) problem = self.build_problem(script=script, @@ -2132,15 +1699,6 @@ class CustomResponseTest(ResponseTest): self.assertEqual(correct_map.get_correctness('1_2_2'), 'incorrect') self.assertEqual(correct_map.get_correctness('1_2_3'), 'incorrect') - # Grade the inputs (one input partially correct) - input_dict = {'1_2_1': '-1', '1_2_2': '2', '1_2_3': '3'} - correct_map = problem.grade_answers(input_dict) - - # Everything marked partially correct - self.assertEqual(correct_map.get_correctness('1_2_1'), 'partially-correct') - self.assertEqual(correct_map.get_correctness('1_2_2'), 'partially-correct') - self.assertEqual(correct_map.get_correctness('1_2_3'), 'partially-correct') - # Grade the inputs (everything correct) input_dict = {'1_2_1': '1', '1_2_2': '2', '1_2_3': '3'} correct_map = problem.grade_answers(input_dict) diff --git a/common/lib/xmodule/xmodule/css/capa/display.scss b/common/lib/xmodule/xmodule/css/capa/display.scss index e769d48df6..cf441cb462 100644 --- a/common/lib/xmodule/xmodule/css/capa/display.scss +++ b/common/lib/xmodule/xmodule/css/capa/display.scss @@ -24,7 +24,6 @@ $annotation-yellow: rgba(255,255,10,0.3); $color-copy-tip: rgb(100,100,100); $correct: $green-d1; -$partiallycorrect: $green-d1; $incorrect: $red; // +Extends - Capa @@ -76,11 +75,6 @@ h2 { color: $correct; } -.feedback-hint-partially-correct { - margin-top: ($baseline/2); - color: $partiallycorrect; -} - .feedback-hint-incorrect { margin-top: ($baseline/2); color: $incorrect; @@ -180,16 +174,6 @@ div.problem { } } - &.choicegroup_partially-correct { - @include status-icon($partiallycorrect, "\f00c"); - border: 2px solid $partiallycorrect; - - // keep green for correct answers on hover. - &:hover { - border-color: $partiallycorrect; - } - } - &.choicegroup_incorrect { @include status-icon($incorrect, "\f00d"); border: 2px solid $incorrect; @@ -243,11 +227,6 @@ div.problem { @include status-icon($correct, "\f00c"); } - // CASE: partially correct answer - &.partially-correct { - @include status-icon($partiallycorrect, "\f00c"); - } - // CASE: incorrect answer &.incorrect { @include status-icon($incorrect, "\f00d"); @@ -359,19 +338,6 @@ div.problem { } } - &.partially-correct, &.ui-icon-check { - p.status { - display: inline-block; - width: 25px; - height: 20px; - background: url('../images/partially-correct-icon.png') center center no-repeat; - } - - input { - border-color: $partiallycorrect; - } - } - &.processing { p.status { display: inline-block; @@ -747,7 +713,7 @@ div.problem { height: 46px; } - > .incorrect, .partially-correct, .correct, .unanswered { + > .incorrect, .correct, .unanswered { .status { display: inline-block; @@ -768,18 +734,6 @@ div.problem { } } - // CASE: partially correct answer - > .partially-correct { - - input { - border: 2px solid $partiallycorrect; - } - - .status { - @include status-icon($partiallycorrect, "\f00c"); - } - } - // CASE: correct answer > .correct { @@ -821,7 +775,7 @@ div.problem { .indicator-container { display: inline-block; - .status.correct:after, .status.partially-correct:after, .status.incorrect:after, .status.unanswered:after { + .status.correct:after, .status.incorrect:after, .status.unanswered:after { @include margin-left(0); } } @@ -987,20 +941,6 @@ div.problem { } } - .detailed-targeted-feedback-partially-correct { - > p:first-child { - @extend %t-strong; - color: $partiallycorrect; - text-transform: uppercase; - font-style: normal; - font-size: 0.9em; - } - - p:last-child { - margin-bottom: 0; - } - } - .detailed-targeted-feedback-correct { > p:first-child { @extend %t-strong; @@ -1195,14 +1135,6 @@ div.problem { } } - .result-partially-correct { - background: url('../images/partially-correct-icon.png') left 20px no-repeat; - - .result-actual-output { - color: #090; - } - } - .result-incorrect { background: url('../images/incorrect-icon.png') left 20px no-repeat; @@ -1408,14 +1340,6 @@ div.problem { } } - label.choicetextgroup_partially-correct, section.choicetextgroup_partially-correct { - @extend label.choicegroup_partially-correct; - - input[type="text"] { - border-color: $partiallycorrect; - } - } - label.choicetextgroup_incorrect, section.choicetextgroup_incorrect { @extend label.choicegroup_incorrect; } diff --git a/common/static/images/partially-correct-icon.png b/common/static/images/partially-correct-icon.png index 5ae7de2a141e0daacf13f58fa208413431a4941f..9ac0fd32f7a5c579fcb2e419486fc4443cccf6a5 100644 GIT binary patch literal 1230 zcmeAS@N?(olHy`uVBq!ia0vp^l0YoN!3HEN%BSrG36!`-lmzFem6RtIr7}3Cer{{GxPyLrY6bkQqisxQ#zd*q`*i1pgH!(Rg4gi&u1T;f0Gc(1?#KqXy$=Te++|t<8(9qS$$;r^o(bC!6)x^Nm zz|qkVrq?AuximL5uLPzy1)CgZF_RMP;xs%m}Z_d)MnB)FXziNZU38x!L2Pa;4mzg|8==`B0Ggmx~n!x(+Qr+sS z6`_^3DtG(O?>4_bW7RaBx(yaGDU9~o_BU)a{WQZ;x@W9NY@EO4zR?U;1BT5R=k8i= z^4YS1fo)y=t~xVU%cq8_(qd%Kl0iD-yh o^I#K)h`XZ0o`yS5@z)NYooL=<7UTvxa5(s=%Rv* element: diff --git a/common/test/acceptance/tests/lms/test_lms_problems.py b/common/test/acceptance/tests/lms/test_lms_problems.py index ba14a10208..b38d7905ef 100644 --- a/common/test/acceptance/tests/lms/test_lms_problems.py +++ b/common/test/acceptance/tests/lms/test_lms_problems.py @@ -213,35 +213,3 @@ class ProblemWithMathjax(ProblemsTest): self.assertIn("Hint (2 of 2): mathjax should work2", problem_page.hint_text) self.assertTrue(problem_page.mathjax_rendered_in_hint, "MathJax did not rendered in problem hint") - - -class ProblemPartialCredit(ProblemsTest): - """ - Makes sure that the partial credit is appearing properly. - """ - def get_problem(self): - """ - Create a problem with partial credit. - """ - xml = dedent(""" - -

The answer is 1. Partial credit for -1.

- - - - - -
- """) - return XBlockFixtureDesc('problem', 'PARTIAL CREDIT TEST PROBLEM', data=xml) - - def test_partial_credit(self): - """ - Test that we can see the partial credit value and feedback. - """ - self.courseware_page.visit() - problem_page = ProblemPage(self.browser) - self.assertEqual(problem_page.problem_name, 'PARTIAL CREDIT TEST PROBLEM') - problem_page.fill_answer_numerical('-1') - problem_page.click_check() - self.assertTrue(problem_page.simpleprob_is_partially_correct())