Commit 1b66d3cd by Braden MacDonald

New show_title and custom title options

parent 700f1834
...@@ -141,13 +141,7 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock): ...@@ -141,13 +141,7 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
enforce_type=True enforce_type=True
) )
editable_fields = ('question', 'name', 'min_characters', 'weight', 'default_from') editable_fields = ('question', 'name', 'min_characters', 'weight', 'default_from', 'display_name', 'show_title')
@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")
@lazy @lazy
def student_input(self): def student_input(self):
...@@ -172,6 +166,7 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock): ...@@ -172,6 +166,7 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
""" Render this XBlock within a mentoring block. """ """ Render this XBlock within a mentoring block. """
context = context or {} context = context or {}
context['self'] = self 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) html = loader.render_template('templates/html/answer_editable.html', context)
fragment = Fragment(html) fragment = Fragment(html)
......
...@@ -69,7 +69,10 @@ class MRQBlock(QuestionnaireAbstractBlock): ...@@ -69,7 +69,10 @@ class MRQBlock(QuestionnaireAbstractBlock):
default=[], default=[],
) )
hide_results = Boolean(display_name="Hide results", scope=Scope.content, default=False) 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): def describe_choice_correctness(self, choice_value):
if choice_value in self.required_choices: if choice_value in self.required_choices:
......
...@@ -81,7 +81,7 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc ...@@ -81,7 +81,7 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
scope=Scope.content, scope=Scope.content,
enforce_type=True enforce_type=True
) )
editable_fields = ('question', 'message', 'weight') editable_fields = ('question', 'message', 'weight', 'display_name', 'show_title')
has_children = True has_children = True
def _(self, text): def _(self, text):
...@@ -113,12 +113,6 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc ...@@ -113,12 +113,6 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
return block 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 student_view(self, context=None): def student_view(self, context=None):
name = getattr(self, "unmixed_class", self.__class__).__name__ name = getattr(self, "unmixed_class", self.__class__).__name__
...@@ -127,6 +121,7 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc ...@@ -127,6 +121,7 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
context = context or {} context = context or {}
context['self'] = self context['self'] = self
context['custom_choices'] = self.custom_choices 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)) fragment = Fragment(loader.render_template(template_path, context))
# If we use local_resource_url(self, ...) the runtime may insert many identical copies # If we use local_resource_url(self, ...) the runtime may insert many identical copies
......
...@@ -19,9 +19,15 @@ ...@@ -19,9 +19,15 @@
# #
from lazy import lazy from lazy import lazy
from xblock.fields import String, Boolean, Scope
from xblockutils.helpers import child_isinstance from xblockutils.helpers import child_isinstance
# Make '_' a no-op so we can scrape strings
def _(text):
return text
def _normalize_id(key): def _normalize_id(key):
""" """
Helper method to normalize a key to avoid issues where some keys have version/branch and others don't. Helper method to normalize a key to avoid issues where some keys have version/branch and others don't.
...@@ -49,10 +55,26 @@ class StepParentMixin(object): ...@@ -49,10 +55,26 @@ class StepParentMixin(object):
class StepMixin(object): 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 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 @lazy
def step_number(self): def step_number(self):
return list(self.get_parent().steps).index(_normalize_id(self.scope_ids.usage_id)) + 1 return list(self.get_parent().steps).index(_normalize_id(self.scope_ids.usage_id)) + 1
...@@ -63,6 +85,15 @@ class StepMixin(object): ...@@ -63,6 +85,15 @@ class StepMixin(object):
raise ValueError("Step's parent should contain Step", self, self.get_parent().steps) raise ValueError("Step's parent should contain Step", self, self.get_parent().steps)
return len(self.get_parent().steps) == 1 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): def author_view(self, context):
context = context or {} context = context or {}
context['hide_header'] = True context['hide_header'] = True
......
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014-2015 Harvard, edX & OpenCraft
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
"""
Test that the various title/display_name options for Answer and MCQ/MRQ/Ratings work.
"""
# Imports ###########################################################
from mock import patch
from xblockutils.base_test import SeleniumXBlockTest
# Classes ###########################################################
class StepTitlesTest(SeleniumXBlockTest):
"""
Test that the various title/display_name options for Answer and MCQ/MRQ/Ratings work.
"""
test_parameters = (
# display_name, show_title?, expected_title: (None means default value)
("Custom Title", None, "Custom Title",),
("Custom Title", True, "Custom Title",),
("Custom Title", False, None),
("", None, "Question"),
("", True, "Question"),
("", False, None),
)
mcq_template = """
<problem-builder mode="{{mode}}">
<pb-mcq name="mcq_1_1" question="Who was your favorite character?"
correct_choices="gaius,adama,starbuck,roslin,six,lee"
{display_name_attr} {show_title_attr}
>
<pb-choice value="gaius">Gaius Baltar</pb-choice>
<pb-choice value="adama">Admiral William Adama</pb-choice>
<pb-choice value="starbuck">Starbuck</pb-choice>
<pb-choice value="roslin">Laura Roslin</pb-choice>
<pb-choice value="six">Number Six</pb-choice>
<pb-choice value="lee">Lee Adama</pb-choice>
</pb-mcq>
</problem-builder>
"""
mrq_template = """
<problem-builder mode="{{mode}}">
<pb-mrq name="mrq_1_1" question="What makes a great MRQ?"
ignored_choices="1,2,3"
{display_name_attr} {show_title_attr}
>
<pb-choice value="1">Lots of choices</pb-choice>
<pb-choice value="2">Funny choices</pb-choice>
<pb-choice value="3">Not sure</pb-choice>
</pb-mrq>
</problem-builder>
"""
rating_template = """
<problem-builder mode="{{mode}}">
<pb-rating name="rating_1_1" question="How do you rate Battlestar Galactica?"
correct_choices="5,6"
{display_name_attr} {show_title_attr}
>
<pb-choice value="6">More than 5 stars</pb-choice>
</pb-rating>
</problem-builder>
"""
long_answer_template = """
<problem-builder mode="{{mode}}">
<pb-answer name="answer_1_1" question="What did you think of the ending?"
{display_name_attr} {show_title_attr} />
</problem-builder>
"""
def setUp(self):
super(StepTitlesTest, self).setUp()
# Disable asides for this test since the acid aside seems to cause Database errors
# When we test multiple scenarios in one test method.
patcher = patch(
'workbench.runtime.WorkbenchRuntime.applicable_aside_types',
lambda self, block: [], create=True
)
patcher.start()
self.addCleanup(patcher.stop)
def test_all_the_things(self):
""" Test various permutations of our problem-builder components and title options. """
# We use a loop within the test rather than DDT, because this is WAY faster
# since we can bypass the Selenium set-up and teardown
for display_name, show_title, expected_title in self.test_parameters:
for mode in ("standard", "assessment"):
for qtype in ("mcq", "mrq", "rating", "long_answer"):
template = getattr(self, qtype + "_template")
xml = template.format(
mode=mode,
display_name_attr='display_name="{}"'.format(display_name) if display_name is not None else "",
show_title_attr='show_title="{}"'.format(show_title) if show_title is not None else "",
)
self.set_scenario_xml(xml)
pb_element = self.go_to_view()
if expected_title:
h3 = pb_element.find_element_by_css_selector('h3')
self.assertEqual(h3.text, expected_title)
else:
# No <h3> element should be present:
all_h3s = pb_element.find_elements_by_css_selector('h3')
self.assertEqual(len(all_h3s), 0)
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