diff --git a/.travis.yml b/.travis.yml
index 6c0587c4..dbb65671 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -5,12 +5,13 @@ before_install:
- "export DISPLAY=:99"
- "sh -e /etc/init.d/xvfb start"
install:
- - "pip install -e git://github.com/edx/xblock-sdk.git#egg=xblock-sdk"
+ - "pip install -e git://github.com/edx/xblock-sdk.git@22c1b2f173919bef22f2d9d9295ec5396d02dffd#egg=xblock-sdk"
- "pip install -r requirements.txt"
- - "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements.txt"
- - "pip install -r $VIRTUAL_ENV/src/xblock-sdk/test-requirements.txt"
+ - "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/base.txt"
+ - "pip install -r $VIRTUAL_ENV/src/xblock-sdk/requirements/test.txt"
- "pip uninstall -y xblock-problem-builder && python setup.py sdist && pip install dist/xblock-problem-builder-2.0.tar.gz"
- "pip install -r test_requirements.txt"
+ - "mkdir var"
script:
- pep8 problem_builder --max-line-length=120
- pylint problem_builder --disable=all --enable=function-redefined,undefined-variable,unused-variable
diff --git a/problem_builder/answer.py b/problem_builder/answer.py
index 68ddf973..d2e15d9c 100644
--- a/problem_builder/answer.py
+++ b/problem_builder/answer.py
@@ -152,13 +152,6 @@ class AnswerBlock(SubmittingXBlockMixin, AnswerMixin, QuestionMixin, StudioEdita
default="",
multiline_editor=True,
)
- weight = Float(
- display_name=_("Weight"),
- help=_("Defines the maximum total grade of the answer block."),
- default=1,
- scope=Scope.settings,
- enforce_type=True
- )
editable_fields = ('question', 'name', 'min_characters', 'weight', 'default_from', 'display_name', 'show_title')
diff --git a/problem_builder/mixins.py b/problem_builder/mixins.py
index 3513638a..cdcafd85 100644
--- a/problem_builder/mixins.py
+++ b/problem_builder/mixins.py
@@ -1,5 +1,5 @@
from lazy import lazy
-from xblock.fields import String, Boolean, Scope
+from xblock.fields import String, Boolean, Float, Scope, UNIQUE_ID
from xblockutils.helpers import child_isinstance
from xblockutils.resources import ResourceLoader
@@ -125,12 +125,25 @@ class QuestionMixin(EnumerableChildMixin):
has_author_view = True
# Fields:
+ name = String(
+ display_name=_("Question ID (name)"),
+ help=_("The ID of this question (required). Should be unique within this mentoring component."),
+ default=UNIQUE_ID,
+ scope=Scope.settings, # Must be scope.settings, or the unique ID will change every time this block is edited
+ )
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
)
+ weight = Float(
+ display_name=_("Weight"),
+ help=_("Defines the maximum total grade of this question."),
+ default=1,
+ scope=Scope.content,
+ enforce_type=True
+ )
@lazy
def siblings(self):
diff --git a/problem_builder/public/css/problem-builder-edit.css b/problem_builder/public/css/problem-builder-edit.css
index 6d6c06a6..c1a0b3a3 100644
--- a/problem_builder/public/css/problem-builder-edit.css
+++ b/problem_builder/public/css/problem-builder-edit.css
@@ -49,3 +49,7 @@
margin-top: 1em;
padding-top: 0.3em;
}
+
+.xblock-author_view-pb-slider .url-name-footer {
+ margin: 0 -20px -20px -20px; /* Counteract spacing from xblock-render wrapper. */
+}
diff --git a/problem_builder/public/css/problem-builder.css b/problem_builder/public/css/problem-builder.css
index 41decf07..8417767a 100644
--- a/problem_builder/public/css/problem-builder.css
+++ b/problem_builder/public/css/problem-builder.css
@@ -64,6 +64,32 @@
margin-bottom: 0;
}
+.mentoring .xblock-pb-slider p label {
+ font-size: inherit;
+}
+
+.mentoring .pb-slider-box {
+ max-width: 400px;
+}
+
+.mentoring .pb-slider-range {
+ width: 100%;
+}
+
+.mentoring .pb-slider-min-label {
+ float: left;
+}
+
+.mentoring .pb-slider-max-label {
+ float: right;
+}
+
+.mentoring .clearfix::after {
+ clear: both;
+ display: block;
+ content: " ";
+}
+
.mentoring .attempts {
margin-left: 10px;
display: inline-block;
diff --git a/problem_builder/public/js/mentoring_standard_view.js b/problem_builder/public/js/mentoring_standard_view.js
index 7357d5ab..a484cb63 100644
--- a/problem_builder/public/js/mentoring_standard_view.js
+++ b/problem_builder/public/js/mentoring_standard_view.js
@@ -7,6 +7,7 @@ function MentoringStandardView(runtime, element, mentoring) {
function handleSubmitResults(response, disable_submit) {
messagesDOM.empty().hide();
+ var all_have_results = response.results.length > 0;
$.each(response.results || [], function(index, result_spec) {
var input = result_spec[0];
var result = result_spec[1];
@@ -16,6 +17,7 @@ function MentoringStandardView(runtime, element, mentoring) {
num_attempts: response.num_attempts
};
callIfExists(child, 'handleSubmit', result, options);
+ all_have_results = all_have_results && !$.isEmptyObject(result);
});
$('.attempts', element).data('max_attempts', response.max_attempts);
@@ -29,10 +31,10 @@ function MentoringStandardView(runtime, element, mentoring) {
messagesDOM.show();
}
- // this method is called on successful submission and on page load
- // results will be empty only for initial load if no submissions was made
- // in such case we must allow submission to support submitting empty read-only long answer recaps
- if (disable_submit || response.results.length > 0) {
+ // Disable the submit button if we have just submitted new answers,
+ // or if we have just [re]loaded the page and are showing a complete set
+ // of old answers.
+ if (disable_submit || all_have_results) {
submitDOM.attr('disabled', 'disabled');
}
}
diff --git a/problem_builder/public/js/slider.js b/problem_builder/public/js/slider.js
new file mode 100644
index 00000000..ea1d46fe
--- /dev/null
+++ b/problem_builder/public/js/slider.js
@@ -0,0 +1,41 @@
+function SliderBlock(runtime, element) {
+ var $slider = $('.pb-slider-range', element);
+ return {
+ mode: null,
+ mentoring: null,
+
+ value: function() {
+ return parseInt($slider.val());
+ },
+
+ init: function(options) {
+ this.mentoring = options.mentoring;
+ this.mode = options.mode;
+ $slider.on('change', options.onChange);
+ },
+
+ submit: function() {
+ return this.value() / 100.0;
+ },
+
+ handleReview: function(result){
+ $slider.val(result.submission * 100.0);
+ $slider.prop('disabled', true);
+ },
+
+ handleSubmit: function(result) {
+ // Show a green check if the user has submitted a valid value:
+ if (typeof result.submission !== "undefined") {
+ $('.submit-result', element).css('visibility', 'visible');
+ }
+ },
+
+ clearResult: function() {
+ $('.submit-result', element).css('visibility', 'hidden');
+ },
+
+ validate: function(){
+ return Boolean(this.value() >= 0 && this.value() <= 100);
+ }
+ };
+}
diff --git a/problem_builder/questionnaire.py b/problem_builder/questionnaire.py
index 7e846809..301da80f 100644
--- a/problem_builder/questionnaire.py
+++ b/problem_builder/questionnaire.py
@@ -24,7 +24,7 @@
from lazy import lazy
import uuid
from xblock.core import XBlock
-from xblock.fields import Scope, String, Float, UNIQUE_ID
+from xblock.fields import Scope, String
from xblock.fragment import Fragment
from xblock.validation import ValidationMessage
from xblockutils.helpers import child_isinstance
@@ -61,13 +61,6 @@ class QuestionnaireAbstractBlock(
values entered by the student, and supports multiple types of multiple-choice
set, with preset choices and author-defined values.
"""
- name = String(
- # This doesn't need to be a field but is kept for backwards compatibility with v1 student data
- display_name=_("Question ID (name)"),
- help=_("The ID of this question (required). Should be unique within this mentoring component."),
- default=UNIQUE_ID,
- scope=Scope.settings, # Must be scope.settings, or the unique ID will change every time this block is edited
- )
question = String(
display_name=_("Question"),
help=_("Question to ask the student"),
@@ -81,13 +74,6 @@ class QuestionnaireAbstractBlock(
scope=Scope.content,
default=""
)
- weight = Float(
- display_name=_("Weight"),
- help=_("Defines the maximum total grade of this question."),
- default=1,
- scope=Scope.content,
- enforce_type=True
- )
editable_fields = ('question', 'message', 'weight', 'display_name', 'show_title')
has_children = True
answerable = True
diff --git a/problem_builder/slider.py b/problem_builder/slider.py
new file mode 100644
index 00000000..4097d768
--- /dev/null
+++ b/problem_builder/slider.py
@@ -0,0 +1,157 @@
+# -*- 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 .
+#
+
+# Imports ###########################################################
+
+import logging
+import uuid
+
+from xblock.core import XBlock
+from xblock.fields import Scope, String, Float
+from xblock.fragment import Fragment
+from xblockutils.studio_editable import StudioEditableXBlockMixin
+from xblockutils.resources import ResourceLoader
+
+from .mixins import QuestionMixin, XBlockWithTranslationServiceMixin
+from .sub_api import sub_api, SubmittingXBlockMixin
+
+
+# Globals ###########################################################
+
+log = logging.getLogger(__name__)
+loader = ResourceLoader(__name__)
+
+
+# Make '_' a no-op so we can scrape strings
+def _(text):
+ return text
+
+# Classes ###########################################################
+
+
+@XBlock.needs("i18n")
+class SliderBlock(
+ SubmittingXBlockMixin, QuestionMixin, StudioEditableXBlockMixin, XBlockWithTranslationServiceMixin, XBlock,
+):
+ """
+ An XBlock used by students to indicate a numeric value on a sliding scale.
+ The student's answer is always considered "correct".
+ """
+ CATEGORY = 'pb-slider'
+ STUDIO_LABEL = _(u"Ranged Value Slider")
+ answerable = True
+
+ min_label = String(
+ display_name=_("Low"),
+ help=_("Label for low end of the range"),
+ scope=Scope.content,
+ default=_("0%"),
+ )
+ max_label = String(
+ display_name=_("High"),
+ help=_("Label for high end of the range"),
+ scope=Scope.content,
+ default=_("100%"),
+ )
+
+ question = String(
+ display_name=_("Question"),
+ help=_("Question to ask the student (optional)"),
+ scope=Scope.content,
+ default="",
+ multiline_editor=True,
+ )
+
+ student_value = Float(
+ # The value selected by the student
+ default=None,
+ scope=Scope.user_state,
+ )
+
+ editable_fields = ('min_label', 'max_label', 'display_name', 'question', 'show_title')
+
+ def mentoring_view(self, context):
+ """ Main view of this block """
+ context = context.copy() if context else {}
+ context['question'] = self.question
+ context['slider_id'] = 'pb-slider-{}'.format(uuid.uuid4().hex[:20])
+ context['initial_value'] = int(self.student_value*100) if self.student_value is not None else 50
+ context['min_label'] = self.min_label
+ context['max_label'] = self.max_label
+ context['title'] = self.display_name_with_default
+ context['hide_header'] = context.get('hide_header', False) or not self.show_title
+ context['instructions_string'] = self._("Select a value from {min_label} to {max_label}").format(
+ min_label=self.min_label, max_label=self.max_label
+ )
+ html = loader.render_template('templates/html/slider.html', context)
+
+ fragment = Fragment(html)
+ fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/slider.js'))
+ fragment.initialize_js('SliderBlock')
+ return fragment
+
+ student_view = mentoring_view
+ preview_view = mentoring_view
+
+ def author_view(self, context):
+ """
+ Add some HTML to the author view that allows authors to see the ID of the block, so they
+ can refer to it in other blocks such as Plot blocks.
+ """
+ context['hide_header'] = True # Header is already shown in the Studio wrapper
+ fragment = self.student_view(context)
+ fragment.add_content(loader.render_template('templates/html/slider_edit_footer.html', {
+ "url_name": self.url_name
+ }))
+ return fragment
+
+ def get_last_result(self):
+ """ Return the current/last result in the required format """
+ if self.student_value is None:
+ return {}
+ return {
+ 'submission': self.student_value,
+ 'status': 'correct',
+ 'tips': [],
+ 'weight': self.weight,
+ 'score': 1,
+ }
+
+ def get_results(self, _previous_result_unused=None):
+ """ Alias for get_last_result() """
+ return self.get_last_result()
+
+ def submit(self, value):
+ log.debug(u'Received Slider submission: "%s"', value)
+ if value < 0 or value > 1:
+ return {} # Invalid
+ self.student_value = value
+ if sub_api:
+ # Also send to the submissions API:
+ sub_api.create_submission(self.student_item_key, {'value': value})
+ result = self.get_last_result()
+ log.debug(u'Slider submission result: %s', result)
+ return result
+
+ def validate_field_data(self, validation, data):
+ """
+ Validate this block's field data.
+ """
+ super(SliderBlock, self).validate_field_data(validation, data)
diff --git a/problem_builder/step.py b/problem_builder/step.py
index 2386b9d0..9372f63b 100644
--- a/problem_builder/step.py
+++ b/problem_builder/step.py
@@ -38,6 +38,7 @@
from problem_builder.mixins import EnumerableChildMixin, MessageParentMixin, StepParentMixin
from problem_builder.mrq import MRQBlock
from problem_builder.plot import PlotBlock
+from problem_builder.slider import SliderBlock
from problem_builder.table import MentoringTableBlock
@@ -147,7 +148,7 @@ def allowed_nested_blocks(self):
return [
NestedXBlockSpec(AnswerBlock, boilerplate='studio_default'),
MCQBlock, RatingBlock, MRQBlock, HtmlBlockShim,
- AnswerRecapBlock, MentoringTableBlock, PlotBlock
+ AnswerRecapBlock, MentoringTableBlock, PlotBlock, SliderBlock
] + additional_blocks
@property
diff --git a/problem_builder/templates/html/mentoring_add_buttons.html b/problem_builder/templates/html/mentoring_add_buttons.html
index da36bf18..d03c7452 100644
--- a/problem_builder/templates/html/mentoring_add_buttons.html
+++ b/problem_builder/templates/html/mentoring_add_buttons.html
@@ -8,6 +8,7 @@
diff --git a/problem_builder/templates/html/slider.html b/problem_builder/templates/html/slider.html
new file mode 100644
index 00000000..09bb7c55
--- /dev/null
+++ b/problem_builder/templates/html/slider.html
@@ -0,0 +1,17 @@
+
+ {% if not hide_header %}
{{ title }}
{% endif %}
+ {% if question %}
+
+ {% endif %}
+
+
+
{{ min_label }}
+
{{ max_label }}
+
+
+
+
+
diff --git a/problem_builder/templates/html/slider_edit_footer.html b/problem_builder/templates/html/slider_edit_footer.html
new file mode 100644
index 00000000..1bb013e0
--- /dev/null
+++ b/problem_builder/templates/html/slider_edit_footer.html
@@ -0,0 +1,5 @@
+{% load i18n %}
+
diff --git a/problem_builder/tests/integration/test_slider.py b/problem_builder/tests/integration/test_slider.py
new file mode 100644
index 00000000..148026d6
--- /dev/null
+++ b/problem_builder/tests/integration/test_slider.py
@@ -0,0 +1,146 @@
+# -*- 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 .
+#
+
+# Imports ###########################################################
+
+from .base_test import ProblemBuilderBaseTest, MentoringAssessmentBaseTest, GetChoices
+
+
+# Classes ###########################################################
+
+
+class SliderBlockTestMixins(object):
+ """ Mixins for testing slider blocks. Assumes only one slider block is on the page. """
+
+ def get_slider_value(self):
+ return int(self.browser.execute_script("return $('.pb-slider-range').val()"))
+
+ def set_slider_value(self, val):
+ self.browser.execute_script("$('.pb-slider-range').val(arguments[0]).change()", val)
+
+
+class SliderBlockTest(SliderBlockTestMixins, ProblemBuilderBaseTest):
+ """
+ Tests for the SliderBlock inside a normal Problem Builder block.
+ """
+ def test_simple_flow(self):
+ """ Test a regular Problem Builder block containing one slider """
+ pb_wrapper = self.load_scenario("slider_problem.xml", {"include_mcq": False})
+ self.wait_for_init()
+ # The initial value should be 50 and submit should be enabled since 50 is a valid value:
+ self.assertTrue(self.submit_button.is_enabled())
+ self.assertEqual(self.get_slider_value(), 50)
+ self.expect_checkmark_visible(False)
+ # Set the value to 75:
+ self.set_slider_value(75)
+ self.assertEqual(self.get_slider_value(), 75)
+ self.click_submit(pb_wrapper)
+ # Now, we expect submit to be disabled and the checkmark to be visible:
+ self.expect_checkmark_visible(True)
+ self.assertFalse(self.submit_button.is_enabled())
+ # Now change the value, and the button/checkmark should reset:
+ self.set_slider_value(45)
+ self.assertTrue(self.submit_button.is_enabled())
+ self.expect_checkmark_visible(False)
+ # Now reload the page:
+ self.browser.execute_script("$(document).html(' ');")
+ pb_wrapper = self.go_to_view("student_view")
+ self.wait_for_init()
+ # Now the initial value should be 75 and submit should be disabled (to discourage submitting the same answer):
+ self.assertEqual(self.get_slider_value(), 75)
+ self.assertFalse(self.submit_button.is_enabled())
+ self.expect_checkmark_visible(True)
+
+ def test_simple_flow_with_peer(self):
+ """ Test a regular Problem Builder block containing one slider and an MCQ """
+ pb_wrapper = self.load_scenario("slider_problem.xml", {"include_mcq": True})
+ self.wait_for_init()
+ # The initial value should be 50 and submit should be disabled until an MCQ choice is selected
+ self.assertEqual(self.get_slider_value(), 50)
+ self.assertFalse(self.submit_button.is_enabled())
+ self.expect_checkmark_visible(False)
+ # Set the value to 15:
+ self.set_slider_value(15)
+ self.assertEqual(self.get_slider_value(), 15)
+ self.assertFalse(self.submit_button.is_enabled())
+ # Choose a choice:
+ GetChoices(pb_wrapper).select('Yes')
+ self.assertTrue(self.submit_button.is_enabled())
+ self.click_submit(pb_wrapper)
+ # Now, we expect submit to be disabled and the checkmark to be visible:
+ self.expect_checkmark_visible(True)
+ self.assertFalse(self.submit_button.is_enabled())
+ # Now change the value, and the button/checkmark should reset:
+ self.set_slider_value(20)
+ self.assertTrue(self.submit_button.is_enabled())
+ self.expect_checkmark_visible(False)
+
+ def wait_for_init(self):
+ """ Wait for the scenario to initialize """
+ self.wait_until_hidden(self.browser.find_element_by_css_selector('.messages'))
+
+ @property
+ def submit_button(self):
+ return self.browser.find_element_by_css_selector('.submit input.input-main')
+
+ def expect_checkmark_visible(self, visible):
+ checkmark = self.browser.find_element_by_css_selector('.xblock-pb-slider .submit-result')
+ self.assertEqual(checkmark.is_displayed(), visible)
+
+
+class SliderStepBlockTest(SliderBlockTestMixins, MentoringAssessmentBaseTest):
+ """
+ Tests for the SliderBlock inside a Step Builder block.
+ """
+
+ def test_step_with_slider(self):
+ """ Test a regular Step Builder block containing one slider and an MCQ """
+ step_builder, controls = self.load_assessment_scenario("slider_step.xml")
+ self.wait_for_init()
+ self.assertEqual(self.get_slider_value(), 50)
+
+ # Check step 1 (the slider step):
+ question = self.expect_question_visible(1, step_builder, question_text="Information Reliability")
+ self.assertIn("How reliable is this information?", question.text)
+ self.assertIn("Select a value from 0% to 100%", question.text) # Screen reader explanation
+ self.assertTrue(controls.submit.is_enabled())
+ self.assert_hidden(controls.try_again)
+
+ self.set_slider_value(0)
+ controls.submit.click()
+ self.do_submit_wait(controls, last=False)
+ self.wait_until_clickable(controls.next_question)
+ controls.next_question.click()
+
+ # Submit step 2:
+ question = self.expect_question_visible(2, step_builder)
+ GetChoices(question).select("Yes")
+ controls.submit.click()
+ self.do_submit_wait(controls, last=True)
+ self.wait_until_clickable(controls.review)
+
+ controls.review.click()
+ self.wait_until_visible(controls.try_again)
+ # You can't get a slider question wrong, but it does count as one correct point by default:
+ self.assertIn("You answered 2 questions correctly", step_builder.text)
+
+ def wait_for_init(self):
+ """ Wait for the scenario to initialize """
+ self.wait_until_hidden(self.browser.find_element_by_css_selector('.assessment-review-tips'))
diff --git a/problem_builder/tests/integration/xml_templates/slider_problem.xml b/problem_builder/tests/integration/xml_templates/slider_problem.xml
new file mode 100644
index 00000000..cddf98a2
--- /dev/null
+++ b/problem_builder/tests/integration/xml_templates/slider_problem.xml
@@ -0,0 +1,12 @@
+
+
+
+ {% if include_mcq %}
+
+ Yes
+ Maybe not
+ I don't understand
+
+ {% endif %}
+
+
diff --git a/problem_builder/tests/integration/xml_templates/slider_step.xml b/problem_builder/tests/integration/xml_templates/slider_step.xml
new file mode 100644
index 00000000..00c1f4b7
--- /dev/null
+++ b/problem_builder/tests/integration/xml_templates/slider_step.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+ Yes
+ No
+
+
+
+
+
+
diff --git a/problem_builder/tests/unit/test_step.py b/problem_builder/tests/unit/test_step.py
index 94e7637f..eaab18b5 100644
--- a/problem_builder/tests/unit/test_step.py
+++ b/problem_builder/tests/unit/test_step.py
@@ -108,7 +108,17 @@ def test_allowed_nested_blocks(self):
block = MentoringStepBlock(Mock(), DictFieldData({}), Mock())
self.assertEqual(
self.get_allowed_blocks(block),
- ['pb-answer', 'pb-mcq', 'pb-rating', 'pb-mrq', 'html', 'pb-answer-recap', 'pb-table', 'sb-plot']
+ [
+ 'pb-answer',
+ 'pb-mcq',
+ 'pb-rating',
+ 'pb-mrq',
+ 'html',
+ 'pb-answer-recap',
+ 'pb-table',
+ 'sb-plot',
+ 'pb-slider',
+ ]
)
from sys import modules
xmodule_mock = Mock()
@@ -121,7 +131,16 @@ def test_allowed_nested_blocks(self):
with patch.dict(modules, fake_modules):
self.assertEqual(
self.get_allowed_blocks(block), [
- 'pb-answer', 'pb-mcq', 'pb-rating', 'pb-mrq', 'html', 'pb-answer-recap',
- 'pb-table', 'sb-plot', 'video', 'imagemodal'
+ 'pb-answer',
+ 'pb-mcq',
+ 'pb-rating',
+ 'pb-mrq',
+ 'html',
+ 'pb-answer-recap',
+ 'pb-table',
+ 'sb-plot',
+ 'pb-slider',
+ 'video',
+ 'imagemodal',
]
)
diff --git a/setup.py b/setup.py
index 61e1534c..80fd6be8 100644
--- a/setup.py
+++ b/setup.py
@@ -54,6 +54,7 @@ def package_data(pkg, root_list):
'pb-mcq = problem_builder.mcq:MCQBlock',
'pb-rating = problem_builder.mcq:RatingBlock',
'pb-mrq = problem_builder.mrq:MRQBlock',
+ 'pb-slider = problem_builder.slider:SliderBlock',
'pb-message = problem_builder.message:MentoringMessageBlock',
'pb-tip = problem_builder.tip:TipBlock',
'pb-choice = problem_builder.choice:ChoiceBlock',