Commit bb867ba3 by Diana Huang

Use the new CombinedOpenEndedRubric class for everything

related to rubric rendering. Remove the old input type.
parent ae65bb03
...@@ -735,142 +735,3 @@ class ChemicalEquationInput(InputTypeBase): ...@@ -735,142 +735,3 @@ class ChemicalEquationInput(InputTypeBase):
registry.register(ChemicalEquationInput) registry.register(ChemicalEquationInput)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
class RubricInput(InputTypeBase):
"""
This is the logic for parsing and displaying a rubric of type
"""
template = "rubricinput.html"
tags = ['rubric']
# pulled out for testing
submitted_msg = ("Feedback not yet available. Reload to check again. "
"Once the problem is graded, this message will be "
"replaced with the grader's feedback.")
has_score = False
@classmethod
def get_attributes(cls):
"""
Convert options to a convenient format.
"""
return [
Attribute('height', None),
Attribute('width', None)]
def _extra_context(self):
"""
Add in the various bits and pieces that we need
"""
return {'categories': self.categories,
'view_only': False,
'has_score': self.has_score}
def setup(self):
# set the categories
self.categories = self.extract_categories(self.xml)
def extract_categories(self, element):
'''
Contstruct a list of categories such that the structure looks like:
[ { category: "Category 1 Name",
options: [{text: "Option 1 Name", points: 0}, {text:"Option 2 Name", points: 5}]
},
{ category: "Category 2 Name",
options: [{text: "Option 1 Name", points: 0},
{text: "Option 2 Name", points: 1},
{text: "Option 3 Name", points: 2]}]
'''
categories = []
for category in element:
if category.tag != 'category':
raise Exception("[capa.inputtypes.extract_categories] Expected a <category> tag: got {0} instead".format(category.tag))
else:
categories.append(self.extract_category(category))
return categories
def extract_category(self, category):
'''
construct an individual category
{category: "Category 1 Name",
options: [{text: "Option 1 text", points: 1},
{text: "Option 2 text", points: 2}]}
all sorting and auto-point generation occurs in this function
'''
descriptionxml = category[0]
optionsxml = category[1:]
scorexml = category[1]
score = None
if scorexml.tag == 'score':
score_text = scorexml.text
optionsxml = category[2:]
score = int(score_text)
self.has_score = True
# if we are missing the score tag and we are expecting one
elif self.has_score:
raise Exception("[inputtypes.extract_category] Category {0} is missing a score".format(descriptionxml.text))
# parse description
if descriptionxml.tag != 'description':
raise Exception("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag))
description = descriptionxml.text
cur_points = 0
options = []
autonumbering = True
# parse options
for option in optionsxml:
if option.tag != 'option':
raise Exception("[extract_category]: expected option tag, got {0} instead".format(option.tag))
else:
pointstr = option.get("points")
if pointstr:
autonumbering = False
# try to parse this into an int
try:
points = int(pointstr)
except ValueError:
raise Exception("[extract_category]: expected points to have int, got {0} instead".format(pointstr))
elif autonumbering:
# use the generated one if we're in the right mode
points = cur_points
cur_points = cur_points + 1
else:
raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly dfined.")
selected = score == points
optiontext = option.text
options.append({'text': option.text, 'points': points, 'selected': selected})
# sort and check for duplicates
options = sorted(options, key=lambda option: option['points'])
RubricInput.validate_options(options)
return {'description': description, 'options': options}
@staticmethod
def validate_options(options):
'''
Validates a set of options. This can and should be extended to filter out other bad edge cases
'''
if len(options) == 0:
raise Exception("[extract_category]: no options associated with this category")
if len(options) == 1:
return
prev = options[0]['points']
for option in options[1:]:
if prev == option['points']:
raise Exception("[extract_category]: found duplicate point values between two different options")
else:
prev = option['points']
registry.register(RubricInput)
#-----------------------------------------------------------------------------
<form class="rubric-template" id="inputtype_${id}">
<h3>Rubric</h3>
% if view_only and has_score:
<p>This is the rubric that was used to grade your submission.The highlighted selection matches how the grader feels you performed in each category.</p>
% elif view_only:
<p>Use the below rubric to rate this submission.</p>
% else:
<p>Select the criteria you feel best represents this submission in each category.</p>
% endif
<table class="rubric">
% for i in range(len(categories)):
<% category = categories[i] %>
<tr>
<th>${category['description']}</th>
% for j in range(len(category['options'])):
<% option = category['options'][j] %>
<td>
% if view_only:
## if this is the selected rubric block, show it
% if option['selected']:
<div class="view-only selected-grade">
% else:
<div class="view-only">
% endif
${option['text']}
<div class="grade">[${option['points']} points]</div>
</div>
% else:
<input type="radio" class="score-selection" name="score-selection-${i}" id="score-${i}-${j}" value="${option['points']}"/>
<label for="score-${i}-${j}">${option['text']}</label>
% endif
</td>
% endfor
</tr>
% endfor
</table>
</form>
...@@ -6,18 +6,23 @@ log=logging.getLogger(__name__) ...@@ -6,18 +6,23 @@ log=logging.getLogger(__name__)
class CombinedOpenEndedRubric: class CombinedOpenEndedRubric:
@staticmethod def __init__ (self, view_only = False):
def render_rubric(rubric_xml): self.has_score = False
self.view_only = view_only
def render_rubric(self, rubric_xml):
try: try:
rubric_categories = CombinedOpenEndedRubric.extract_rubric_categories(rubric_xml) rubric_categories = self.extract_categories(rubric_xml)
html = render_to_string('open_ended_rubric.html', {'rubric_categories' : rubric_categories}) html = render_to_string('open_ended_rubric.html',
{'categories' : rubric_categories,
'has_score': self.has_score,
'view_only': self.view_only})
except: except:
log.exception("Could not parse the rubric.") log.exception("Could not parse the rubric.")
html = rubric_xml html = etree.tostring(rubric_xml, pretty_print=True)
return html return html
@staticmethod def extract_categories(self, element):
def extract_rubric_categories(element):
''' '''
Contstruct a list of categories such that the structure looks like: Contstruct a list of categories such that the structure looks like:
[ { category: "Category 1 Name", [ { category: "Category 1 Name",
...@@ -29,17 +34,19 @@ class CombinedOpenEndedRubric: ...@@ -29,17 +34,19 @@ class CombinedOpenEndedRubric:
{text: "Option 3 Name", points: 2]}] {text: "Option 3 Name", points: 2]}]
''' '''
element = etree.fromstring(element) if element.tag != 'rubric':
raise Exception("[extract_categories] Expected a <rubric> tag: got {0} instead".format(element.tag))
categorylist = list(element)
categories = [] categories = []
for category in element: for category in categorylist:
if category.tag != 'category': if category.tag != 'category':
raise Exception("[capa.inputtypes.extract_categories] Expected a <category> tag: got {0} instead".format(category.tag)) raise Exception("[extract_categories] Expected a <category> tag: got {0} instead".format(category.tag))
else: else:
categories.append(CombinedOpenEndedRubric.extract_category(category)) categories.append(self.extract_category(category))
return categories return categories
@staticmethod
def extract_category(category): def extract_category(self, category):
''' '''
construct an individual category construct an individual category
{category: "Category 1 Name", {category: "Category 1 Name",
...@@ -48,35 +55,26 @@ class CombinedOpenEndedRubric: ...@@ -48,35 +55,26 @@ class CombinedOpenEndedRubric:
all sorting and auto-point generation occurs in this function all sorting and auto-point generation occurs in this function
''' '''
has_score=False
descriptionxml = category[0] descriptionxml = category[0]
scorexml = category[1]
if scorexml.tag == "option":
optionsxml = category[1:] optionsxml = category[1:]
else: scorexml = category[1]
score = None
if scorexml.tag == 'score':
score_text = scorexml.text
optionsxml = category[2:] optionsxml = category[2:]
has_score=True score = int(score_text)
self.has_score = True
# if we are missing the score tag and we are expecting one
elif self.has_score:
raise Exception("[extract_category] Category {0} is missing a score".format(descriptionxml.text))
# parse description # parse description
if descriptionxml.tag != 'description': if descriptionxml.tag != 'description':
raise Exception("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag)) raise Exception("[extract_category]: expected description tag, got {0} instead".format(descriptionxml.tag))
if has_score:
if scorexml.tag != 'score':
raise Exception("[extract_category]: expected score tag, got {0} instead".format(scorexml.tag))
for option in optionsxml:
if option.tag != "option":
raise Exception("[extract_category]: expected option tag, got {0} instead".format(option.tag))
description = descriptionxml.text description = descriptionxml.text
if has_score:
score = int(scorexml.text)
else:
score = 0
cur_points = 0 cur_points = 0
options = [] options = []
autonumbering = True autonumbering = True
...@@ -99,18 +97,17 @@ class CombinedOpenEndedRubric: ...@@ -99,18 +97,17 @@ class CombinedOpenEndedRubric:
cur_points = cur_points + 1 cur_points = cur_points + 1
else: else:
raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly dfined.") raise Exception("[extract_category]: missing points attribute. Cannot continue to auto-create points values after a points value is explicitly dfined.")
selected = score == points
optiontext = option.text optiontext = option.text
selected = False options.append({'text': option.text, 'points': points, 'selected': selected})
if has_score:
if points == score:
selected = True
options.append({'text': option.text, 'points': points, 'selected' : selected})
# sort and check for duplicates # sort and check for duplicates
options = sorted(options, key=lambda option: option['points']) options = sorted(options, key=lambda option: option['points'])
CombinedOpenEndedRubric.validate_options(options) CombinedOpenEndedRubric.validate_options(options)
return {'description': description, 'options': options, 'score' : score, 'has_score' : has_score} return {'description': description, 'options': options}
@staticmethod @staticmethod
def validate_options(options): def validate_options(options):
......
...@@ -19,7 +19,7 @@ from xmodule.course_module import CourseDescriptor ...@@ -19,7 +19,7 @@ from xmodule.course_module import CourseDescriptor
from student.models import unique_id_for_user from student.models import unique_id_for_user
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from mitxmako.shortcuts import render_to_string from mitxmako.shortcuts import render_to_string
from capa import inputtypes from xmodule.combined_open_ended_rubric import CombinedOpenEndedRubric
from lxml import etree from lxml import etree
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -263,8 +263,9 @@ def _get_next(course_id, grader_id, location): ...@@ -263,8 +263,9 @@ def _get_next(course_id, grader_id, location):
response = staff_grading_service().get_next(course_id, location, grader_id) response = staff_grading_service().get_next(course_id, location, grader_id)
response_json = json.loads(response) response_json = json.loads(response)
rubric = response_json['rubric'] rubric = response_json['rubric']
rubric_input = inputtypes.RubricInput(module_system, etree.XML(rubric), {'id': location}) rubric_renderer = CombinedOpenEndedRubric(False)
rubric_html = etree.tostring(rubric_input.get_html()) rubric_xml = etree.XML(rubric)
rubric_html = rubric_renderer.render_rubric(rubric_xml)
response_json['rubric'] = rubric_html response_json['rubric'] = rubric_html
return json.dumps(response_json) return json.dumps(response_json)
except GradingServiceError: except GradingServiceError:
......
<table class="rubric"> <form class="rubric-template" id="inputtype_${id}">
% for i in range(len(rubric_categories)): <h3>Rubric</h3>
<% category = rubric_categories[i] %> % if view_only and has_score:
<tr> <p>This is the rubric that was used to grade your submission.The highlighted selection matches how the grader feels you performed in each category.</p>
<th> % elif view_only:
${category['description']} <p>Use the below rubric to rate this submission.</p>
% if category['has_score'] == True: % else:
(Your score: ${category['score']}) <p>Select the criteria you feel best represents this submission in each category.</p>
% endif % endif
</th> <table class="rubric">
% for i in range(len(categories)):
<% category = categories[i] %>
<tr>
<th>${category['description']}</th>
% for j in range(len(category['options'])): % for j in range(len(category['options'])):
<% option = category['options'][j] %> <% option = category['options'][j] %>
<td> <td>
% if view_only:
## if this is the selected rubric block, show it
% if option['selected']:
<div class="view-only selected-grade">
% else:
<div class="view-only"> <div class="view-only">
${option['text']}
% if option.has_key('selected'):
% if option['selected'] == True:
<div class="selected-grade">[${option['points']} points]</div>
%else:
<div class="grade">[${option['points']} points]</div>
% endif % endif
% else: ${option['text']}
<div class="grade">[${option['points']} points]</div> <div class="grade">[${option['points']} points]</div>
%endif
</div> </div>
% else:
<input type="radio" class="score-selection" name="score-selection-${i}" id="score-${i}-${j}" value="${option['points']}"/>
<label for="score-${i}-${j}">${option['text']}</label>
% endif
</td> </td>
% endfor % endfor
</tr> </tr>
% endfor % endfor
</table> </table>
\ No newline at end of file </form>
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