Commit 9acd923b by Xavier Antoviaque

Merge pull request #94 from open-craft/eugeny/html-in-choices

Added ability to use HTML LightChild in question blocks
parents 850080ed 5028cdfb
......@@ -26,6 +26,7 @@
import logging
from .light_children import LightChild, Scope, String
from .utils import loader, ContextConstants
# Globals ###########################################################
......@@ -41,3 +42,16 @@ class ChoiceBlock(LightChild):
"""
value = String(help="Value of the choice when selected", scope=Scope.content, default="")
content = String(help="Human-readable version of the choice value", scope=Scope.content, default="")
has_children = True
def render(self):
# return self.content
"""
Returns a fragment containing the formatted tip
"""
fragment, named_children = self.get_children_fragment({ContextConstants.AS_TEMPLATE: False})
fragment.add_content(loader.render_template('templates/html/choice.html', {
'self': self,
'named_children': named_children,
}))
return self.xblock_container.fragment_text_rewriting(fragment)
......@@ -33,6 +33,7 @@ from .light_children import LightChild, Scope, String
# Globals ###########################################################
from .utils import ContextConstants
log = logging.getLogger(__name__)
......@@ -50,13 +51,15 @@ class HTMLBlock(LightChild):
block.light_children = []
node.tag = 'div'
node_classes = (cls for cls in [node.get('class', ''), 'html_child'] if cls)
node.set('class', " ".join(node_classes))
block.content = unicode(etree.tostring(node))
node.tag = 'html'
return block
def student_view(self, context=None):
as_template = context.get('as_template', True) if context is not None else True
as_template = context.get(ContextConstants.AS_TEMPLATE, True) if context is not None else True
if as_template:
return Fragment(u"<script type='text/template' id='{}'>\n{}\n</script>".format(
'light-child-template',
......
......@@ -80,3 +80,13 @@
text-indent: -22px;
line-height: 1.3;
}
.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;
}
......@@ -31,7 +31,7 @@ from .choice import ChoiceBlock
from .step import StepMixin
from .light_children import LightChild, Scope, String, Float
from .tip import TipBlock
from .utils import loader
from .utils import loader, ContextConstants
# Globals ###########################################################
......@@ -75,7 +75,7 @@ class QuestionnaireAbstractBlock(LightChild, StepMixin):
def student_view(self, context=None):
name = self.__class__.__name__
as_template = context.get('as_template', True) if context is not None else True
as_template = context.get(ContextConstants.AS_TEMPLATE, True) if context is not None else True
if str(self.type) not in self.valid_types:
raise ValueError(u'Invalid value for {}.type: `{}`'.format(name, self.type))
......
<span class="choice-text">
{% if self.content %}{{ self.content }}{% endif %}
{% for name, c in named_children %}
{{c.body_html|safe}}
{% endfor %}
</span>
\ No newline at end of file
......@@ -8,7 +8,8 @@
<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 %} />{{ choice.content }}
<input class="choice-selector" type="radio" name="{{ self.name }}" value="{{ choice.value }}"{% if self.student_choice == choice.value %} checked{% endif %} />
{{ choice.render.body_html|safe }}
</label>
<div class="choice-tips"></div>
</div>
......
......@@ -34,7 +34,9 @@
{% for choice in custom_choices %}
<div class="choice">
<div class="choice-result fa icon-2x"></div>
<label><input type="radio" name="{{ self.name }}" value="{{ choice.value }}"{% if self.student_choice == '{{ choice.value }}' %} checked{% endif %} />{{ choice.content }}</label>
<label><input type="radio" name="{{ self.name }}" value="{{ choice.value }}"{% if self.student_choice == '{{ choice.value }}' %} checked{% endif %} />
{{ choice.render.body_html|safe }}
</label>
<div class="choice-tips"></div>
</div>
{% endfor %}
......
......@@ -11,7 +11,7 @@
<input class="choice-selector" type="checkbox" name="{{ self.name }}"
value="{{ choice.value }}"
{% if choice.value in self.student_choices %} checked{% endif %} />
{{ choice.content }}
{{ choice.render.body_html|safe }}
</label>
<div class="choice-tips"></div>
</div>
......
......@@ -89,3 +89,7 @@ class XBlockWithChildrenFragmentsMixin(object):
for name, child_fragment in named_children:
fragment.add_content(child_fragment.content)
return fragment
class ContextConstants(object):
AS_TEMPLATE = 'as_template'
\ No newline at end of file
ddt
-e .
unicodecsv==0.9.4
-e git://github.com/nosedjango/nosedjango.git@ed7d7f9aa969252ff799ec159f828eaa8c1cbc5a#egg=nosedjango-dev
......
......@@ -36,7 +36,7 @@ class AnswerBlockTest(MentoringBaseTest):
# Answer should initially be blank on all instances with the same answer name
mentoring = self.go_to_page('Answer Edit 2')
answer1_bis = mentoring.find_element_by_css_selector('.xblock textarea')
answer1_bis = mentoring.find_element_by_css_selector('textarea')
answer1_readonly = mentoring.find_element_by_css_selector('blockquote.answer.read_only')
self.assertEqual(answer1_bis.get_attribute('value'), '')
self.assertEqual(answer1_readonly.text, '')
......@@ -47,7 +47,7 @@ class AnswerBlockTest(MentoringBaseTest):
self.assertEqual(header1.text, 'XBlock: Answer Edit 1')
# Check <html> child
p = mentoring.find_element_by_css_selector('div.xblock p')
p = mentoring.find_element_by_css_selector('p')
self.assertEqual(p.text, 'This should be displayed in the answer_edit scenario')
# Initial unsubmitted text
......@@ -77,7 +77,7 @@ class AnswerBlockTest(MentoringBaseTest):
# Answer content should show on a different instance with the same name
mentoring = self.go_to_page('Answer Edit 2')
answer1_bis = mentoring.find_element_by_css_selector('.xblock textarea')
answer1_bis = mentoring.find_element_by_css_selector('textarea')
answer1_readonly = mentoring.find_element_by_css_selector('blockquote.answer.read_only')
self.assertEqual(answer1_bis.get_attribute('value'), 'This is the answer. It has a second statement.')
self.assertEqual(answer1_readonly.text, 'This is the answer. It has a second statement.')
......
......@@ -316,4 +316,7 @@ class MentoringAssessmentTest(MentoringBaseTest):
self.peek_at_review(mentoring, controls, expected_results)
controls.try_again.click()
\ No newline at end of file
controls.try_again.click()
# this is a wait and assertion all together - it waits until expected text is in mentoring block
# and it fails with PrmoiseFailed exception if it's not
self.wait_until_text_in(self.question_text(0), mentoring)
\ No newline at end of file
......@@ -23,12 +23,14 @@
# Imports ###########################################################
import ddt
from .base_test import MentoringBaseTest
# Classes ###########################################################
@ddt.ddt
class MCQBlockTest(MentoringBaseTest):
def _selenium_bug_workaround_scroll_to(self, mcq_legend):
......@@ -46,6 +48,10 @@ class MCQBlockTest(MentoringBaseTest):
def _get_inputs(self, choices):
return [choice.find_element_by_css_selector('input') for choice in choices]
def assert_messages_empty(self, messages):
self.assertEqual(messages.text, '')
self.assertFalse(messages.find_elements_by_xpath('./*'))
def test_mcq_choices_rating(self):
"""
Mentoring MCQ should display tips according to user choice
......@@ -57,8 +63,7 @@ class MCQBlockTest(MentoringBaseTest):
messages = mentoring.find_element_by_css_selector('.messages')
submit = mentoring.find_element_by_css_selector('.submit input.input-main')
self.assertEqual(messages.text, '')
self.assertFalse(messages.find_elements_by_xpath('./*'))
self.assert_messages_empty(messages)
self.assertFalse(submit.is_enabled())
mcq1_legend = mcq1.find_element_by_css_selector('legend')
......@@ -183,3 +188,49 @@ class MCQBlockTest(MentoringBaseTest):
mentoring.click()
self.assertFalse(item_feedback_popup.is_displayed())
def _get_questionnaire_options(self, questionnaire):
result = []
# this could be a list comprehension, but a bit complicated one - hence explicit loop
for choice_wrapper in questionnaire.find_elements_by_css_selector(".choice"):
choice_label = choice_wrapper.find_element_by_css_selector(".choice-label .choice-text")
light_child = choice_label.find_element_by_css_selector(".xblock-light-child")
result.append(light_child.find_element_by_css_selector("div").get_attribute('innerHTML'))
return result
@ddt.data(
'Mrq With Html Choices',
'Mcq With Html Choices'
)
def test_questionnaire_html_choices(self, page):
mentoring = self.go_to_page(page)
choices_list = mentoring.find_element_by_css_selector(".choices-list")
messages = mentoring.find_element_by_css_selector('.messages')
expected_options = [
"<b>Its elegance</b>",
"<i>Its beauty</i>",
"<strong>Its gracefulness</strong>",
'<span style="font-color:red">Its bugs</span>'
]
options = self._get_questionnaire_options(choices_list)
self.assertEqual(expected_options, options)
self.assert_messages_empty(messages)
submit = mentoring.find_element_by_css_selector('.submit input.input-main')
self.assertFalse(submit.is_enabled())
inputs = choices_list.find_elements_by_css_selector('input.choice-selector')
self._selenium_bug_workaround_scroll_to(choices_list)
inputs[0].click()
inputs[1].click()
inputs[2].click()
self.assertTrue(submit.is_enabled())
submit.click()
self.wait_until_disabled(submit)
self.assertIn('Congratulations!', messages.text)
\ No newline at end of file
<vertical_demo>
<mentoring url_name="mcq_with_comments" display_name="MCQ Exercise 7" weight="1" enforce_dependency="false">
<title>MRQ With Resizable popups</title>
<mcq name="mrq_1_1_7" type="choices">
<question>What do you like in this MRQ?</question>
<choice value="elegance"><html><b>Its elegance</b></html></choice>
<choice value="beauty"><html><i>Its beauty</i></html></choice>
<choice value="gracefulness"><html><strong>Its gracefulness</strong></html></choice>
<choice value="bugs"><html><span style="font-color:red">Its bugs</span></html></choice>
<tip require="gracefulness" width ="200" height = "200">This MRQ is indeed very graceful</tip>
<tip require="elegance" width ="600" height = "800">This is something everyone has to like about this MRQ</tip>
<tip require="beauty" width ="400" height = "600">This is something everyone has to like about beauty</tip>
<tip reject="bugs" width = "100" height = "200">Nah, there isn\'t any!</tip>
</mcq>
<message type="completed">
<html><p>Congratulations!</p></html>
</message>
<message type="incomplete">
<html><p>Still some work to do...</p></html>
</message>
</mentoring>
</vertical_demo>
<vertical_demo>
<mentoring url_name="mcq_with_comments" display_name="MRQ Exercise 7" weight="1" enforce_dependency="false">
<title>MRQ With Resizable popups</title>
<mrq name="mrq_1_1_7" type="choices">
<question>What do you like in this MRQ?</question>
<choice value="elegance"><html><b>Its elegance</b></html></choice>
<choice value="beauty"><html><i>Its beauty</i></html></choice>
<choice value="gracefulness"><html><strong>Its gracefulness</strong></html></choice>
<choice value="bugs"><html><span style="font-color:red">Its bugs</span></html></choice>
<tip require="gracefulness" width ="200" height = "200">This MRQ is indeed very graceful</tip>
<tip require="elegance" width ="600" height = "800">This is something everyone has to like about this MRQ</tip>
<tip require="beauty" width ="400" height = "600">This is something everyone has to like about beauty</tip>
<tip reject="bugs" width = "100" height = "200">Nah, there isn\'t any!</tip>
</mrq>
<message type="completed">
<html><p>Congratulations!</p></html>
</message>
<message type="incomplete">
<html><p>Still some work to do...</p></html>
</message>
</mentoring>
</vertical_demo>
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