From c51c6ebdf09bebe1dac66b718f3d80facd3f6e70 Mon Sep 17 00:00:00 2001 From: Jesse Shapiro Date: Fri, 16 Sep 2016 12:56:58 -0400 Subject: [PATCH 1/7] Initial work --- problem_builder/mentoring.py | 62 ++++++++++++++++++++++++-------- problem_builder/questionnaire.py | 8 ++--- 2 files changed, 52 insertions(+), 18 deletions(-) diff --git a/problem_builder/mentoring.py b/problem_builder/mentoring.py index 3cd5b9c7..9dd19326 100644 --- a/problem_builder/mentoring.py +++ b/problem_builder/mentoring.py @@ -47,6 +47,14 @@ NestedXBlockSpec, StudioEditableXBlockMixin, StudioContainerXBlockMixin, StudioContainerWithNestedXBlocksMixin, ) +from problem_builder.answer import AnswerBlock, AnswerRecapBlock +from problem_builder.mcq import MCQBlock, RatingBlock +from problem_builder.mixins import EnumerableChildMixin, 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 + try: # Used to detect if we're in the workbench so we can add Font Awesome @@ -220,7 +228,7 @@ def max_score(self): return 1.0 -class MentoringBlock(BaseMentoringBlock, StudioContainerXBlockMixin, StepParentMixin): +class MentoringBlock(BaseMentoringBlock, StudioContainerWithNestedXBlocksMixin, StepParentMixin): """ An XBlock providing mentoring capabilities @@ -321,6 +329,40 @@ class MentoringBlock(BaseMentoringBlock, StudioContainerXBlockMixin, StepParentM 'display_submit', 'feedback_label', 'weight', 'extended_feedback' ) + @property + def allowed_nested_blocks(self): + """ + Returns a list of allowed nested XBlocks. Each item can be either + * An XBlock class + * A NestedXBlockSpec + + If XBlock class is used it is assumed that this XBlock is enabled and allows multiple instances. + NestedXBlockSpec allows explicitly setting disabled/enabled state, disabled reason (if any) and single/multiple + instances + """ + additional_blocks = [] + try: + from xmodule.video_module.video_module import VideoDescriptor + additional_blocks.append(NestedXBlockSpec( + VideoDescriptor, category='video', label=_(u"Video") + )) + except ImportError: + pass + try: + from imagemodal import ImageModal + additional_blocks.append(NestedXBlockSpec( + ImageModal, category='imagemodal', label=_(u"Image Modal") + )) + except ImportError: + pass + + return [ + NestedXBlockSpec(AnswerBlock, boilerplate='studio_default'), + MCQBlock, RatingBlock, MRQBlock, + NestedXBlockSpec(None, category="html", label=self._("HTML")), + AnswerRecapBlock, MentoringTableBlock, PlotBlock, SliderBlock + ] + additional_blocks + @property def is_assessment(self): """ Checks if mentoring XBlock is in assessment mode """ @@ -817,23 +859,15 @@ def author_edit_view(self, context): """ Add some HTML to the author view that allows authors to add child blocks. """ - fragment = Fragment(u'
') # This DIV is needed for CSS to apply to the previews - self.render_children(context, fragment, can_reorder=True, can_add=False) - fragment.add_content(u'
') - - # Show buttons to add review-related child blocks only in assessment mode. - fragment.add_content(loader.render_template('templates/html/mentoring_add_buttons.html', { - "show_review": self.is_assessment, - })) - fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', { - "url_name": self.url_name - })) + local_context = dict(context) + local_context['author_edit_view'] = True + fragment = super(MentoringBlock, self).author_edit_view(local_context) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-tinymce-content.css')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js')) - fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring_edit.js')) - fragment.initialize_js('MentoringEditComponents') + fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js')) + fragment.initialize_js('ProblemBuilderContainerEdit') return fragment @staticmethod diff --git a/problem_builder/questionnaire.py b/problem_builder/questionnaire.py index 5d338e90..ac672f78 100644 --- a/problem_builder/questionnaire.py +++ b/problem_builder/questionnaire.py @@ -32,7 +32,7 @@ from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContainerXBlockMixin, XBlockWithPreviewMixin from .choice import ChoiceBlock -from .mentoring import MentoringBlock +#from .mentoring import MentoringBlock from .message import MentoringMessageBlock from .mixins import QuestionMixin, XBlockWithTranslationServiceMixin from .tip import TipBlock @@ -95,9 +95,9 @@ def student_view(self, context=None): fragment = Fragment(loader.render_template(template_path, context)) # If we use local_resource_url(self, ...) the runtime may insert many identical copies # of questionnaire.[css/js] into the DOM. So we use the mentoring block here if possible - block_with_resources = self.get_parent() - if not isinstance(block_with_resources, MentoringBlock): - block_with_resources = self + # block_with_resources = self.get_parent() + # if not isinstance(block_with_resources, MentoringBlock): + block_with_resources = self fragment.add_css_url(self.runtime.local_resource_url(block_with_resources, 'public/css/questionnaire.css')) fragment.add_javascript_url(self.runtime.local_resource_url(block_with_resources, 'public/js/questionnaire.js')) fragment.initialize_js(name) From 6385beebf177fdd881880bb60587bbb9c138c771 Mon Sep 17 00:00:00 2001 From: Jesse Shapiro Date: Tue, 27 Sep 2016 14:30:44 -0400 Subject: [PATCH 2/7] Avoiding circular import --- problem_builder/questionnaire.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/problem_builder/questionnaire.py b/problem_builder/questionnaire.py index ac672f78..0bf86dc2 100644 --- a/problem_builder/questionnaire.py +++ b/problem_builder/questionnaire.py @@ -32,7 +32,6 @@ from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContainerXBlockMixin, XBlockWithPreviewMixin from .choice import ChoiceBlock -#from .mentoring import MentoringBlock from .message import MentoringMessageBlock from .mixins import QuestionMixin, XBlockWithTranslationServiceMixin from .tip import TipBlock @@ -93,13 +92,8 @@ def student_view(self, context=None): context['hide_header'] = context.get('hide_header', False) or not self.show_title fragment = Fragment(loader.render_template(template_path, context)) - # If we use local_resource_url(self, ...) the runtime may insert many identical copies - # of questionnaire.[css/js] into the DOM. So we use the mentoring block here if possible - # block_with_resources = self.get_parent() - # if not isinstance(block_with_resources, MentoringBlock): - block_with_resources = self - fragment.add_css_url(self.runtime.local_resource_url(block_with_resources, 'public/css/questionnaire.css')) - fragment.add_javascript_url(self.runtime.local_resource_url(block_with_resources, 'public/js/questionnaire.js')) + fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/questionnaire.css')) + fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/questionnaire.js')) fragment.initialize_js(name) return fragment From 14da31c35f775ed1aa33efd2025b13c25968db2a Mon Sep 17 00:00:00 2001 From: Jesse Shapiro Date: Tue, 27 Sep 2016 17:04:46 -0400 Subject: [PATCH 3/7] Pointing to mentoring edit JS --- problem_builder/mentoring.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/problem_builder/mentoring.py b/problem_builder/mentoring.py index 9dd19326..e16aecf6 100644 --- a/problem_builder/mentoring.py +++ b/problem_builder/mentoring.py @@ -49,7 +49,6 @@ from problem_builder.answer import AnswerBlock, AnswerRecapBlock from problem_builder.mcq import MCQBlock, RatingBlock -from problem_builder.mixins import EnumerableChildMixin, StepParentMixin from problem_builder.mrq import MRQBlock from problem_builder.plot import PlotBlock from problem_builder.slider import SliderBlock @@ -866,8 +865,8 @@ def author_edit_view(self, context): fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-tinymce-content.css')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js')) - fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js')) - fragment.initialize_js('ProblemBuilderContainerEdit') + fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring_edit.js')) + fragment.initialize_js('MentoringEditComponents') return fragment @staticmethod From 702f265d0c607636b6356a0776e22fe2d84aa20b Mon Sep 17 00:00:00 2001 From: Jesse Shapiro Date: Tue, 27 Sep 2016 17:21:07 -0400 Subject: [PATCH 4/7] Remove unnecessary import --- problem_builder/mentoring.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/problem_builder/mentoring.py b/problem_builder/mentoring.py index e16aecf6..8ca9be3f 100644 --- a/problem_builder/mentoring.py +++ b/problem_builder/mentoring.py @@ -44,7 +44,7 @@ from xblockutils.resources import ResourceLoader from xblockutils.settings import XBlockWithSettingsMixin, ThemableXBlockMixin from xblockutils.studio_editable import ( - NestedXBlockSpec, StudioEditableXBlockMixin, StudioContainerXBlockMixin, StudioContainerWithNestedXBlocksMixin, + NestedXBlockSpec, StudioEditableXBlockMixin, StudioContainerWithNestedXBlocksMixin, ) from problem_builder.answer import AnswerBlock, AnswerRecapBlock From d1fae47f2fe61d85315b6f44638652c19228e141 Mon Sep 17 00:00:00 2001 From: Jesse Shapiro Date: Sun, 2 Oct 2016 18:22:26 -0400 Subject: [PATCH 5/7] Review comments --- problem_builder/mentoring.py | 26 ++++++++++++++++--- problem_builder/message.py | 11 ++++++++ problem_builder/questionnaire.py | 10 +++++-- .../templates/html/mentoring_add_buttons.html | 23 ---------------- 4 files changed, 41 insertions(+), 29 deletions(-) delete mode 100644 problem_builder/templates/html/mentoring_add_buttons.html diff --git a/problem_builder/mentoring.py b/problem_builder/mentoring.py index 8ca9be3f..7f34b695 100644 --- a/problem_builder/mentoring.py +++ b/problem_builder/mentoring.py @@ -34,7 +34,7 @@ from xblock.fragment import Fragment from xblock.validation import ValidationMessage -from .message import MentoringMessageBlock +from .message import MentoringMessageBlock, get_message_label from .mixins import ( _normalize_id, QuestionMixin, MessageParentMixin, StepParentMixin, XBlockWithTranslationServiceMixin ) @@ -355,12 +355,27 @@ def allowed_nested_blocks(self): except ImportError: pass + message_block_shims = [ + NestedXBlockSpec( + MentoringMessageBlock, + category='pb-message', + boilerplate=message_type, + label=get_message_label(message_type), + ) + for message_type in ( + 'completed', + 'incomplete', + 'max_attempts_reached', + 'on-assessment-review', + ) + ] + return [ NestedXBlockSpec(AnswerBlock, boilerplate='studio_default'), MCQBlock, RatingBlock, MRQBlock, NestedXBlockSpec(None, category="html", label=self._("HTML")), AnswerRecapBlock, MentoringTableBlock, PlotBlock, SliderBlock - ] + additional_blocks + ] + additional_blocks + message_block_shims @property def is_assessment(self): @@ -858,15 +873,18 @@ def author_edit_view(self, context): """ Add some HTML to the author view that allows authors to add child blocks. """ - local_context = dict(context) + local_context = context.copy() local_context['author_edit_view'] = True fragment = super(MentoringBlock, self).author_edit_view(local_context) + fragment.add_content(loader.render_template('templates/html/mentoring_url_name.html', { + 'url_name': self.url_name + })) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-tinymce-content.css')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js')) - fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/mentoring_edit.js')) fragment.initialize_js('MentoringEditComponents') + return fragment @staticmethod diff --git a/problem_builder/message.py b/problem_builder/message.py index 7315c98d..4e6095a1 100644 --- a/problem_builder/message.py +++ b/problem_builder/message.py @@ -33,6 +33,13 @@ def _(text): return text +MESSAGE_TYPES = { + 'completed': _(u'Message (Complete)'), + 'incomplete': _(u'Message (Incomplete)'), + 'max_attempts_reached': _(u'Message (Max # Attempts)'), + 'on-assessment-review': _(u'Message (Assessment Review)'), +} + # Classes ########################################################### @@ -189,3 +196,7 @@ class CompletedMentoringMessageShim(object): class IncompleteMentoringMessageShim(object): CATEGORY = 'pb-message' STUDIO_LABEL = _("Message (Incomplete)") + + +def get_message_label(type): + return MESSAGE_TYPES.get(type, 'Message') diff --git a/problem_builder/questionnaire.py b/problem_builder/questionnaire.py index 0bf86dc2..55865b24 100644 --- a/problem_builder/questionnaire.py +++ b/problem_builder/questionnaire.py @@ -92,8 +92,14 @@ def student_view(self, context=None): context['hide_header'] = context.get('hide_header', False) or not self.show_title fragment = Fragment(loader.render_template(template_path, context)) - fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/questionnaire.css')) - fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/questionnaire.js')) + # If we use local_resource_url(self, ...) the runtime may insert many identical copies + # of questionnaire.[css/js] into the DOM. So we use the mentoring block here if possible + block_with_resources = self.get_parent() + from .mentoring import MentoringBlock + if not isinstance(block_with_resources, MentoringBlock): + block_with_resources = self + fragment.add_css_url(self.runtime.local_resource_url(block_with_resources, 'public/css/questionnaire.css')) + fragment.add_javascript_url(self.runtime.local_resource_url(block_with_resources, 'public/js/questionnaire.js')) fragment.initialize_js(name) return fragment diff --git a/problem_builder/templates/html/mentoring_add_buttons.html b/problem_builder/templates/html/mentoring_add_buttons.html deleted file mode 100644 index e9ac09a0..00000000 --- a/problem_builder/templates/html/mentoring_add_buttons.html +++ /dev/null @@ -1,23 +0,0 @@ -{% load i18n %} - - From eff467d7695f2067aa9f2545065c1b2810745b6a Mon Sep 17 00:00:00 2001 From: Jesse Shapiro Date: Mon, 3 Oct 2016 05:46:16 -0400 Subject: [PATCH 6/7] Review comments --- problem_builder/mentoring.py | 14 ++++++++++++-- problem_builder/message.py | 15 +++++++-------- problem_builder/questionnaire.py | 3 ++- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/problem_builder/mentoring.py b/problem_builder/mentoring.py index 7f34b695..ae7f7cf3 100644 --- a/problem_builder/mentoring.py +++ b/problem_builder/mentoring.py @@ -366,10 +366,19 @@ def allowed_nested_blocks(self): 'completed', 'incomplete', 'max_attempts_reached', - 'on-assessment-review', ) ] + if self.is_assessment: + message_block_shims.append( + NestedXBlockSpec( + MentoringMessageBlock, + category='pb-message', + boilerplate='on-assessment-review', + label=get_message_label('on-assessment-review'), + ) + ) + return [ NestedXBlockSpec(AnswerBlock, boilerplate='studio_default'), MCQBlock, RatingBlock, MRQBlock, @@ -883,7 +892,8 @@ def author_edit_view(self, context): fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-edit.css')) fragment.add_css_url(self.runtime.local_resource_url(self, 'public/css/problem-builder-tinymce-content.css')) fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/util.js')) - fragment.initialize_js('MentoringEditComponents') + fragment.add_javascript_url(self.runtime.local_resource_url(self, 'public/js/container_edit.js')) + fragment.initialize_js('ProblemBuilderContainerEdit') return fragment diff --git a/problem_builder/message.py b/problem_builder/message.py index 4e6095a1..a1bf3942 100644 --- a/problem_builder/message.py +++ b/problem_builder/message.py @@ -33,13 +33,6 @@ def _(text): return text -MESSAGE_TYPES = { - 'completed': _(u'Message (Complete)'), - 'incomplete': _(u'Message (Incomplete)'), - 'max_attempts_reached': _(u'Message (Max # Attempts)'), - 'on-assessment-review': _(u'Message (Assessment Review)'), -} - # Classes ########################################################### @@ -52,6 +45,7 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin, XBlockWithTransla MESSAGE_TYPES = { "completed": { "display_name": _(u"Completed"), + "studio_label": _(u'Message (Complete)'), "long_display_name": _(u"Message shown when complete"), "default": _(u"Great job!"), "description": _( @@ -61,6 +55,7 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin, XBlockWithTransla }, "incomplete": { "display_name": _(u"Incomplete"), + "studio_label": _(u'Message (Incomplete)'), "long_display_name": _(u"Message shown when incomplete"), "default": _(u"Not quite! You can try again, though."), "description": _( @@ -71,6 +66,7 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin, XBlockWithTransla }, "max_attempts_reached": { "display_name": _(u"Reached max. # of attempts"), + "studio_label": _(u'Message (Max # Attempts)'), "long_display_name": _(u"Message shown when student reaches max. # of attempts"), "default": _(u"Sorry, you have used up all of your allowed submissions."), "description": _( @@ -81,6 +77,7 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin, XBlockWithTransla }, "on-assessment-review": { "display_name": _(u"Review with attempts left"), + "studio_label": _(u'Message (Assessment Review)'), "long_display_name": _(u"Message shown during review when attempts remain"), "default": _( u"Note: if you retake this assessment, only your final score counts. " @@ -107,6 +104,8 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin, XBlockWithTransla }, } + has_author_view = True + content = String( display_name=_("Message"), help=_("Message to display upon completion"), @@ -199,4 +198,4 @@ class IncompleteMentoringMessageShim(object): def get_message_label(type): - return MESSAGE_TYPES.get(type, 'Message') + return MentoringMessageBlock.MESSAGE_TYPES[type]['studio_label'] diff --git a/problem_builder/questionnaire.py b/problem_builder/questionnaire.py index 55865b24..ee3627ac 100644 --- a/problem_builder/questionnaire.py +++ b/problem_builder/questionnaire.py @@ -93,9 +93,10 @@ def student_view(self, context=None): fragment = Fragment(loader.render_template(template_path, context)) # If we use local_resource_url(self, ...) the runtime may insert many identical copies - # of questionnaire.[css/js] into the DOM. So we use the mentoring block here if possible + # of questionnaire.[css/js] into the DOM. So we use the mentoring block here if possible. block_with_resources = self.get_parent() from .mentoring import MentoringBlock + # We use an inline import here to avoid a circular dependency with the .mentoring module. if not isinstance(block_with_resources, MentoringBlock): block_with_resources = self fragment.add_css_url(self.runtime.local_resource_url(block_with_resources, 'public/css/questionnaire.css')) From 97749d8b7eed4c09782e1211d16962ddc8f166e2 Mon Sep 17 00:00:00 2001 From: Jesse Shapiro Date: Mon, 3 Oct 2016 09:11:27 -0400 Subject: [PATCH 7/7] Remove trailing whitespace --- problem_builder/questionnaire.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/problem_builder/questionnaire.py b/problem_builder/questionnaire.py index ee3627ac..595bb25f 100644 --- a/problem_builder/questionnaire.py +++ b/problem_builder/questionnaire.py @@ -96,7 +96,7 @@ def student_view(self, context=None): # of questionnaire.[css/js] into the DOM. So we use the mentoring block here if possible. block_with_resources = self.get_parent() from .mentoring import MentoringBlock - # We use an inline import here to avoid a circular dependency with the .mentoring module. + # We use an inline import here to avoid a circular dependency with the .mentoring module. if not isinstance(block_with_resources, MentoringBlock): block_with_resources = self fragment.add_css_url(self.runtime.local_resource_url(block_with_resources, 'public/css/questionnaire.css'))