diff --git a/lms/templates/poll.html b/lms/templates/poll.html deleted file mode 100644 index 8a8c6dc1ca23..000000000000 --- a/lms/templates/poll.html +++ /dev/null @@ -1,8 +0,0 @@ -
- - -
\ No newline at end of file diff --git a/openedx/envs/common.py b/openedx/envs/common.py index 31b0015d34d9..56afc21a1d50 100644 --- a/openedx/envs/common.py +++ b/openedx/envs/common.py @@ -2077,14 +2077,6 @@ def add_optional_apps(optional_apps, installed_apps): # .. toggle_target_removal_date: 2026-04-10 USE_EXTRACTED_ANNOTATABLE_BLOCK = True -# .. toggle_name: USE_EXTRACTED_POLL_QUESTION_BLOCK -# .. toggle_default: True -# .. toggle_implementation: DjangoSetting -# .. toggle_description: Enables the use of the extracted poll question XBlock, which has been shifted to the 'openedx/xblocks-contrib' repo. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2024-11-10 -# .. toggle_target_removal_date: 2026-04-10 -USE_EXTRACTED_POLL_QUESTION_BLOCK = True # .. toggle_name: USE_EXTRACTED_LTI_BLOCK # .. toggle_default: True diff --git a/pyproject.toml b/pyproject.toml index 44488938ec95..1b4ed2d7685a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ image = "xmodule.template_block:TranslateCustomTagBlock" library = "xmodule.library_root_xblock:LibraryRoot" library_content = "xmodule.library_content_block:LegacyLibraryContentBlock" lti = "xmodule.lti_block:LTIBlock" -poll_question = "xmodule.poll_block:PollBlock" +poll_question = "xblocks_contrib:PollBlock" problem = "xmodule.capa_block:ProblemBlock" randomize = "xmodule.randomize_block:RandomizeBlock" sequential = "xmodule.seq_block:SequenceBlock" diff --git a/webpack.builtinblocks.config.js b/webpack.builtinblocks.config.js index 20d8790fc5b5..d5964343df90 100644 --- a/webpack.builtinblocks.config.js +++ b/webpack.builtinblocks.config.js @@ -50,13 +50,6 @@ module.exports = { './xmodule/js/src/xmodule.js', './xmodule/js/src/raw/edit/metadata-only.js' ], - PollBlockDisplay: [ - './xmodule/js/src/xmodule.js', - './xmodule/js/src/javascript_loader.js', - './xmodule/js/src/poll/poll.js', - './xmodule/js/src/poll/poll_main.js' - ], - PollBlockEditor: './xmodule/js/src/xmodule.js', ProblemBlockDisplay: [ './xmodule/js/src/xmodule.js', './xmodule/js/src/javascript_loader.js', diff --git a/webpack.common.config.js b/webpack.common.config.js index cd9d0f53af59..b2b9571dea3a 100644 --- a/webpack.common.config.js +++ b/webpack.common.config.js @@ -421,24 +421,7 @@ module.exports = Merge.merge({ } ] }, - { - test: /xmodule\/js\/src\/poll\/poll.js/, - use: [ - { - loader: 'imports-loader', - options: 'this=>window' - } - ] - }, - { - test: /xmodule\/js\/src\/poll\/poll_main.js/, - use: [ - { - loader: 'imports-loader', - options: 'this=>window' - } - ] - }, + { test: /xmodule\/js\/src\/problem\/edit.js/, use: [ diff --git a/xmodule/js/src/poll/.gitignore b/xmodule/js/src/poll/.gitignore deleted file mode 100644 index d4aa116a26c7..000000000000 --- a/xmodule/js/src/poll/.gitignore +++ /dev/null @@ -1 +0,0 @@ -!*.js diff --git a/xmodule/js/src/poll/poll.js b/xmodule/js/src/poll/poll.js deleted file mode 100644 index 018dbaf708a2..000000000000 --- a/xmodule/js/src/poll/poll.js +++ /dev/null @@ -1,11 +0,0 @@ -define(['poll/poll_main.js'], function(PollMain) { - 'use strict'; - - function Poll(el) { - return new PollMain(el); - } - - window.Poll = Poll; - - return Poll; -}); diff --git a/xmodule/js/src/poll/poll_main.js b/xmodule/js/src/poll/poll_main.js deleted file mode 100644 index 49eb10446a53..000000000000 --- a/xmodule/js/src/poll/poll_main.js +++ /dev/null @@ -1,326 +0,0 @@ -(function(requirejs, require, define) { - define('PollMain', ['edx-ui-toolkit/js/utils/html-utils'], function(HtmlUtils) { - PollMain.prototype = { - - showAnswerGraph: function(poll_answers, total) { - var _this, totalValue; - - totalValue = parseFloat(total); - if (isFinite(totalValue) === false) { - return; - } - - _this = this; - - $.each(poll_answers, function(index, value) { - var numValue, percentValue; - - numValue = parseFloat(value); - if (isFinite(numValue) === false) { - return; - } - - percentValue = (numValue / totalValue) * 100.0; - - _this.answersObj[index].statsEl.show(); - // eslint-disable-next-line max-len - _this.answersObj[index].numberEl.html(HtmlUtils.HTML('' + value + ' (' + percentValue.toFixed(1) + '%)').toString()); - _this.answersObj[index].percentEl.css({ - width: '' + percentValue.toFixed(1) + '%' - }); - }); - }, - - submitAnswer: function(answer, answerObj) { - var _this; - - // Make sure that the user can answer a question only once. - if (this.questionAnswered === true) { - return; - } - this.questionAnswered = true; - - _this = this; - - console.log('submit answer'); - - answerObj.buttonEl.addClass('answered'); - - // Send the data to the server as an AJAX request. Attach a callback that will - // be fired on server's response. - $.postWithPrefix( - _this.ajax_url + '/' + answer, {}, - function(response) { - console.log('success! response = '); - console.log(response); - - _this.showAnswerGraph(response.poll_answers, response.total); - - if (_this.canReset === true) { - _this.resetButton.show(); - } - - // Initialize Conditional constructors. - if (_this.wrapperSectionEl !== null) { - $(_this.wrapperSectionEl).find('.xmodule_ConditionalModule').each(function(index, value) { - // eslint-disable-next-line no-new - new window.Conditional(value, _this.runtime, _this.id.replace(/^poll_/, '')); - }); - } - } - ); - }, // End-of: 'submitAnswer': function (answer, answerEl) { - - submitReset: function() { - var _this; - - _this = this; - - console.log('submit reset'); - - // Send the data to the server as an AJAX request. Attach a callback that will - // be fired on server's response. - $.postWithPrefix( - // eslint-disable-next-line no-useless-concat - this.ajax_url + '/' + 'reset_poll', - {}, - function(response) { - console.log('success! response = '); - console.log(response); - - if ( - (response.hasOwnProperty('status') !== true) - || (typeof response.status !== 'string') - || (response.status.toLowerCase() !== 'success')) { - return; - } - - _this.questionAnswered = false; - _this.questionEl.find('.button.answered').removeClass('answered'); - _this.questionEl.find('.stats').hide(); - _this.resetButton.hide(); - - // Initialize Conditional constructors. We will specify the third parameter as 'true' - // notifying the constructor that this is a reset operation. - if (_this.wrapperSectionEl !== null) { - $(_this.wrapperSectionEl).find('.xmodule_ConditionalModule').each(function(index, value) { - // eslint-disable-next-line no-new - new window.Conditional(value, _this.runtime, _this.id.replace(/^poll_/, '')); - }); - } - } - ); - }, // End-of: 'submitAnswer': function (answer, answerEl) { - - postInit: function() { - var _this; - - // Access this object inside inner functions. - _this = this; - - if ( - (this.jsonConfig.poll_answer.length > 0) - && (this.jsonConfig.answers.hasOwnProperty(this.jsonConfig.poll_answer) === false) - ) { - HtmlUtils.append(this.questionEl, HtmlUtils.joinHtml( - HtmlUtils.HTML('

Error!

'), - HtmlUtils.HTML( - '

XML data format changed. List of answers was modified, but poll data was not updated.

' - ) - )); - - return; - } - - // Get the DOM id of the question. - this.id = this.questionEl.attr('id'); - - // Get the URL to which we will post the users answer to the question. - this.ajax_url = this.questionEl.data('ajax-url'); - - this.questionHtmlMarkup = $('
').html(HtmlUtils.HTML(this.jsonConfig.question).toString()).text(); - this.questionEl.append(HtmlUtils.HTML(this.questionHtmlMarkup).toString()); - - // When the user selects and answer, we will set this flag to true. - this.questionAnswered = false; - - this.answersObj = {}; - this.shortVersion = true; - - $.each(this.jsonConfig.answers, function(index, value) { - if (value.length >= 18) { - _this.shortVersion = false; - } - }); - - $.each(this.jsonConfig.answers, function(index, value) { - var answer; - - answer = {}; - - _this.answersObj[index] = answer; - - answer.el = $('
'); - - answer.questionEl = $('
'); - answer.buttonEl = $('
'); - answer.textEl = $('
'); - answer.questionEl.append(HtmlUtils.HTML(answer.buttonEl).toString()); - answer.questionEl.append(HtmlUtils.HTML(answer.textEl).toString()); - - answer.el.append(HtmlUtils.HTML(answer.questionEl).toString()); - - answer.statsEl = $('
'); - answer.barEl = $('
'); - answer.percentEl = $('
'); - answer.barEl.append(HtmlUtils.HTML(answer.percentEl).toString()); - answer.numberEl = $('
'); - answer.statsEl.append(HtmlUtils.HTML(answer.barEl).toString()); - answer.statsEl.append(HtmlUtils.HTML(answer.numberEl).toString()); - - answer.statsEl.hide(); - - answer.el.append(HtmlUtils.HTML(answer.statsEl).toString()); - - answer.textEl.html(HtmlUtils.HTML(value).toString()); - - if (_this.shortVersion === true) { - // eslint-disable-next-line no-shadow - $.each(answer, function(index, value) { - if (value instanceof jQuery) { - value.addClass('short'); - } - }); - } - - answer.el.appendTo(_this.questionEl); - - answer.textEl.on('click', function() { - _this.submitAnswer(index, answer); - }); - - answer.buttonEl.on('click', function() { - _this.submitAnswer(index, answer); - }); - - if (index === _this.jsonConfig.poll_answer) { - answer.buttonEl.addClass('answered'); - _this.questionAnswered = true; - } - }); - - console.log(this.jsonConfig.reset); - - if ((typeof this.jsonConfig.reset === 'string') && (this.jsonConfig.reset.toLowerCase() === 'true')) { - this.canReset = true; - - this.resetButton = $('
Change your vote
'); - - if (this.questionAnswered === false) { - this.resetButton.hide(); - } - - HtmlUtils.append(this.questionEl, this.resetButton); - this.resetButton.on('click', function() { - _this.submitReset(); - }); - } else { - this.canReset = false; - } - - // If it turns out that the user already answered the question, show the answers graph. - if (this.questionAnswered === true) { - this.showAnswerGraph(this.jsonConfig.poll_answers, this.jsonConfig.total); - } - } // End-of: 'postInit': function () { - }; // End-of: PollMain.prototype = { - - return PollMain; - - function PollMain(el, runtime) { - var _this; - - this.runtime = runtime; - this.questionEl = $(el).find('.poll_question'); - if (this.questionEl.length !== 1) { - // We require one question DOM element. - console.log('ERROR: PollMain constructor requires one question DOM element.'); - - return; - } - - // Just a safety precussion. If we run this code more than once, multiple 'click' callback handlers will be - // attached to the same DOM elements. We don't want this to happen. - if (this.questionEl.attr('poll_main_processed') === 'true') { - console.log( - 'ERROR: PolMain JS constructor was called on a DOM element that has already been processed once.' - ); - - return; - } - - // This element was not processed earlier. - // Make sure that next time we will not process this element a second time. - this.questionEl.attr('poll_main_processed', 'true'); - - // Access this object inside inner functions. - _this = this; - - // DOM element which contains the current poll along with any conditionals. By default we assume that such - // element is not present. We will try to find it. - this.wrapperSectionEl = null; - - (function(tempEl, c1) { - while (tempEl.tagName.toLowerCase() !== 'body') { - tempEl = $(tempEl).parent()[0]; - c1 += 1; - - if ( - (tempEl.tagName.toLowerCase() === 'div') - && ($(tempEl).data('block-type') === 'wrapper') - ) { - _this.wrapperSectionEl = tempEl; - - break; - } else if (c1 > 50) { - // In case something breaks, and we enter an endless loop, a sane - // limit for loop iterations. - - break; - } - } - }($(el)[0], 0)); - - try { - this.jsonConfig = JSON.parse(this.questionEl.children('.poll_question_div').html()); - - $.postWithPrefix( - // eslint-disable-next-line no-useless-concat - '' + this.questionEl.data('ajax-url') + '/' + 'get_state', {}, - function(response) { - _this.jsonConfig.poll_answer = response.poll_answer; - _this.jsonConfig.total = response.total; - - $.each(response.poll_answers, function(index, value) { - _this.jsonConfig.poll_answers[index] = value; - }); - - // xss-lint: disable=javascript-jquery-html - _this.questionEl.children('.poll_question_div').html(JSON.stringify(_this.jsonConfig)); - - _this.postInit(); - } - ); - - return; - } catch (err) { - console.log( - 'ERROR: Invalid JSON config for poll ID "' + this.id + '".', - 'Error messsage: "' + err.message + '".' - ); - } - } // End-of: function PollMain(el) { - }); // End-of: define('PollMain', [], function () { - -// End-of: (function (requirejs, require, define) { -}(RequireJS.requirejs, RequireJS.require, RequireJS.define)); diff --git a/xmodule/poll_block.py b/xmodule/poll_block.py deleted file mode 100644 index 8a25fd476fe4..000000000000 --- a/xmodule/poll_block.py +++ /dev/null @@ -1,278 +0,0 @@ -"""Poll block is ungraded xmodule used by students to -to do set of polls. - -On the client side we show: -If student does not yet anwered - Question with set of choices. -If student have answered - Question with statistics for each answers. -""" - -import html -import json -import logging -import warnings -from collections import OrderedDict -from copy import deepcopy - -from django.conf import settings -from lxml import etree -from web_fragments.fragment import Fragment -from xblock.core import XBlock -from xblock.fields import Boolean, Dict, List, Scope, String # pylint: disable=wrong-import-order -from xblocks_contrib.poll import PollBlock as _ExtractedPollBlock - -from openedx.core.djangolib.markup import HTML, Text -from xmodule.mako_block import MakoTemplateBlockBase -from xmodule.stringify import stringify_children -from xmodule.util.builtin_assets import add_css_to_fragment, add_webpack_js_to_fragment -from xmodule.x_module import ResourceTemplates, XModuleMixin, XModuleToXBlockMixin, shim_xmodule_js -from xmodule.xml_block import XmlMixin - -log = logging.getLogger(__name__) -_ = lambda text: text - - -@XBlock.needs('mako') -class _BuiltInPollBlock( - MakoTemplateBlockBase, - XmlMixin, - XModuleToXBlockMixin, - ResourceTemplates, - XModuleMixin, -): # pylint: disable=abstract-method - """ - Poll Block. - - .. deprecated:: 2026-03 - This built-in poll block is deprecated. Please use the extracted ``PollBlock`` - from ``xblocks_contrib.poll`` instead. - """ - - is_extracted = False - - # Name of poll to use in links to this poll - display_name = String( - help=_("The display name for this component."), - scope=Scope.settings - ) - - voted = Boolean( - help=_("Whether this student has voted on the poll"), - scope=Scope.user_state, - default=False - ) - poll_answer = String( - help=_("Student answer"), - scope=Scope.user_state, - default='' - ) - poll_answers = Dict( - help=_("Poll answers from all students"), - scope=Scope.user_state_summary - ) - - # List of answers, in the form {'id': 'some id', 'text': 'the answer text'} - answers = List( - help=_("Poll answers from xml"), - scope=Scope.content, - default=[] - ) - - question = String( - help=_("Poll question"), - scope=Scope.content, - default='' - ) - - resources_dir = None - uses_xmodule_styles_setup = True - - def handle_ajax(self, dispatch, data): # pylint: disable=unused-argument - """Ajax handler. - - Args: - dispatch: string request slug - data: dict request data parameters - - Returns: - json string - """ - if dispatch in self.poll_answers and not self.voted: - # FIXME: fix this, when xblock will support mutable types. - # Now we use this hack. - temp_poll_answers = self.poll_answers - temp_poll_answers[dispatch] += 1 - self.poll_answers = temp_poll_answers - - self.voted = True - self.poll_answer = dispatch - return json.dumps({'poll_answers': self.poll_answers, - 'total': sum(self.poll_answers.values()), - 'callback': {'objectName': 'Conditional'} - }) - elif dispatch == 'get_state': - return json.dumps({'poll_answer': self.poll_answer, - 'poll_answers': self.poll_answers, - 'total': sum(self.poll_answers.values()) - }) - elif dispatch == 'reset_poll' and self.voted and \ - self.xml_attributes.get('reset', 'True').lower() != 'false': - self.voted = False - - # FIXME: fix this, when xblock will support mutable types. - # Now we use this hack. - temp_poll_answers = self.poll_answers - temp_poll_answers[self.poll_answer] -= 1 - self.poll_answers = temp_poll_answers - - self.poll_answer = '' - return json.dumps({'status': 'success'}) - else: # return error message - return json.dumps({'error': 'Unknown Command!'}) - - def student_view(self, _context): - """ - Renders the student view. - """ - fragment = Fragment() - params = { - 'element_id': self.location.html_id(), - 'element_class': self.location.block_type, - 'ajax_url': self.ajax_url, - 'configuration_json': self.dump_poll(), - } - fragment.add_content(self.runtime.service(self, 'mako').render_lms_template('poll.html', params)) - add_css_to_fragment(fragment, 'PollBlockDisplay.css') - add_webpack_js_to_fragment(fragment, 'PollBlockDisplay') - shim_xmodule_js(fragment, 'Poll') - return fragment - - def dump_poll(self): - """Dump poll information. - - Returns: - string - Serialize json. - """ - # FIXME: hack for resolving caching `default={}` during definition - # poll_answers field - if self.poll_answers is None: - self.poll_answers = {} - - answers_to_json = OrderedDict() - - # FIXME: fix this, when xblock support mutable types. - # Now we use this hack. - temp_poll_answers = self.poll_answers - - # Fill self.poll_answers, prepare data for template context. - for answer in self.answers: - # Set default count for answer = 0. - if answer['id'] not in temp_poll_answers: - temp_poll_answers[answer['id']] = 0 - answers_to_json[answer['id']] = html.escape(answer['text'], quote=False) - self.poll_answers = temp_poll_answers - - return json.dumps({ - 'answers': answers_to_json, - 'question': html.escape(self.question, quote=False), - # to show answered poll after reload: - 'poll_answer': self.poll_answer, - 'poll_answers': self.poll_answers if self.voted else {}, - 'total': sum(self.poll_answers.values()) if self.voted else 0, - 'reset': str(self.xml_attributes.get('reset', 'true')).lower() - }) - - _tag_name = 'poll_question' - _child_tag_name = 'answer' - - @classmethod - def definition_from_xml(cls, xml_object, system): - """Pull out the data into dictionary. - - Args: - xml_object: xml from file. - system: `system` object. - - Returns: - (definition, children) - tuple - definition - dict: - { - 'answers': , - 'question': - } - """ - # Check for presense of required tags in xml. - if len(xml_object.xpath(cls._child_tag_name)) == 0: - raise ValueError("Poll_question definition must include \ - at least one 'answer' tag") - - xml_object_copy = deepcopy(xml_object) - answers = [] - for element_answer in xml_object_copy.findall(cls._child_tag_name): - answer_id = element_answer.get('id', None) - if answer_id: - answers.append({ - 'id': answer_id, - 'text': stringify_children(element_answer) - }) - xml_object_copy.remove(element_answer) - - definition = { - 'answers': answers, - 'question': stringify_children(xml_object_copy) - } - children = [] - - return (definition, children) - - def definition_to_xml(self, resource_fs): - """Return an xml element representing to this definition.""" - - poll_str = HTML('<{tag_name}>{text}').format( - tag_name=self._tag_name, text=self.question) - xml_object = etree.fromstring(poll_str) - xml_object.set('display_name', self.display_name) - - def add_child(xml_obj, answer): # pylint: disable=unused-argument - # Escape answer text before adding to xml tree. - answer_text = str(answer['text']) - child_str = Text('{tag_begin}{text}{tag_end}').format( - tag_begin=HTML('<{tag_name} id="{id}">').format( - tag_name=self._child_tag_name, - id=answer['id'] - ), - text=answer_text, - tag_end=HTML('').format(tag_name=self._child_tag_name) - ) - child_node = etree.fromstring(child_str) - xml_object.append(child_node) - - for answer in self.answers: - add_child(xml_object, answer) - return xml_object - - -PollBlock = None - - -def reset_class(): - """Reset class as per django settings flag""" - global PollBlock - PollBlock = ( - _ExtractedPollBlock if settings.USE_EXTRACTED_POLL_QUESTION_BLOCK - else _BuiltInPollBlock - ) - return PollBlock - -reset_class() -PollBlock.__name__ = "PollBlock" - -if not settings.USE_EXTRACTED_POLL_QUESTION_BLOCK: - warnings.warn( - "The built-in `xmodule.poll_block` PollBlock implementation is deprecated. " - "To fix this warning, enable `USE_EXTRACTED_POLL_QUESTION_BLOCK` (set it to True) to use " - "`xblocks_contrib.poll.PollBlock` instead. " - "Support for the built-in implementation, and the `USE_EXTRACTED_POLL_QUESTION_BLOCK` setting, " - "will be removed in Willow.", - DeprecationWarning, - stacklevel=2, - ) diff --git a/xmodule/static/css-builtin-blocks/PollBlockDisplay.css b/xmodule/static/css-builtin-blocks/PollBlockDisplay.css deleted file mode 100644 index 0615ecf123d5..000000000000 --- a/xmodule/static/css-builtin-blocks/PollBlockDisplay.css +++ /dev/null @@ -1,217 +0,0 @@ -@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,400,400i,600,700"); - -@media print { - .xmodule_display.xmodule_PollBlock div.poll_question { - display: block; - width: auto; - padding: 0; - } - - .xmodule_display.xmodule_PollBlock div.poll_question canvas, - .xmodule_display.xmodule_PollBlock div.poll_question img { - page-break-inside: avoid; - } -} - -.xmodule_display.xmodule_PollBlock div.poll_question .inline { - display: inline; -} - -.xmodule_display.xmodule_PollBlock div.poll_question h3 { - margin-top: 0; - margin-bottom: calc((var(--baseline, 20px) * 0.75)); - color: #fe57a1; - font-size: 1.9em; -} - -.xmodule_display.xmodule_PollBlock div.poll_question h3.problem-header div.staff { - margin-top: calc((var(--baseline, 20px) * 1.5)); - font-size: 80%; -} - -@media print { - .xmodule_display.xmodule_PollBlock div.poll_question h3 { - display: block; - width: auto; - border-right: 0; - } -} - -.xmodule_display.xmodule_PollBlock div.poll_question p { - text-align: justify; - font-weight: bold; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer { - margin-bottom: var(--baseline, 20px); -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer.short { - clear: both; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .question { - height: auto; - clear: both; - min-height: 30px; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .question.short { - clear: none; - width: 30%; - display: inline; - float: left; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .question .button { - -webkit-appearance: none; - -webkit-background-clip: padding-box; - -webkit-border-image: none; - -webkit-box-align: center; - -webkit-box-shadow: white 0px 1px 0px 0px inset; - -webkit-font-smoothing: antialiased; - -webkit-rtl-ordering: logical; - -webkit-user-select: text; - -webkit-writing-mode: horizontal-tb; - background-clip: padding-box; - background-color: #eeeeee; - background-image: -webkit-linear-gradient(top, #eeeeee, #d2d2d2); - border-bottom-color: #cacaca; - border-bottom-left-radius: 3px; - border-bottom-right-radius: 3px; - border-bottom-style: solid; - border-bottom-width: 1px; - border-left-color: #cacaca; - border-left-style: solid; - border-left-width: 1px; - border-right-color: #cacaca; - border-right-style: solid; - border-right-width: 1px; - border-top-color: #cacaca; - border-top-left-radius: 3px; - border-top-right-radius: 3px; - border-top-style: solid; - border-top-width: 1px; - box-shadow: white 0px 1px 0px 0px inset; - box-sizing: border-box; - color: #333333; - /* display: inline-block; */ - display: inline; - float: left; - font-family: 'Open Sans', Verdana, Geneva, sans-serif; - font-size: 13px; - font-style: normal; - font-variant: normal; - font-weight: bold; - letter-spacing: normal; - line-height: 25.59375px; - margin-bottom: calc((var(--baseline, 20px) * 0.75)); - margin: 0; - padding: 0px; - text-align: center; - text-decoration: none; - text-indent: 0px; - text-shadow: #f8f8f8 0px 1px 0px; - text-transform: none; - vertical-align: top; - white-space: pre-line; - width: 25px; - height: 25px; - word-spacing: 0px; - writing-mode: lr-tb; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .question .button.answered { - -webkit-box-shadow: #61b8e1 0px 1px 0px 0px inset; - background-color: #1d9dd9; - background-image: -webkit-linear-gradient(top, #1d9dd9, #0e7cb0); - border-bottom-color: #0d72a2; - border-left-color: #0d72a2; - border-right-color: #0d72a2; - border-top-color: #0d72a2; - box-shadow: #61b8e1 0px 1px 0px 0px inset; - color: white; - text-shadow: #076794 0px 1px 0px; - background-image: none; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .question .text { - display: inline; - float: left; - width: 80%; - text-align: left; - min-height: 30px; - margin-left: var(--baseline, 20px); - height: auto; - margin-bottom: var(--baseline, 20px); -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .question .text.short { - width: 100px; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .stats { - min-height: 40px; - margin-top: var(--baseline, 20px); - clear: both; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .stats.short { - margin-top: 0; - clear: none; - display: inline; - float: right; - width: 70%; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .stats .bar { - width: 75%; - height: 20px; - border: 1px solid black; - display: inline; - float: left; - margin-right: calc((var(--baseline, 20px) / 2)); -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .stats .bar.short { - width: 65%; - height: 20px; - margin-top: 3px; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .stats .bar .percent { - background-color: gray; - width: 0; - height: 20px; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .stats .number { - width: 80px; - display: inline; - float: right; - height: 28px; - text-align: right; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer .stats .number.short { - width: 120px; - height: auto; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .poll_answer.answered { - -webkit-box-shadow: #61b8e1 0 1px 0 0 inset; - background-color: #1d9dd9; - background-image: -webkit-linear-gradient(top, #1d9dd9, #0e7cb0); - border-bottom-color: #0d72a2; - border-left-color: #0d72a2; - border-right-color: #0d72a2; - border-top-color: #0d72a2; - box-shadow: #61b8e1 0 1px 0 0 inset; - color: white; - text-shadow: #076794 0 1px 0; -} - -.xmodule_display.xmodule_PollBlock div.poll_question .button.reset-button { - clear: both; - float: right; -} diff --git a/xmodule/tests/test_poll.py b/xmodule/tests/test_poll.py deleted file mode 100644 index 2604aad0ced5..000000000000 --- a/xmodule/tests/test_poll.py +++ /dev/null @@ -1,102 +0,0 @@ -"""Test for Poll Xmodule functional logic.""" - -import json - -from django.test import TestCase, override_settings -from opaque_keys.edx.keys import CourseKey -from xblock.field_data import DictFieldData -from xblock.fields import ScopeIds - -from openedx.core.lib.safe_lxml import etree -from xmodule import poll_block - -from . import get_test_system -from .test_import import DummyModuleStoreRuntime - - -class _PollBlockTestBase(TestCase): - """Logic tests for Poll Xmodule.""" - __test__ = False - - @classmethod - def setUpClass(cls): - super().setUpClass() - cls.poll_block_class = poll_block.reset_class() - - raw_field_data = { - 'poll_answers': {'Yes': 1, 'Dont_know': 0, 'No': 0}, - 'voted': False, - 'poll_answer': '' - } - - def setUp(self): - super().setUp() - course_key = CourseKey.from_string('org/course/run') - self.system = get_test_system(course_key) - usage_key = course_key.make_usage_key('poll_question', 'test_loc') - # ScopeIds has 4 fields: user_id, block_type, def_id, usage_id - self.scope_ids = ScopeIds(1, 'poll_question', usage_key, usage_key) - self.xblock = self.poll_block_class( - self.system, DictFieldData(self.raw_field_data), self.scope_ids - ) - - def ajax_request(self, dispatch, data): - """Call Xmodule.handle_ajax.""" - return json.loads(self.xblock.handle_ajax(dispatch, data)) - - def test_bad_ajax_request(self): - # Make sure that answer for incorrect request is error json. - response = self.ajax_request('bad_answer', {}) - self.assertDictEqual(response, {'error': 'Unknown Command!'}) # noqa: PT009 - - def test_good_ajax_request(self): - # Make sure that ajax request works correctly. - response = self.ajax_request('No', {}) - - poll_answers = response['poll_answers'] - total = response['total'] - callback = response['callback'] - - self.assertDictEqual(poll_answers, {'Yes': 1, 'Dont_know': 0, 'No': 1}) # noqa: PT009 - assert total == 2 - self.assertDictEqual(callback, {'objectName': 'Conditional'}) # noqa: PT009 - assert self.xblock.poll_answer == 'No' - - def test_poll_export_with_unescaped_characters_xml(self): - """ - Make sure that poll_block will export fine if its xml contains - unescaped characters. - """ - module_system = DummyModuleStoreRuntime(load_error_blocks=True) - module_system.id_generator.target_course_id = self.xblock.context_key - sample_poll_xml = ''' - -

How old are you?

- 18 -
- ''' - node = etree.fromstring(sample_poll_xml) - - output = self.poll_block_class.parse_xml(node, module_system, self.scope_ids) - # Update the answer with invalid character. - invalid_characters_poll_answer = output.answers[0] - # Invalid less-than character. - invalid_characters_poll_answer['text'] = '< 18' - output.answers[0] = invalid_characters_poll_answer - output.save() - - xml = output.definition_to_xml(None) - # Extract texts of all children. - child_texts = xml.xpath('//text()') - # Last index of child_texts contains text of answer tag. - assert child_texts[(- 1)] == '< 18' - - -@override_settings(USE_EXTRACTED_POLL_QUESTION_BLOCK=True) -class PollBlockTestExtracted(_PollBlockTestBase): - __test__ = True - - -@override_settings(USE_EXTRACTED_POLL_QUESTION_BLOCK=False) -class PollBlockTestBuiltIn(_PollBlockTestBase): - __test__ = True