Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions problem_builder/answer.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,7 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
enforce_type=True
)

editable_fields = ('question', 'name', 'min_characters', 'weight', 'default_from')

@property
def display_name_with_default(self):
if not self.lonely_step:
return self._(u"Question {number}").format(number=self.step_number)
return self._(u"Question")
editable_fields = ('question', 'name', 'min_characters', 'weight', 'default_from', 'display_name', 'show_title')

@lazy
def student_input(self):
Expand All @@ -172,6 +166,7 @@ def mentoring_view(self, context=None):
""" Render this XBlock within a mentoring block. """
context = context or {}
context['self'] = self
context['hide_header'] = context.get('hide_header', False) or not self.show_title
html = loader.render_template('templates/html/answer_editable.html', context)

fragment = Fragment(html)
Expand Down
4 changes: 3 additions & 1 deletion problem_builder/mentoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,9 @@ def author_edit_view(self, context):
"""
Add some HTML to the author view that allows authors to add child blocks.
"""
fragment = super(MentoringBlock, self).author_edit_view(context)
fragment = Fragment(u'<div class="mentoring">') # This DIV is needed for CSS to apply to the previews
self.render_children(context, fragment, can_reorder=True, can_add=False)
fragment.add_content(u'</div>')
fragment.add_content(loader.render_template('templates/html/mentoring_add_buttons.html', {}))
fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', {
"url_name": self.url_name
Expand Down
5 changes: 4 additions & 1 deletion problem_builder/mrq.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ class MRQBlock(QuestionnaireAbstractBlock):
default=[],
)
hide_results = Boolean(display_name="Hide results", scope=Scope.content, default=False)
editable_fields = ('question', 'required_choices', 'ignored_choices', 'message', 'weight', 'hide_results', )
editable_fields = (
'question', 'required_choices', 'ignored_choices', 'message', 'display_name',
'show_title', 'weight', 'hide_results',
)

def describe_choice_correctness(self, choice_value):
if choice_value in self.required_choices:
Expand Down
37 changes: 15 additions & 22 deletions problem_builder/public/css/questionnaire.css
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
.mentoring .questionnaire .choices-list {
display: table;
position: relative;
width: 100%;
border-spacing: 0 6px;
padding-top: 10px;
margin-bottom: 10px;
}

.mentoring .questionnaire .choice-result {
display: inline-block;
display: table-cell;
width: 40px;
vertical-align: middle;
vertical-align: top;
cursor: pointer;
float: none;
}

.mentoring .questionnaire .choice {
overflow-y: hidden;
display: table-row;
}

.mentoring .questionnaire .choice-result.checkmark-correct,
Expand All @@ -32,10 +36,11 @@
background: none repeat scroll 0 0 #66A5B5;
font-family: arial;
font-size: 14px;
overflow-y: auto;
opacity: 0.9;
padding: 10px;
padding: 22px 10px;
width: 300px;
min-height: 40px;
z-index: 10000;
}

.mentoring .questionnaire .choice-tips .title {
Expand All @@ -48,6 +53,7 @@
.mentoring .questionnaire .feedback .tip-choice-group,
.mentoring .questionnaire .feedback .message-content {
position: relative;
overflow-y: auto;
}

.mentoring .questionnaire .choice-tips .close,
Expand All @@ -69,26 +75,13 @@
}

.mentoring .choices-list .choice-selector {
margin-right: 5px;
display: table-cell;
vertical-align: top;
width: 28px;
}

.mentoring .choice-label {
display: inline-block;
margin-top: 8px;
margin-bottom: 5px;
display: table-cell;
vertical-align: top;
line-height: 1.3;
}

.mentoring .choices-list .choice-text > .xblock-light-child * {
vertical-align: middle;
}

.mentoring .choices-list .choice-text > .xblock-light-child,
.mentoring .choices-list .choice-text > .xblock-light-child > .html_child {
/*
HTML Light Child content is wrapped in two divs: div.xblock-light-child and just div
On the other hand, choice are usually rendered inline.
Hence, we render first two divs inline, than all the actual content of HTML is rendered as is
*/
display: inline-block;
}
7 changes: 7 additions & 0 deletions problem_builder/public/js/questionnaire.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,22 @@ function MessageView(element, mentoring) {
var data = $(tip).data();
if (data && data.width) {
popupDOM.css('width', data.width);
popupDOM.find('.tip-choice-group').css('width', data.width);
} else {
popupDOM.css('width', '');
popupDOM.find('.tip-choice-group').css('width', '');
}

if (data && data.height) {
popupDOM.css('height', data.height);
popupDOM.css('maxHeight', data.height);
} else {
popupDOM.css('height', '');
popupDOM.css('maxHeight', '');
}
// .tip-choice-group should always be the same height as the popup
// for scrolling to work properly.
popupDOM.find('.tip-choice-group').height(popupDOM.height());

var container = popupDOM.parent('.choice-tips-container');
if (container.length) {
Expand Down
21 changes: 15 additions & 6 deletions problem_builder/questionnaire.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

# Imports ###########################################################

from django.utils.safestring import mark_safe
import logging
from lxml import etree
from xblock.core import XBlock
from xblock.fields import Scope, String, Float, List, UNIQUE_ID
Expand Down Expand Up @@ -81,7 +83,7 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
scope=Scope.content,
enforce_type=True
)
editable_fields = ('question', 'message', 'weight')
editable_fields = ('question', 'message', 'weight', 'display_name', 'show_title')
has_children = True

def _(self, text):
Expand All @@ -98,6 +100,9 @@ def parse_xml(cls, node, runtime, keys, id_generator):

# Load XBlock properties from the XML attributes:
for name, value in node.items():
if name not in block.fields:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bradenmacdonald heads up - parse_xml is going to be removed: open-craft/xblock-mentoring#16

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@e-kolpakov Yep, that's great. Thanks :)

logging.warn("XBlock %s does not contain field %s", type(block), name)
continue
field = block.fields[name]
if isinstance(field, List) and not value.startswith('['):
# This list attribute is just a string of comma separated strings:
Expand All @@ -114,10 +119,13 @@ def parse_xml(cls, node, runtime, keys, id_generator):
return block

@property
def display_name_with_default(self):
if not self.lonely_step:
return self._(u"Question {number}").format(number=self.step_number)
return self._(u"Question")
def html_id(self):
"""
A short, simple ID string used to uniquely identify this question.

This is only used by templates for matching <input> and <label> elements.
"""
return unicode(id(self)) # Unique as long as all choices are loaded at once

def student_view(self, context=None):
name = getattr(self, "unmixed_class", self.__class__).__name__
Expand All @@ -127,6 +135,7 @@ def student_view(self, context=None):
context = context or {}
context['self'] = self
context['custom_choices'] = self.custom_choices
context['hide_header'] = context.get('hide_header', False) or not self.show_title

fragment = Fragment(loader.render_template(template_path, context))
# If we use local_resource_url(self, ...) the runtime may insert many identical copies
Expand Down Expand Up @@ -156,7 +165,7 @@ def all_choice_values(self):

@property
def human_readable_choices(self):
return [{"display_name": c.content, "value": c.value} for c in self.custom_choices]
return [{"display_name": mark_safe(c.content), "value": c.value} for c in self.custom_choices]

@staticmethod
def choice_values_provider(question):
Expand Down
33 changes: 32 additions & 1 deletion problem_builder/step.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@
#

from lazy import lazy
from xblock.fields import String, Boolean, Scope
from xblockutils.helpers import child_isinstance


# Make '_' a no-op so we can scrape strings
def _(text):
return text


def _normalize_id(key):
"""
Helper method to normalize a key to avoid issues where some keys have version/branch and others don't.
Expand Down Expand Up @@ -49,10 +55,26 @@ def steps(self):

class StepMixin(object):
"""
An XBlock mixin for a child block that is a "Step"
An XBlock mixin for a child block that is a "Step".

A step is a question that the user can answer (as opposed to a read-only child).
"""
has_author_view = True

# Fields:
display_name = String(
display_name=_("Question title"),
help=_('Leave blank to use the default ("Question 1", "Question 2", etc.)'),
default="", # Blank will use 'Question x' - see display_name_with_default
scope=Scope.content
)
show_title = Boolean(
display_name=_("Show title"),
help=_("Display the title?"),
default=True,
scope=Scope.content
)

@lazy
def step_number(self):
return list(self.get_parent().steps).index(_normalize_id(self.scope_ids.usage_id)) + 1
Expand All @@ -63,6 +85,15 @@ def lonely_step(self):
raise ValueError("Step's parent should contain Step", self, self.get_parent().steps)
return len(self.get_parent().steps) == 1

@property
def display_name_with_default(self):
""" Get the title/display_name of this question. """
if self.display_name:
return self.display_name
if not self.lonely_step:
return self._(u"Question {number}").format(number=self.step_number)
return self._(u"Question")

def author_view(self, context):
context = context or {}
context['hide_header'] = True
Expand Down
11 changes: 8 additions & 3 deletions problem_builder/templates/html/mcqblock.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
{% for choice in custom_choices %}
<div class="choice">
<div class="choice-result fa icon-2x"></div>
<label class="choice-label">
<input class="choice-selector" type="radio" name="{{ self.name }}" value="{{ choice.value }}"{% if self.student_choice == choice.value %} checked{% endif %} />
<span class="choice-text">{{ choice.content|safe }}</span>
<div class="choice-selector">
<input id="choice-{{ self.html_id }}-{{ forloop.counter }}" type="radio"
name="{{ self.name }}" value="{{ choice.value }}"
{% if self.student_choice == choice.value %} checked{% endif %}
/>
</div>
<label class="choice-label" for="choice-{{ self.html_id }}-{{ forloop.counter }}">
{{ choice.content|safe }}
</label>
<div class="choice-tips-container">
<div class="choice-tips"></div>
Expand Down
13 changes: 8 additions & 5 deletions problem_builder/templates/html/mrqblock.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
{% for choice in custom_choices %}
<div class="choice">
<div class="choice-result fa icon-2x"></div>
<label class="choice-label">
<input class="choice-selector" type="checkbox" name="{{ self.name }}"
value="{{ choice.value }}"
{% if choice.value in self.student_choices %} checked{% endif %} />
<span class="choice-text">{{ choice.content|safe }}</span>
<div class="choice-selector">
<input id="choice-{{ self.html_id }}-{{ forloop.counter }}" type="checkbox"
name="{{ self.name }}" value="{{ choice.value }}"
{% if choice.value in self.student_choices %} checked{% endif %}
/>
</div>
<label class="choice-label" for="choice-{{ self.html_id }}-{{ forloop.counter }}">
{{ choice.content|safe }}
</label>
<div class="choice-tips-container">
<div class="choice-tips"></div>
Expand Down
35 changes: 20 additions & 15 deletions problem_builder/templates/html/ratingblock.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,37 @@
<p>{{ self.question }}</p>
</legend>
<div class="choices-list">
{% for value in '12345' %}
{% for i in '12345' %}
<div class="choice">
<div class="choice-result fa icon-2x"></div>
<label class="choice-label">
<input class="choice-selector" type="radio" name="{{ self.name }}" value="{{ value }}"
{% if self.student_choice == value %}checked{% endif %} />
{{ value }}
{% if forloop.first %}
<span class="low">- {{ self.low }}</span>
{% endif %}
{% if forloop.last %}
<span class="low">- {{ self.high }}</span>
{% endif %}
<div class="choice-selector">
<input id="choice-{{ self.html_id }}-{{i}}" type="radio"
name="{{ self.name }}" value="{{i}}"
{% if self.student_choice == i %} checked{%else%} data-student-choice='{{self.student_choice}}'{% endif %}
/>
</div>
<label class="choice-label" for="choice-{{ self.html_id }}-{{i}}">
{{i}}
{% if i == '1' %} - {{ self.low }}{% endif %}
{% if i == '5' %} - {{ self.high }}{% endif %}
</label>
<div class="choice-tips-container">
<div class="choice-tips"></div>
</div>
</div>
{% endfor %}

{% for choice in custom_choices %}
<div class="choice">
<div class="choice-result fa icon-2x"></div>
<label class="choice-label">
<input class="choice-selector" type="radio" name="{{ self.name }}" value="{{ choice.value }}"
{% if self.student_choice == '{{ choice.value }}' %} checked{% endif %} />
<span class="choice-text">{{ choice.content|safe }}</span>
<div class="choice-selector">
<input id="choice-{{ self.html_id }}-custom{{ forloop.counter }}" type="radio"
name="{{ self.name }}" value="{{ choice.value }}"
{% if self.student_choice == choice.value %} checked{%else%} data-student-choice='{{self.student_choice}}'{% endif %}
/>
</div>
<label class="choice-label" for="choice-{{ self.html_id }}-custom{{ forloop.counter }}">
{{ choice.content|safe }}
</label>
<div class="choice-tips-container">
<div class="choice-tips"></div>
Expand Down
Loading