Unverified Commit 0d7b18fa by Braden MacDonald Committed by GitHub

Merge pull request #173 from open-craft/uman/fix-pb-swipe

[MCKIN-5975] Make pb-swipe return proper student view data.
parents 1dc9578a 0931ca2c
...@@ -371,40 +371,29 @@ Swipeable Binary Response (`pb-swipe`) ...@@ -371,40 +371,29 @@ Swipeable Binary Response (`pb-swipe`)
- `block_id`: (string) The XBlock's usage ID - `block_id`: (string) The XBlock's usage ID
- `display_name`: (string) The XBlock's display name - `display_name`: (string) The XBlock's display name
- `type`: (string): The XBlock's identifier, "pb-swipe" - `type`: (string): The XBlock's identifier, "pb-swipe"
- `question`: (string) The question contents - `text`: (string) The text to display on the card
- `message`: (string) Feedback provided when submitting - `img_url`: (string) URL of the image to display as the background of the card
- `img_url`: (string) URL to an associated image - `correct`: (boolean) Whether the student's swipe is correct or not
- `weight`: (float) Overall value of the question
- `choices`: (array) A list of objects providing info about available
choices. See below for more info.
- `tips`: (array) A list of objects providing info about tips defined for the
problem. See below for more info.
#### `tips`
Each entry in the `tips` array contains these values:
- `content`: (string) The text content of the tip.
- `for_choices`: (array) A list of string values corresponding to choices to
which this tip applies to.
#### `choices`
Each item in the `choices` array contains these fields:
- `value`: (string) The value of the choice.
- `content`: (string) The description of the choice
### `student_view_user_state` ### `student_view_user_state`
- `student_choice`: (string) The value of the last submitted choice. - `student_choice`: (boolean) The value of the last submitted choice.
### POST Submit Data ### POST Submit Data
When submitting the problem the data should be a single object containing the When submitting the problem the data should be a single object containing the
`"value"` property which has the value of the selected choice. `"value"` property which has a boolean value indicating which swipe the student took:
Example: `{"value": "blue"}`
- Right -> True
- Left -> False
Example: `{"value": true}` if the student made a *rightwards* swipe.
The returned result will contain the following:
- `submission`: (boolean) The value just POST'd.
- `status`: (string) `"correct"` if the swipe is correct, `"incorrect"` otherwise.
- `score`: (integer) 1 if the swipe is correct, otherwise 0.
Multiple Response Question (`pb-mrq`) Multiple Response Question (`pb-mrq`)
------------------------------------- -------------------------------------
......
...@@ -260,7 +260,3 @@ ...@@ -260,7 +260,3 @@
.mentoring .copyright a { .mentoring .copyright a {
color: #69C0E8; color: #69C0E8;
} }
.swipe-img {
max-width: 100%;
}
...@@ -19,18 +19,20 @@ ...@@ -19,18 +19,20 @@
# #
# Imports ########################################################### # Imports ###########################################################
import logging import logging
from xblock.fields import Scope, String from xblock.core import XBlock
from xblock.validation import ValidationMessage from xblock.fields import Boolean, Scope, String
from xblock.fragment import Fragment
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin, XBlockWithPreviewMixin
from .mixins import StudentViewUserStateMixin from .mixins import QuestionMixin, StudentViewUserStateMixin
from .questionnaire import QuestionnaireAbstractBlock
from .sub_api import SubmittingXBlockMixin
# Globals ########################################################### # Globals ###########################################################
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
loader = ResourceLoader(__name__) loader = ResourceLoader(__name__)
...@@ -41,53 +43,51 @@ def _(text): ...@@ -41,53 +43,51 @@ def _(text):
# Classes ########################################################### # Classes ###########################################################
class SwipeBlock(SubmittingXBlockMixin, StudentViewUserStateMixin, QuestionnaireAbstractBlock):
@XBlock.needs("i18n")
class SwipeBlock(
QuestionMixin, StudioEditableXBlockMixin, StudentViewUserStateMixin, XBlockWithPreviewMixin, XBlock,
):
""" """
An XBlock used to ask binary-choice questions with a swiping interface An XBlock used to ask binary-choice questions with a swiping interface
""" """
CATEGORY = 'pb-swipe' CATEGORY = 'pb-swipe'
STUDIO_LABEL = _(u"Swipeable Binary Choice Question") STUDIO_LABEL = _(u"Swipeable Binary Choice Question")
USER_STATE_FIELDS = ['num_attempts', 'student_choice'] USER_STATE_FIELDS = ['student_choice']
message = String(
display_name=_("Message"),
help=_(
"General feedback provided when submitting. "
"(This is not shown if there is a more specific feedback tip for the choice selected by the learner.)"
),
scope=Scope.content,
default=""
)
student_choice = String( text = String(
# {Last input submitted by the student display_name=_("Text"),
help=_("Text to display on this card. The student must determine if this statement is true or false."),
scope=Scope.content,
default="", default="",
scope=Scope.user_state, multiline_editor=True,
) )
correct_choice = String(
display_name=_("Correct Choice"),
help=_("Specify the value that students may select for this question to be considered correct."),
scope=Scope.content,
values_provider=QuestionnaireAbstractBlock.choice_values_provider,
)
img_url = String( img_url = String(
display_name=_("Image"), display_name=_("Image"),
help=_( help=_("Specify the URL of an image associated with this question."),
"Specify the URL of an image associated with this question."
),
scope=Scope.content, scope=Scope.content,
default="" default=""
) )
editable_fields = QuestionnaireAbstractBlock.editable_fields + ('message', 'correct_choice', 'img_url',)
correct = Boolean(
display_name=_("Correct Choice"),
help=_("Specifies whether the card is correct."),
scope=Scope.content,
)
student_choice = Boolean(
scope=Scope.user_state,
help=_("Last input submitted by the student.")
)
editable_fields = ('display_name', 'text', 'img_url', 'correct')
def calculate_results(self, submission): def calculate_results(self, submission):
correct = self.correct_choice == submission correct = submission == self.correct
return { return {
'submission': submission, 'submission': submission,
'message': self.message_formatted,
'status': 'correct' if correct else 'incorrect', 'status': 'correct' if correct else 'incorrect',
'weight': self.weight,
'score': 1 if correct else 0, 'score': 1 if correct else 0,
} }
...@@ -99,33 +99,12 @@ class SwipeBlock(SubmittingXBlockMixin, StudentViewUserStateMixin, Questionnaire ...@@ -99,33 +99,12 @@ class SwipeBlock(SubmittingXBlockMixin, StudentViewUserStateMixin, Questionnaire
def submit(self, submission): def submit(self, submission):
log.debug(u'Received Swipe submission: "%s"', submission) log.debug(u'Received Swipe submission: "%s"', submission)
result = self.calculate_results(submission['value']) # We expect to receive a boolean indicating the swipe the student made (left -> false, right -> true)
self.student_choice = submission['value'] self.student_choice = submission['value']
result = self.calculate_results(self.student_choice)
log.debug(u'Swipe submission result: %s', result) log.debug(u'Swipe submission result: %s', result)
return result return result
def validate_field_data(self, validation, data):
"""
Validate this block's field data.
"""
super(SwipeBlock, self).validate_field_data(validation, data)
def add_error(msg):
validation.add(ValidationMessage(ValidationMessage.ERROR, msg))
if len(self.all_choice_values) == 0:
# Let's not set an error until at least one choice is added
return
if len(self.all_choice_values) != 2:
add_error(
self._(u"You must have exactly two choices.")
)
if not data.correct_choice:
add_error(
self._(u"You must indicate the correct answer, or the student will always get this question wrong.")
)
def student_view_data(self, context=None): def student_view_data(self, context=None):
""" """
Returns a JSON representation of the student_view of this XBlock, Returns a JSON representation of the student_view of this XBlock,
...@@ -136,15 +115,9 @@ class SwipeBlock(SubmittingXBlockMixin, StudentViewUserStateMixin, Questionnaire ...@@ -136,15 +115,9 @@ class SwipeBlock(SubmittingXBlockMixin, StudentViewUserStateMixin, Questionnaire
'block_id': unicode(self.scope_ids.usage_id), 'block_id': unicode(self.scope_ids.usage_id),
'display_name': self.display_name_with_default, 'display_name': self.display_name_with_default,
'type': self.CATEGORY, 'type': self.CATEGORY,
'question': self.question, 'text': self.text,
'message': self.message,
'img_url': self.expand_static_url(self.img_url), 'img_url': self.expand_static_url(self.img_url),
'choices': [ 'correct': self.correct,
{'value': choice['value'], 'content': choice['display_name']}
for choice in self.human_readable_choices
],
'weight': self.weight,
'tips': [tip.student_view_data() for tip in self.get_tips()],
} }
def expand_static_url(self, url): def expand_static_url(self, url):
...@@ -166,6 +139,18 @@ class SwipeBlock(SubmittingXBlockMixin, StudentViewUserStateMixin, Questionnaire ...@@ -166,6 +139,18 @@ class SwipeBlock(SubmittingXBlockMixin, StudentViewUserStateMixin, Questionnaire
pass pass
return url return url
@property def mentoring_view(self, context=None):
def expanded_img_url(self): """ Render the swipe image, text & whether it's correct within a mentoring block question. """
return self.expand_static_url(self.img_url) return Fragment(
(
u'<img src="{img_url}" style="max-width: 100%;" />'
u'<p class="swipe-text">"{text}"</p>'
).format(
img_url=self.expand_static_url(self.img_url),
text=self.text,
)
)
def student_view(self, context=None):
""" Normal view of this XBlock, identical to mentoring_view """
return self.mentoring_view(context)
{% load i18n %}
{% if not hide_header %}
<h4 class="question-title" id="heading_{{ self.html_id }}">{{ self.display_name_with_default }}</h4>
{% endif %}
{% if self.img_url.strip %}
<img class="swipe-img" src="{{ self.expanded_img_url }}" alt="" />
{% endif %}
<fieldset class="choices questionnaire" id="{{ self.html_id }}">
<legend class="question field-group-hd">{{ self.question|safe }}</legend>
<div class="choices-list">
{% for choice in custom_choices %}
<div class="choice" aria-live="polite" aria-atomic="true">
<label class="choice-label"
aria-describedby="feedback_{{ self.html_id }} choice_tips_{{ self.html_id }}-{{ forloop.counter }}">
<span class="choice-result fa icon-2x"
aria-label=""
data-label_correct="{% trans "Correct" %}"
data-label_incorrect="{% trans "Incorrect" %}"></span>
<span class="choice-selector">
<input type="radio" name="{{ self.name }}" value="{{ choice.value }}"
{% if self.student_choice == choice.value and not hide_prev_answer %} checked{% endif %}
/>
</span>
<span class="choice-label-text">{{ choice.content|safe }}</span>
</label>
<div class="choice-tips-container">
<div class="choice-tips" id="choice_tips_{{ self.html_id }}-{{ forloop.counter }}"></div>
</div>
</div>
{% endfor %}
<div class="feedback" id="feedback_{{ self.html_id }}"></div>
</div>
</fieldset>
...@@ -72,7 +72,7 @@ BLOCKS = [ ...@@ -72,7 +72,7 @@ BLOCKS = [
setup( setup(
name='xblock-problem-builder', name='xblock-problem-builder',
version='2.7.9', version='2.7.10',
description='XBlock - Problem Builder', description='XBlock - Problem Builder',
packages=find_packages(), packages=find_packages(),
install_requires=[ install_requires=[
......
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