Commit 990ba628 by Jonathan Piacenti

Add scaffolding for displaying list of questions in extended feedback.

parent 7f0d3def
......@@ -61,6 +61,10 @@ def _(text):
Score = namedtuple("Score", ["raw", "percentage", "correct", "incorrect", "partially_correct"])
CORRECT = 'correct'
INCORRECT = 'incorrect'
PARTIAL = 'partial'
@XBlock.needs("i18n")
@XBlock.wants('settings')
......@@ -160,6 +164,11 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
default=[],
scope=Scope.user_state
)
extended_feedback = Boolean(
help=_("Show extended feedback details when all attempts are used up."),
default=False,
Scope=Scope.content
)
# Global user state
next_step = String(
......@@ -201,17 +210,39 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
return xblock_settings[self.theme_key]
return _default_theme_config
def get_question_number(self, question_id):
"""
Get the step number of the question id
"""
for child_id in self.children:
question = self.runtime.get_block(child_id)
if isinstance(question, StepMixin) and (question.name == question_id):
return question.step_number
raise ValueError("Question ID in answer set not a step of this Mentoring Block!")
def answer_mapper(self, answer_status):
"""
Create a JSON-dumpable object with readable key names from a list of student answers.
"""
return [
{
'number': self.get_question_number(answer[0]),
'id': answer[0],
'details': answer[1],
} for answer in self.student_results if answer[1]['status'] == answer_status
]
@property
def score(self):
"""Compute the student score taking into account the weight of each step."""
weights = (float(self.runtime.get_block(step_id).weight) for step_id in self.steps)
total_child_weight = sum(weights)
if total_child_weight == 0:
return Score(0, 0, 0, 0, 0)
return Score(0, 0, [], [], [])
score = sum(r[1]['score'] * r[1]['weight'] for r in self.student_results) / total_child_weight
correct = sum(1 for r in self.student_results if r[1]['status'] == 'correct')
incorrect = sum(1 for r in self.student_results if r[1]['status'] == 'incorrect')
partially_correct = sum(1 for r in self.student_results if r[1]['status'] == 'partial')
correct = self.answer_mapper(CORRECT)
incorrect = self.answer_mapper(INCORRECT)
partially_correct = self.answer_mapper(PARTIAL)
return Score(score, int(round(score * 100)), correct, incorrect, partially_correct)
......@@ -259,6 +290,7 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring.js'))
fragment.add_resource(loader.load_unicode('templates/html/mentoring_attempts.html'), "text/html")
fragment.add_resource(loader.load_unicode('templates/html/mentoring_grade.html'), "text/html")
fragment.add_resource(loader.load_unicode('templates/html/mentoring_review_questions.html'), "text/html")
self.include_theme_files(fragment)
# Workbench doesn't have font awesome, so add it:
......@@ -350,6 +382,72 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
else:
return None
def show_extended_feedback(self):
return self.extended_feedback and self.max_attempts_reached
def feedback_dispatch(self, target_data, stringify):
if self.show_extended_feedback():
if stringify:
return json.dumps(target_data)
else:
return target_data
def correct_json(self, stringify=True):
return self.feedback_dispatch(self.score.correct, stringify)
def incorrect_json(self, stringify=True):
return self.feedback_dispatch(self.score.incorrect, stringify)
def partial_json(self, stringify=True):
return self.feedback_dispatch(self.score.partially_correct, stringify)
@XBlock.json_handler
def get_results(self, queries, suffix=''):
"""
Gets detailed results in the case of extended feedback.
It may be a good idea to eventually have this function get results
in the general case instead of loading them in the template in the future,
and only using it for extended feedback situations.
Right now there are two ways to get results-- through the template upon loading up
the mentoring block, or after submission of an AJAX request like in
submit or get_results here.
"""
results = []
if not self.show_extended_feedback():
return {
'results': [],
'error': 'Extended feedback results cannot be obtained.'
}
completed = True
choices = dict(self.student_results)
step = self.step
# Only one child should ever be of concern with this method.
for child_id in self.steps:
child = self.runtime.get_block(child_id)
if child.name and child.name in queries:
results = [child.name, child.get_results(choices[child.name])]
# Children may have their own definition of 'completed' which can vary from the general case
# of the whole mentoring block being completed. This is because in standard mode, all children
# must be correct to complete the block. In assessment mode with extended feedback, completion
# happens when you're out of attempts, no matter how you did.
completed = choices[child.name]['status'] == 'correct'
break
# The 'completed' message should always be shown in this case, since no more attempts are available.
message = self.get_message(True)
return {
'results': results,
'completed': completed,
'attempted': self.attempted,
'message': message,
'step': step,
'max_attempts': self.max_attempts,
'num_attempts': self.num_attempts,
}
@XBlock.json_handler
def submit(self, submissions, suffix=''):
log.info(u'Received submissions: {}'.format(submissions))
......@@ -480,9 +578,9 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
'num_attempts': self.num_attempts,
'step': self.step,
'score': score.percentage,
'correct_answer': score.correct,
'incorrect_answer': score.incorrect,
'partially_correct_answer': score.partially_correct,
'correct_answer': len(score.correct),
'incorrect_answer': len(score.incorrect),
'partially_correct_answer': len(score.partially_correct),
'assessment_message': assessment_message,
}
......
function MentoringAssessmentView(runtime, element, mentoring) {
var gradeTemplate = _.template($('#xblock-grade-template').html());
var reviewQuestionsTemplate = _.template($('#xblock-review-questions-template').html());
var submitDOM, nextDOM, reviewDOM, tryAgainDOM, messagesDOM;
var submitXHR;
var checkmark;
......@@ -24,9 +25,24 @@ function MentoringAssessmentView(runtime, element, mentoring) {
messagesDOM.empty().hide();
}
function no_more_attempts() {
var attempts_data = $('.attempts', element).data();
return attempts_data.num_attempts >= attempts_data.max_attempts;
}
function renderGrade() {
notify('navigation', {state: 'unlock'})
var data = $('.grade', element).data();
_.extend(data, {
'enable_extended': (no_more_attempts() && data.extended_feedback),
'runDetails': function(label) {
if (! this.enable_extended) {
return '.'
}
var self = this;
return reviewQuestionsTemplate({'questions': self[label], 'label': label})
}
});
cleanAll();
$('.grade', element).html(gradeTemplate(data));
reviewDOM.hide();
......
......@@ -21,9 +21,9 @@
{% if self.display_submit %}
<div class="grade" data-score="{{ self.score.1 }}"
data-correct_answer="{{ self.score.2 }}"
data-incorrect_answer="{{ self.score.3 }}"
data-partially_correct_answer="{{ self.score.4 }}"
data-correct_answer="{{ self.score.2|length }}"
data-incorrect_answer="{{ self.score.3|length }}"
data-partially_correct_answer="{{ self.score.4|length }}"
data-max_attempts="{{ self.max_attempts }}"
data-num_attempts="{{ self.num_attempts }}"
data-assessment_message="{{ self.assessment_message }}">
......
......@@ -10,33 +10,42 @@
<hr/>
<span class="assessment-checkmark icon-2x checkmark-correct icon-ok fa fa-check"></span>
<p>
<%= _.template(
ngettext(
"You answered 1 question correctly.",
"You answered {number_correct} questions correctly.",
correct_answer
), {number_correct: correct_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<div class="results-section">
<p>
<%= _.template(
ngettext(
"You answered 1 question correctly.",
"You answered {number_correct} questions correctly.",
correct_answer
), {number_correct: correct_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<%= runDetails('correct') %>
</div>
<span class="assessment-checkmark icon-2x checkmark-partially-correct icon-ok fa fa-check"></span>
<p>
<%= _.template(
ngettext(
"You answered 1 question partially correctly.",
"You answered {number_partially_correct} questions partially correctly.",
partially_correct_answer
), {number_partially_correct: partially_correct_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<div class="results-section">
<p>
<%= _.template(
ngettext(
"You answered 1 question partially correctly.",
"You answered {number_partially_correct} questions partially correctly.",
partially_correct_answer
), {number_partially_correct: partially_correct_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<%= runDetails('partial') %>
</div>
<span class="assessment-checkmark icon-2x checkmark-incorrect icon-exclamation fa fa-exclamation"></span>
<p>
<%= _.template(
ngettext(
"You answered 1 question incorrectly.",
"You answered {number_incorrect} questions incorrectly.",
incorrect_answer
), {number_incorrect: incorrect_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<div class="results-section">
<p>
<%= _.template(
ngettext(
"You answered 1 question incorrectly.",
"You answered {number_incorrect} questions incorrectly.",
incorrect_answer
), {number_incorrect: incorrect_answer}, {interpolate: /\{(.+?)\}/g})
%>
</p>
<%= runDetails('incorrect') %>
</div>
</script>
<script type="text/template" id="xblock-review-questions-template">
<% var q, last_question; %>
<ul class="review-list <%= label %>-list">
<% for (var question in questions) {{ q = questions[question]; last_question = question == questions.length - 1; %>
<li><a href="#" class="question-link" data-name="<%= q.id %>"><= gettext("Question {number}", {number: q.number}) =></a></li>
<% }} %>
</ul>
</script>
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