Commit 347af2e6 by Alan Boudreault

max_attempts should be global to the mentoring block

parent cb899cb7
......@@ -30,7 +30,7 @@ from lxml import etree
from StringIO import StringIO
from xblock.core import XBlock
from xblock.fields import Boolean, Scope, String, Float
from xblock.fields import Boolean, Scope, String, Integer, Float
from xblock.fragment import Fragment
from .light_children import XBlockWithLightChildren
......@@ -69,6 +69,10 @@ class MentoringBlock(XBlockWithLightChildren):
display_submit = Boolean(help="Allow to submit current block?", default=True, scope=Scope.content)
xml_content = String(help="XML content", default='', scope=Scope.content)
weight = Float(help="Defines the maximum total grade of the block.", default=0, scope=Scope.content)
num_attempts = Integer(help="Number of attempts a user has answered for this questions",
default=0, scope=Scope.user_state)
max_attempts = Integer(help="Number of max attempts for this questions", default=0,
scope=Scope.content)
icon_class = 'problem'
has_score = True
......@@ -86,7 +90,7 @@ class MentoringBlock(XBlockWithLightChildren):
self.runtime.local_resource_url(self, 'public/js/vendor/underscore-min.js'))
fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring.js'))
fragment.add_resource(load_resource('templates/html/mentoring_progress.html'), "text/html")
fragment.add_resource(load_resource('templates/html/mrqblock_attempts.html'), "text/html")
fragment.add_resource(load_resource('templates/html/mentoring_attempts.html'), "text/html")
fragment.initialize_js('MentoringBlock')
......@@ -112,6 +116,15 @@ class MentoringBlock(XBlockWithLightChildren):
log.info(u'Received submissions: {}'.format(submissions))
self.attempted = True
# TODO Don't do that...
# The xblock.fields.Field implementation has the effect that if the data *is not* read from
# the json (real xblock), it is set as dirty and it doesn't take care of the data type. So
# self.max_attempts is a string since it is read from the xml and set using 'setattr'.
try:
max_attempts = int(self.max_attempts)
except ValueError:
max_attempts = 0
submit_results = []
completed = True
for child in self.get_children_objects():
......@@ -145,11 +158,17 @@ class MentoringBlock(XBlockWithLightChildren):
})
self.completed = bool(completed)
if not self.completed and max_attempts > 0:
setattr(self, 'num_attempts', self.num_attempts + 1)
return {
'submitResults': submit_results,
'completed': self.completed,
'attempted': self.attempted,
'message': message,
'max_attempts': max_attempts,
'num_attempts': self.num_attempts
}
def get_message_fragment(self, message_type):
......
......@@ -26,7 +26,7 @@
import logging
from .light_children import Integer, List, Scope
from .light_children import List, Scope
from .questionnaire import QuestionnaireAbstractBlock
from .utils import render_template
......@@ -43,17 +43,6 @@ class MRQBlock(QuestionnaireAbstractBlock):
An XBlock used to ask multiple-response questions
"""
student_choices = List(help="Last submissions by the student", default=[], scope=Scope.user_state)
max_attempts = Integer(help="Number of max attempts for this questions", default=0,
scope=Scope.content)
num_attempts = Integer(help="Number of attempts a user has answered for this questions",
default=0, scope=Scope.user_state)
# TODO REMOVE THIS, ONLY NEEDED FOR LIGHTCHILDREN
@classmethod
def get_fields_to_save(cls):
return [
'num_attempts'
]
def submit(self, submissions):
log.debug(u'Received MRQ submissions: "%s"', submissions)
......@@ -85,26 +74,14 @@ class MRQBlock(QuestionnaireAbstractBlock):
}),
})
self.message = u'Your answer is correct!' if completed else u'Your answer is incorrect.'
# Do not increase the counter is the answer is correct
if not completed:
setattr(self, 'num_attempts', self.num_attempts + 1)
if self.max_attempts > 0 and self.num_attempts >= self.max_attempts:
completed = True
self.message += u' You have reached the maximum number of attempts for this question. ' \
u'Your next answers won''t be saved. You can check the answer(s) using the "Show Answer(s)" button.'
else:
self.student_choices = submissions
self.student_choices = submissions
result = {
'submissions': submissions,
'completed': completed,
'choices': results,
'message': self.message,
'max_attempts': self.max_attempts,
'num_attempts': self.num_attempts,
'score': sum(1.0 for r in results if r['completed']) / len(results),
'submissions': submissions,
'completed': completed,
'choices': results,
'message': self.message,
'score': sum(1.0 for r in results if r['completed']) / len(results)
}
log.debug(u'MRQ submissions result: %s', result)
......
......@@ -61,3 +61,12 @@
.mentoring .progress .indicator .checkmark-incorrect {
color: #ff0000;
}
.mentoring .attempts {
margin-top: 20px;
display: inline-block;
vertical-align: baseline;
color: #777;
font-style: italic;
webkit-font-smoothing: antialiased;
}
......@@ -82,14 +82,6 @@
margin-right: 5px;
}
.mentoring .mrq-attempts {
display: inline-block;
vertical-align: baseline;
color: #777;
font-style: italic;
webkit-font-smoothing: antialiased;
}
.mentoring .mrq-attempts div {
display: inline-block;
.mentoring .show-answer {
display: none;
}
function MentoringBlock(runtime, element) {
var progressTemplate = _.template($('#xblock-progress-template').html());
var attemptsTemplate = _.template($('#xblock-attempts-template').html());
var children; // Keep track of children. A Child need a single object scope for its data.
function renderProgress() {
var data = $('.progress', element).data();
$('.indicator', element).html(progressTemplate(data));
}
function renderAttempts() {
var data = $('.attempts', element).data();
$('.attempts', element).html(attemptsTemplate(data));
}
function renderDependency() {
var warning_dom = $('.missing-dependency', element),
data = warning_dom.data();
......@@ -31,24 +38,37 @@ function MentoringBlock(runtime, element) {
var input = submitResult[0],
result = submitResult[1],
child = getChildByName(element, input);
callIfExists(child, 'handleSubmit', result);
var options = {
max_attempts: results.max_attempts,
num_attempts: results.num_attempts
}
callIfExists(child, 'handleSubmit', result, options);
});
$('.progress', element).data('completed', results.completed ? 'True' : 'False');
$('.progress', element).data('attempted', results.attempted ? 'True' : 'False');
renderProgress();
$('.attempts', element).data('max_attempts', results.max_attempts);
$('.attempts', element).data('num_attempts', results.num_attempts);
renderAttempts();
// Messages should only be displayed upon hitting 'submit', not on page reload
messages_dom.append(results.message);
if (messages_dom.html().trim()) {
messages_dom.prepend('<div class="title1">Feedback</div>');
messages_dom.show();
}
validateXBlock();
}
function getChildren(element) {
var children_dom = $('.xblock-light-child', element),
children = [];
if (!_.isUndefined(children))
return children;
var children_dom = $('.xblock-light-child', element);
children = [];
$.each(children_dom, function(index, child_dom) {
var child_type = $(child_dom).attr('data-type'),
......@@ -99,13 +119,15 @@ function MentoringBlock(runtime, element) {
callIfExists(child, 'init', options);
});
validateXBlock();
if (submit_dom.length) {
renderProgress();
}
renderAttempts();
renderDependency();
validateXBlock();
}
function handleRefreshResults(results) {
......@@ -122,15 +144,20 @@ function MentoringBlock(runtime, element) {
function validateXBlock() {
var submit_dom = $(element).find('.submit .input-main');
var children_are_valid = true;
var data = {};
var data = $('.attempts', element).data();
var children = getChildren(element);
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.name !== undefined) {
var child_validation = callIfExists(child, 'validate');
if (_.isBoolean(child_validation)) {
children_are_valid = children_are_valid && child_validation
if ((data.max_attempts > 0) && (data.num_attempts >= data.max_attempts)) {
children_are_valid = false;
}
else {
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (child.name !== undefined) {
var child_validation = callIfExists(child, 'validate');
if (_.isBoolean(child_validation)) {
children_are_valid = children_are_valid && child_validation
}
}
}
}
......
......@@ -91,23 +91,11 @@ function MCQBlock(runtime, element) {
}
function MRQBlock(runtime, element) {
var mrqAttemptsTemplate = _.template($('#xblock-mrq-attempts').html());
return {
renderAttempts: function() {
var data = $('.mrq-attempts', element).data();
$('.mrq-attempts', element).html(mrqAttemptsTemplate(data));
// bind show answer button
var showAnswerButton = $('button', element);
if (showAnswerButton.length != 0) {
if (_.isUndefined(this.answers))
showAnswerButton.hide();
else
showAnswerButton.on('click', _.bind(this.toggleAnswers, this));
}
},
init: function() {
this.renderAttempts();
var answerButton = $('button', element);
answerButton.on('click', _.bind(this.toggleAnswers, this));
this.showAnswerButton();
},
submit: function() {
......@@ -125,7 +113,7 @@ function MRQBlock(runtime, element) {
return checkedValues;
},
handleSubmit: function(result) {
handleSubmit: function(result, options) {
var messageView = MessageView(element);
if (result.message) {
......@@ -149,8 +137,8 @@ function MRQBlock(runtime, element) {
});
choiceResultDOM.removeClass('incorrect icon-exclamation correct icon-ok');
/* show hint if checked or max_attempts is disabled */
if (result.completed || choiceInputDOM.prop('checked') || result.max_attempts <= 0) {
/* show hint if checked or max_attempts is disabled */
if (result.completed || choiceInputDOM.prop('checked') || options.max_attempts <= 0) {
if (choice.completed) {
choiceResultDOM.addClass('correct icon-ok');
} else if (!choice.completed) {
......@@ -170,10 +158,21 @@ function MRQBlock(runtime, element) {
});
});
this.answers = answers;
this.showAnswerButton(options);
},
showAnswerButton: function(options) {
var button = $('.show-answer', element);
var is_enabled = options && (options.num_attempts >= options.max_attempts);
$('.mrq-attempts', element).data('num_attempts', result.num_attempts);
this.renderAttempts();
if (is_enabled && _.isArray(this.answers)) {
button.show();
}
else {
button.hide();
}
},
toggleAnswers: function() {
......
......@@ -7,6 +7,7 @@
{{c.body_html|safe}}
{% endfor %}
{% if self.display_submit %}
<div class="attempts" data-max_attempts="{{ self.max_attempts }}" data-num_attempts="{{ self.num_attempts }}"></div>
<div class="submit">
<input type="button" class="input-main" value="Submit"></input>
<span class="progress" data-completed="{{ self.completed }}" data-attempted="{{ self.attempted }}">
......
<script type="text/template" id="xblock-attempts-template">
<% if (_.isNumber(max_attempts) && max_attempts > 0) {{ %>
<div> You have used <%= _.min([num_attempts, max_attempts]) %> of <%= max_attempts %> attempts.</div>
<% }} %>
</script>
<script type="text/template" id="xblock-mrq-attempts">
<% if (_.isNumber(max_attempts) && max_attempts > 0) {{ %>
<% if (num_attempts >= max_attempts) {{ %>
<button class="show">
<span class="show-label">Show Answer(s)</span>
</button>
<% }} %>
<div> You have used <%= _.min([num_attempts, max_attempts]) %> of <%= max_attempts %> attempts for this question.</div>
<% }} %>
</script>
......@@ -21,4 +21,8 @@
<div class="choice-message"></div>
</div>
</fieldset>
<div class="mrq-attempts" data-max_attempts="{{ self.max_attempts }}" data-num_attempts="{{ self.num_attempts }}"></div>
<div class="show-answer">
<button class="show">
<span class="show-label">Show Answer(s)</span>
</button>
</div>
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