Commit 14fdfb43 by Braden MacDonald

Make Mentoring block itself studio-editable, remove <title> and <header> blocks

parent 00778d03
...@@ -6,5 +6,3 @@ from .mrq import MRQBlock ...@@ -6,5 +6,3 @@ from .mrq import MRQBlock
from .message import MentoringMessageBlock from .message import MentoringMessageBlock
from .table import MentoringTableBlock, MentoringTableColumnBlock, MentoringTableColumnHeaderBlock from .table import MentoringTableBlock, MentoringTableColumnBlock, MentoringTableColumnHeaderBlock
from .tip import TipBlock from .tip import TipBlock
from .title import TitleBlock
from .header import SharedHeaderBlock
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Harvard
#
# Authors:
# Xavier Antoviaque <xavier@antoviaque.org>
# David Gabor Bodor <david.gabor.bodor@gmail.com>
#
# 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/>.
#
from .html import HTMLBlock
class SharedHeaderBlock(HTMLBlock):
"""
A shared header block shown under the title.
"""
FIXED_CSS_CLASS = "shared-header"
pass
...@@ -22,18 +22,74 @@ ...@@ -22,18 +22,74 @@
# #
# Imports ########################################################### # Imports ###########################################################
from lxml import etree
from .common import BlockWithContent from xblock.core import XBlock
from xblock.fields import Scope, String from xblock.fields import Scope, String
from xblock.fragment import Fragment
from xblockutils.studio_editable import StudioEditableXBlockMixin
# Classes ########################################################### # Classes ###########################################################
class MentoringMessageBlock(BlockWithContent): class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
""" """
A message which can be conditionally displayed at the mentoring block level, A message which can be conditionally displayed at the mentoring block level,
for example upon completion of the block for example upon completion of the block
""" """
TEMPLATE = 'templates/html/message.html' content = String(
content = String(help="Message to display upon completion", scope=Scope.content, default="") display_name="Message",
type = String(help="Type of message", scope=Scope.content, default="completed") help="Message to display upon completion",
scope=Scope.content,
default="",
multiline_editor="html",
resettable_editor=False,
)
type = String(
help="Type of message",
scope=Scope.content,
default="completed",
values=(
{"display_name": "Completed", "value": "completed"},
{"display_name": "Incompleted", "value": "incomplete"},
{"display_name": "Reached max. # of attemps", "value": "max_attempts_reached"},
),
)
editable_fields = ("content", )
def fallback_view(self, view_name, context):
html = u'<div class="message {msg_type}">{content}</div>'.format(msg_type=self.type, content=self.content)
return Fragment(html)
@property
def display_name(self):
if self.type == 'max_attempts_reached':
max_attempts = self.get_parent().max_attempts
return u"Message when student reaches max. # of attempts ({current_limit})".format(
current_limit=u"unlimited" if max_attempts == 0 else max_attempts
)
if self.type == 'completed':
return u"Message shown when complete"
if self.type == 'incomplete':
return u"Message shown when incomplete"
return u"INVALID MESSAGE"
@classmethod
def get_template(cls, template_id):
"""
Used to interact with Studio's create_xblock method to instantiate pre-defined templates.
"""
return {'metadata': {'type': template_id, 'content': "Message goes here."}, 'data': {}}
@classmethod
def parse_xml(cls, node, runtime, keys, id_generator):
"""
Construct this XBlock from the given XML node.
"""
block = runtime.construct_xblock_from_class(cls, keys)
block.content = unicode(node.text or u"")
block.type = node.attrib['type']
for child in node:
block.content += etree.tostring(child, encoding='unicode')
return block
...@@ -17,7 +17,20 @@ ...@@ -17,7 +17,20 @@
# along with this program in a file in the toplevel directory called # along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>. # "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
# #
from .utils import child_isinstance from lazy import lazy
from xblockutils.helpers import child_isinstance
def _normalize_id(key):
"""
Helper method to normalize a key to avoid issues where some keys have version/branch and others don't.
e.g. self.scope_ids.usage_id != self.runtime.get_block(self.scope_ids.usage_id).scope_ids.usage_id
"""
if hasattr(key, "for_branch"):
key = key.for_branch(None)
if hasattr(key, "for_version"):
key = key.for_version(None)
return key
class StepParentMixin(object): class StepParentMixin(object):
...@@ -30,19 +43,31 @@ class StepParentMixin(object): ...@@ -30,19 +43,31 @@ class StepParentMixin(object):
""" """
Get the usage_ids of all of this XBlock's children that are "Steps" Get the usage_ids of all of this XBlock's children that are "Steps"
""" """
return [child_id for child_id in self.children if child_isinstance(self, child_id, StepMixin)] return [_normalize_id(child_id) for child_id in self.children if child_isinstance(self, child_id, StepMixin)]
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"
""" """
@property has_author_view = True
@lazy
def step_number(self): def step_number(self):
return list(self.get_parent().steps).index(self.scope_ids.usage_id) + 1 return list(self.get_parent().steps).index(_normalize_id(self.scope_ids.usage_id)) + 1
@property @lazy
def lonely_step(self): def lonely_step(self):
if self.scope_ids.usage_id not in self.get_parent().steps: if _normalize_id(self.scope_ids.usage_id) not in self.get_parent().steps:
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
def author_view(self, context):
context = context or {}
context['hide_header'] = True
return self.mentoring_view(context)
def author_preview_view(self, context):
context = context or {}
context['hide_header'] = True
return self.student_view(context)
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Harvard
#
# Authors:
# Xavier Antoviaque <xavier@antoviaque.org>
#
# 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/>.
#
# Imports ###########################################################
from xblock.core import XBlock
from xblock.fields import Scope, String
# Classes ###########################################################
class TitleBlock(XBlock):
"""
A simple html representation of a title, with the mentoring weight.
"""
content = String(help="Text to display", scope=Scope.content, default="")
"""
Helper methods
Should eventually be moved to xblock-utils.
"""
def child_isinstance(block, child_id, block_class_or_mixin):
"""
Is "block"'s child identified by usage_id "child_id" an instance of
"block_class_or_mixin"?
This is a bit complicated since it avoids the need to actually
instantiate the child block.
"""
def_id = block.runtime.id_reader.get_definition_id(child_id)
type_name = block.runtime.id_reader.get_block_type(def_id)
child_class = block.runtime.load_block_type(type_name)
return issubclass(child_class, block_class_or_mixin)
.mentoring-edit .module-actions .error-message { /* Custom appearance for our "Add" buttons */
color: red; .xblock[data-block-type=mentoring] .add-xblock-component .new-component .new-component-type .add-xblock-component-button {
width: 200px;
height: 30px;
line-height: 30px;
}
.xblock[data-block-type=mentoring] .add-xblock-component .new-component .new-component-type .add-xblock-component-button.disabled,
.xblock[data-block-type=mentoring] .add-xblock-component .new-component .new-component-type .add-xblock-component-button.disabled:hover {
background-color: #ccc;
border-color: #888;
cursor: default;
} }
function MentoringEditBlock(runtime, element) { function MentoringEditComponents(runtime, element) {
var xmlEditorTextarea = $('.block-xml-editor', element), "use strict";
xmlEditor = CodeMirror.fromTextArea(xmlEditorTextarea[0], { mode: 'xml' }); // Disable "add" buttons when a message of that type already exists:
var $buttons = $('.add-xblock-component-button[data-category=mentoring-message]', element);
$('.save-button', element).bind('click', function() { var updateButtons = function() {
var handlerUrl = runtime.handlerUrl(element, 'studio_submit'), $buttons.each(function() {
data = { var msg_type = $(this).data('boilerplate');
'xml_content': xmlEditor.getValue(), $(this).toggleClass('disabled', $('.xblock .message.'+msg_type).length > 0);
};
$('.error-message', element).html();
$.post(handlerUrl, JSON.stringify(data)).done(function(response) {
if (response.result === 'success') {
window.location.reload(false);
} else {
$('.error-message', element).html('Error: '+response.message);
}
}); });
};
updateButtons();
$buttons.click(function(ev) {
if ($(this).is('.disabled')) {
ev.preventDefault();
ev.stopPropagation();
} else {
$(this).addClass('disabled');
}
}); });
runtime.listenTo('deleted-child', updateButtons);
$('.cancel-button', element).bind('click', function() {
runtime.notify('cancel', {});
});
} }
...@@ -4,10 +4,9 @@ ...@@ -4,10 +4,9 @@
attempting this step. attempting this step.
</div> </div>
{% if title or header %} {% if title %}
<div class="title"> <div class="title">
{% if title %} <h2>{{ title }}</h2> {% endif %} {% if title %} <h2>{{ title }}</h2> {% endif %}
{% if header %} {{ header|safe }} {% endif %}
</div> </div>
{% endif %} {% endif %}
......
{% load i18n %}
<div class="add-xblock-component new-component-item adding">
<div class="new-component">
<h5>Add New Component</h5>
<ul class="new-component-type">
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-answer">Long Answer</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-mcq">Multiple Choice Question</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-rating">Rating Question</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-mrq">Multiple Response Question</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="html">HTML</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-answer-recap">Long Answer Recap</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-table">Answer Recap Table</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-message" data-boilerplate="completed">Message (Complete)</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-message" data-boilerplate="incomplete">Message (Incomplete)</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-message" data-boilerplate="max_attempts_reached">Message (Max # Attempts)</a></li>
</ul>
</div>
</div>
{% load i18n %}
<!-- TODO: Replace by default edit view once available in Studio -->
<div class="mentoring-edit editor-with-buttons">
<div class="wrapper-comp-settings is-active" id="settings-tab">
<textarea class="block-xml-editor">{{ xml_content }}</textarea>
</div>
<div class="xblock-actions">
<span class="error-message"></span>
<ul>
<li class="action-item">
<a href="#" class="button action-primary save-button">{% trans "Save" %}</a>
</li>
<li class="action-item">
<a href="#" class="button cancel-button">{% trans "Cancel" %}</a>
</li>
</ul>
</div>
</div>
<mentoring display_name="Nav tooltip title" weight="1" mode="assessment" max_attempts="10"> <mentoring display_name="Mentoring Assessment Example" weight="1" mode="assessment" max_attempts="10">
<title>Default Title</title> <html_demo>
<shared-header>
<p>This paragraph is shared between <strong>all</strong> questions.</p> <p>This paragraph is shared between <strong>all</strong> questions.</p>
</shared-header>
<html>
<p>Please answer the questions below.</p> <p>Please answer the questions below.</p>
</html> </html_demo>
<answer name="goal"> <answer name="goal">
<question>What is your goal?</question> <question>What is your goal?</question>
......
...@@ -4,20 +4,18 @@ ...@@ -4,20 +4,18 @@
should be able to export their data as CSV.</p> should be able to export their data as CSV.</p>
</html> </html>
<mentoring display_name="Mentoring Block 1" mode="standard"> <mentoring display_name="Mentoring Block 1" mode="standard">
<title>Mentoring Block 1</title> <html_demo>
<html>
<p>Please answer the question below.</p> <p>Please answer the question below.</p>
</html> </html_demo>
<answer name="goal"> <answer name="goal">
<question>What is your goal?</question> <question>What is your goal?</question>
</answer> </answer>
</mentoring> </mentoring>
<mentoring display_name="Mentoring Block 1" mode="assessment"> <mentoring display_name="Mentoring Block 2 (Assessment)" mode="assessment">
<title>Mentoring Block 2 (Assessment)</title> <html_demo>
<html>
<p>Please answer the question below.</p> <p>Please answer the question below.</p>
</html> </html_demo>
<answer name="inspired"> <answer name="inspired">
<question>Who has inspired you the most?</question> <question>Who has inspired you the most?</question>
......
<mentoring display_name="Nav tooltip title" weight="1" mode="standard"> <mentoring display_name="Default Title" weight="1" mode="standard">
<title>Default Title</title> <html_demo>
<html>
<p>Please answer the questions below.</p> <p>Please answer the questions below.</p>
</html> </html_demo>
<answer name="goal"> <answer name="goal">
<question>What is your goal?</question> <question>What is your goal?</question>
......
<mentoring display_name="Nav tooltip title" weight="1" mode="standard"> <mentoring display_name="Default Title" weight="1" mode="standard">
<title>Default Title</title> <html_demo>
<html>
<p>Please answer the questions below.</p> <p>Please answer the questions below.</p>
</html> </html_demo>
<mrq name="mrq_1_1" type="choices"> <mrq name="mrq_1_1" type="choices">
<question>What do you like in this MRQ?</question> <question>What do you like in this MRQ?</question>
......
<mentoring url_name="mentoring-assessment-1" display_name="Nav tooltip title" weight="1" mode="assessment" max_attempts="2"> <mentoring url_name="mentoring-assessment-1" display_name="A Simple Assessment" weight="1" mode="assessment" max_attempts="2">
<title>A Simple Assessment</title> <html_demo>
<shared-header>
<p>This paragraph is shared between <strong>all</strong> questions.</p> <p>This paragraph is shared between <strong>all</strong> questions.</p>
</shared-header>
<html>
<p>Please answer the questions below.</p> <p>Please answer the questions below.</p>
</html> </html_demo>
<answer name="goal"> <answer name="goal">
<question>What is your goal?</question> <question>What is your goal?</question>
......
<mentoring url_name="mentoring-assessment-2" display_name="Nav tooltip title" weight="1" mode="assessment" max_attempts="2"> <mentoring url_name="mentoring-assessment-2" display_name="A Simple Assessment" weight="1" mode="assessment" max_attempts="2">
<title>A Simple Assessment</title> <html_demo>
<shared-header>
<p>This paragraph is shared between <strong>all</strong> questions.</p> <p>This paragraph is shared between <strong>all</strong> questions.</p>
</shared-header>
<html>
<p>Please answer the questions below.</p> <p>Please answer the questions below.</p>
</html> </html_demo>
<mcq name="mcq_1_1" type="choices"> <mcq name="mcq_1_1" type="choices">
<question>Do you like this MCQ?</question> <question>Do you like this MCQ?</question>
......
...@@ -13,11 +13,10 @@ ...@@ -13,11 +13,10 @@
<question>What is your goal?</question> <question>What is your goal?</question>
</answer> </answer>
</mentoring> </mentoring>
<mentoring display_name="Mentoring Block 1" mode="assessment"> <mentoring display_name="Mentoring Block 2 (Assessment)" mode="assessment">
<title>Mentoring Block 2 (Assessment)</title> <html_demo>
<html>
<p>Please answer the question below.</p> <p>Please answer the question below.</p>
</html> </html_demo>
<answer name="inspired"> <answer name="inspired">
<question>Who has inspired you the most?</question> <question>Who has inspired you the most?</question>
......
<vertical_demo> <vertical_demo>
<mentoring url_name="mcq_with_comments" display_name="MRQ Exercise 7" weight="1" enforce_dependency="false"> <mentoring url_name="mcq_with_comments" display_name="MRQ With Resizable popups" weight="1" enforce_dependency="false">
<title>MRQ With Resizable popups</title>
<mrq name="mrq_1_1_7" type="choices"> <mrq name="mrq_1_1_7" type="choices">
<question>What do you like in this MRQ?</question> <question>What do you like in this MRQ?</question>
<choice value="elegance">Its elegance</choice> <choice value="elegance">Its elegance</choice>
......
<vertical_demo> <vertical_demo>
<mentoring url_name="mcq_with_comments" display_name="MCQ Exercise 7" weight="1" enforce_dependency="false"> <mentoring url_name="mcq_with_comments" display_name="MCQ With Resizable popups" weight="1" enforce_dependency="false">
<title>MRQ With Resizable popups</title>
<mcq name="mrq_1_1_7" type="choices"> <mcq name="mrq_1_1_7" type="choices">
<question>What do you like in this MRQ?</question> <question>What do you like in this MRQ?</question>
<choice value="elegance"><html><b>Its elegance</b></html></choice> <choice value="elegance"><html><b>Its elegance</b></html></choice>
......
<vertical_demo> <vertical_demo>
<mentoring url_name="mcq_with_comments" display_name="MRQ Exercise 7" weight="1" enforce_dependency="false"> <mentoring url_name="mcq_with_comments" display_name="MRQ With Resizable popups" weight="1" enforce_dependency="false">
<title>MRQ With Resizable popups</title>
<mrq name="mrq_1_1_7" type="choices"> <mrq name="mrq_1_1_7" type="choices">
<question>What do you like in this MRQ?</question> <question>What do you like in this MRQ?</question>
<choice value="elegance"><html><b>Its elegance</b></html></choice> <choice value="elegance"><html><b>Its elegance</b></html></choice>
......
...@@ -47,18 +47,16 @@ BLOCKS = [ ...@@ -47,18 +47,16 @@ BLOCKS = [
'mentoring-dataexport = mentoring:MentoringDataExportBlock', 'mentoring-dataexport = mentoring:MentoringDataExportBlock',
'mentoring-table = mentoring.components:MentoringTableBlock', 'mentoring-table = mentoring.components:MentoringTableBlock',
'column = mentoring.components:MentoringTableColumnBlock',
'header = mentoring.components:MentoringTableColumnHeaderBlock',
'answer = mentoring.components:AnswerBlock',
'quizz = mentoring.components:MCQBlock',
'mcq = mentoring.components:MCQBlock',
'mrq = mentoring.components:MRQBlock',
'message = mentoring.components:MentoringMessageBlock',
'tip = mentoring.components:TipBlock',
'choice = mentoring.components:ChoiceBlock',
'html = mentoring.components:HTMLBlock', 'html = mentoring.components:HTMLBlock',
'title = mentoring.components:TitleBlock', 'mentoring-column = mentoring.components:MentoringTableColumn',
'shared-header = mentoring.components:SharedHeaderBlock', 'mentoring-answer = mentoring.components:AnswerBlock',
'mentoring-answer-recap = mentoring.components:AnswerRecapBlock',
'mentoring-mcq = mentoring.components:MCQBlock',
'mentoring-rating = mentoring.components:RatingBlock',
'mentoring-mrq = mentoring.components:MRQBlock',
'mentoring-message = mentoring.components:MentoringMessageBlock',
'mentoring-tip = mentoring.components:TipBlock',
'mentoring-choice = mentoring.components:ChoiceBlock',
] ]
setup( setup(
......
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