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
from .message import MentoringMessageBlock
from .table import MentoringTableBlock, MentoringTableColumnBlock, MentoringTableColumnHeaderBlock
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 @@
#
# Imports ###########################################################
from lxml import etree
from .common import BlockWithContent
from xblock.core import XBlock
from xblock.fields import Scope, String
from xblock.fragment import Fragment
from xblockutils.studio_editable import StudioEditableXBlockMixin
# Classes ###########################################################
class MentoringMessageBlock(BlockWithContent):
class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
"""
A message which can be conditionally displayed at the mentoring block level,
for example upon completion of the block
"""
TEMPLATE = 'templates/html/message.html'
content = String(help="Message to display upon completion", scope=Scope.content, default="")
type = String(help="Type of message", scope=Scope.content, default="completed")
content = String(
display_name="Message",
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 @@
# along with this program in a file in the toplevel directory called
# "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):
......@@ -30,19 +43,31 @@ class StepParentMixin(object):
"""
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):
"""
An XBlock mixin for a child block that is a "Step"
"""
@property
has_author_view = True
@lazy
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):
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)
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 {
color: red;
/* Custom appearance for our "Add" buttons */
.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) {
var xmlEditorTextarea = $('.block-xml-editor', element),
xmlEditor = CodeMirror.fromTextArea(xmlEditorTextarea[0], { mode: 'xml' });
$('.save-button', element).bind('click', function() {
var handlerUrl = runtime.handlerUrl(element, 'studio_submit'),
data = {
'xml_content': xmlEditor.getValue(),
};
$('.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);
}
function MentoringEditComponents(runtime, element) {
"use strict";
// Disable "add" buttons when a message of that type already exists:
var $buttons = $('.add-xblock-component-button[data-category=mentoring-message]', element);
var updateButtons = function() {
$buttons.each(function() {
var msg_type = $(this).data('boilerplate');
$(this).toggleClass('disabled', $('.xblock .message.'+msg_type).length > 0);
});
};
updateButtons();
$buttons.click(function(ev) {
if ($(this).is('.disabled')) {
ev.preventDefault();
ev.stopPropagation();
} else {
$(this).addClass('disabled');
}
});
$('.cancel-button', element).bind('click', function() {
runtime.notify('cancel', {});
});
runtime.listenTo('deleted-child', updateButtons);
}
......@@ -4,10 +4,9 @@
attempting this step.
</div>
{% if title or header %}
{% if title %}
<div class="title">
{% if title %} <h2>{{ title }}</h2> {% endif %}
{% if header %} {{ header|safe }} {% endif %}
</div>
{% 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">
<title>Default Title</title>
<shared-header>
<mentoring display_name="Mentoring Assessment Example" weight="1" mode="assessment" max_attempts="10">
<html_demo>
<p>This paragraph is shared between <strong>all</strong> questions.</p>
</shared-header>
<html>
<p>Please answer the questions below.</p>
</html>
</html_demo>
<answer name="goal">
<question>What is your goal?</question>
......
......@@ -4,20 +4,18 @@
should be able to export their data as CSV.</p>
</html>
<mentoring display_name="Mentoring Block 1" mode="standard">
<title>Mentoring Block 1</title>
<html>
<html_demo>
<p>Please answer the question below.</p>
</html>
</html_demo>
<answer name="goal">
<question>What is your goal?</question>
</answer>
</mentoring>
<mentoring display_name="Mentoring Block 1" mode="assessment">
<title>Mentoring Block 2 (Assessment)</title>
<html>
<mentoring display_name="Mentoring Block 2 (Assessment)" mode="assessment">
<html_demo>
<p>Please answer the question below.</p>
</html>
</html_demo>
<answer name="inspired">
<question>Who has inspired you the most?</question>
......
<mentoring display_name="Nav tooltip title" weight="1" mode="standard">
<title>Default Title</title>
<html>
<mentoring display_name="Default Title" weight="1" mode="standard">
<html_demo>
<p>Please answer the questions below.</p>
</html>
</html_demo>
<answer name="goal">
<question>What is your goal?</question>
......
<mentoring display_name="Nav tooltip title" weight="1" mode="standard">
<title>Default Title</title>
<html>
<mentoring display_name="Default Title" weight="1" mode="standard">
<html_demo>
<p>Please answer the questions below.</p>
</html>
</html_demo>
<mrq name="mrq_1_1" type="choices">
<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">
<title>A Simple Assessment</title>
<shared-header>
<mentoring url_name="mentoring-assessment-1" display_name="A Simple Assessment" weight="1" mode="assessment" max_attempts="2">
<html_demo>
<p>This paragraph is shared between <strong>all</strong> questions.</p>
</shared-header>
<html>
<p>Please answer the questions below.</p>
</html>
</html_demo>
<answer name="goal">
<question>What is your goal?</question>
......
<mentoring url_name="mentoring-assessment-2" display_name="Nav tooltip title" weight="1" mode="assessment" max_attempts="2">
<title>A Simple Assessment</title>
<shared-header>
<mentoring url_name="mentoring-assessment-2" display_name="A Simple Assessment" weight="1" mode="assessment" max_attempts="2">
<html_demo>
<p>This paragraph is shared between <strong>all</strong> questions.</p>
</shared-header>
<html>
<p>Please answer the questions below.</p>
</html>
</html_demo>
<mcq name="mcq_1_1" type="choices">
<question>Do you like this MCQ?</question>
......
......@@ -13,11 +13,10 @@
<question>What is your goal?</question>
</answer>
</mentoring>
<mentoring display_name="Mentoring Block 1" mode="assessment">
<title>Mentoring Block 2 (Assessment)</title>
<html>
<mentoring display_name="Mentoring Block 2 (Assessment)" mode="assessment">
<html_demo>
<p>Please answer the question below.</p>
</html>
</html_demo>
<answer name="inspired">
<question>Who has inspired you the most?</question>
......
<vertical_demo>
<mentoring url_name="mcq_with_comments" display_name="MRQ Exercise 7" weight="1" enforce_dependency="false">
<title>MRQ With Resizable popups</title>
<mentoring url_name="mcq_with_comments" display_name="MRQ With Resizable popups" weight="1" enforce_dependency="false">
<mrq name="mrq_1_1_7" type="choices">
<question>What do you like in this MRQ?</question>
<choice value="elegance">Its elegance</choice>
......
<vertical_demo>
<mentoring url_name="mcq_with_comments" display_name="MCQ Exercise 7" weight="1" enforce_dependency="false">
<title>MRQ With Resizable popups</title>
<mentoring url_name="mcq_with_comments" display_name="MCQ With Resizable popups" weight="1" enforce_dependency="false">
<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>
......
<vertical_demo>
<mentoring url_name="mcq_with_comments" display_name="MRQ Exercise 7" weight="1" enforce_dependency="false">
<title>MRQ With Resizable popups</title>
<mentoring url_name="mcq_with_comments" display_name="MRQ With Resizable popups" weight="1" enforce_dependency="false">
<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>
......
......@@ -47,18 +47,16 @@ BLOCKS = [
'mentoring-dataexport = mentoring:MentoringDataExportBlock',
'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',
'title = mentoring.components:TitleBlock',
'shared-header = mentoring.components:SharedHeaderBlock',
'mentoring-column = mentoring.components:MentoringTableColumn',
'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(
......
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