Commit 323d03b8 by Braden MacDonald

Generate any required IDs automatically in Studio

parent 22ca961c
...@@ -31,9 +31,11 @@ from mentoring.models import Answer ...@@ -31,9 +31,11 @@ from mentoring.models import Answer
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, Float, Integer, String from xblock.fields import Scope, Float, Integer, String
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xblock.validation import ValidationMessage
from xblockutils.resources import ResourceLoader from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin from xblockutils.studio_editable import StudioEditableXBlockMixin
from .step import StepMixin from .step import StepMixin
import uuid
# Globals ########################################################### # Globals ###########################################################
...@@ -80,6 +82,18 @@ class AnswerMixin(object): ...@@ -80,6 +82,18 @@ class AnswerMixin(object):
) )
return answer_data return answer_data
def validate_field_data(self, validation, data):
"""
Validate this block's field data.
"""
super(AnswerMixin, self).validate_field_data(validation, data)
def add_error(msg):
validation.add(ValidationMessage(ValidationMessage.ERROR, msg))
if not data.name:
add_error(u"A Question ID is required.")
class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock): class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
""" """
...@@ -196,14 +210,24 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock): ...@@ -196,14 +210,24 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
answer_data.student_input = self.student_input answer_data.student_input = self.student_input
answer_data.save() answer_data.save()
@classmethod
def get_template(cls, template_id):
"""
Used to interact with Studio's create_xblock method to instantiate pre-defined templates.
"""
# Generate a random 'name' value
if template_id == 'studio_default':
return {'metadata': {'name': uuid.uuid4().hex[:7]}, 'data': {}}
return {'metadata': {}, 'data': {}}
class AnswerRecapBlock(AnswerMixin, StudioEditableXBlockMixin, XBlock): class AnswerRecapBlock(AnswerMixin, StudioEditableXBlockMixin, XBlock):
""" """
A block that displays an answer previously entered by the student (read-only). A block that displays an answer previously entered by the student (read-only).
""" """
name = String( name = String(
display_name="Answer ID", display_name="Question ID",
help="The ID of the answer to display.", help="The ID of the question for which to display the student's answer.",
scope=Scope.content, scope=Scope.content,
) )
display_name = String( display_name = String(
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
# Imports ########################################################### # Imports ###########################################################
from lxml import etree from lxml import etree
import uuid
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, String from xblock.fields import Scope, String
...@@ -50,15 +51,19 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock): ...@@ -50,15 +51,19 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
scope=Scope.content, scope=Scope.content,
default="", default="",
) )
editable_fields = ('value', 'content') editable_fields = ('content', )
@property def __getattribute__(self, name):
def display_name(self): """
try: Provide a read-only display name without adding a display_name field to the class.
status = self.get_parent().describe_choice_correctness(self.value) """
except Exception: if name == "display_name":
status = u"Out of Context" # Parent block should implement describe_choice_correctness() try:
return u"Choice ({}) ({})".format(self.value, status) status = self.get_parent().describe_choice_correctness(self.value)
except Exception:
status = u"Out of Context" # Parent block should implement describe_choice_correctness()
return u"Choice ({})".format(status)
return super(ChoiceBlock, self).__getattribute__(name)
def fallback_view(self, view_name, context): def fallback_view(self, view_name, context):
return Fragment(u'<span class="choice-text">{}</span>'.format(self.content)) return Fragment(u'<span class="choice-text">{}</span>'.format(self.content))
...@@ -77,13 +82,39 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock): ...@@ -77,13 +82,39 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
if not data.content.strip(): if not data.content.strip():
add_error(u"No choice text set yet.") add_error(u"No choice text set yet.")
def validate(self):
"""
Validates the state of this XBlock.
"""
validation = super(ChoiceBlock, self).validate()
if self.get_parent().all_choice_values.count(self.value) > 1:
validation.add(
ValidationMessage(ValidationMessage.ERROR, (
u"This choice has a non-unique ID and won't work properly. "
"This can happen if you duplicate a choice rather than use the Add Choice button."
))
)
print(self.get_parent().all_choice_values)
return validation
@classmethod
def get_template(cls, template_id):
"""
Used to interact with Studio's create_xblock method to instantiate pre-defined templates.
"""
# Generate a random 'value' value. We can't just use default=UNIQUE_ID on the field,
# because that doesn't work properly with import/export, re-run, or duplicating the block
if template_id == 'studio_default':
return {'metadata': {'value': uuid.uuid4().hex[:7]}, 'data': {}}
return {'metadata': {}, 'data': {}}
@classmethod @classmethod
def parse_xml(cls, node, runtime, keys, id_generator): def parse_xml(cls, node, runtime, keys, id_generator):
""" """
Construct this XBlock from the given XML node. Construct this XBlock from the given XML node.
""" """
block = runtime.construct_xblock_from_class(cls, keys) block = runtime.construct_xblock_from_class(cls, keys)
for field_name in cls.editable_fields: for field_name in ('value', 'content'):
if field_name in node.attrib: if field_name in node.attrib:
setattr(block, field_name, node.attrib[field_name]) setattr(block, field_name, node.attrib[field_name])
......
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
from lxml import etree from lxml import etree
from xblock.core import XBlock from xblock.core import XBlock
from xblock.fields import Scope, String, Float, List from xblock.fields import Scope, String, Float, List, UNIQUE_ID
from xblock.fragment import Fragment from xblock.fragment import Fragment
from xblock.validation import ValidationMessage from xblock.validation import ValidationMessage
from xblockutils.helpers import child_isinstance from xblockutils.helpers import child_isinstance
...@@ -61,10 +61,11 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc ...@@ -61,10 +61,11 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
set, with preset choices and author-defined values. set, with preset choices and author-defined values.
""" """
name = String( name = String(
# This doesn't need to be a field but is kept for backwards compatibility with v1 student data
display_name="Question ID (name)", display_name="Question ID (name)",
help="The ID of this question (required). Should be unique within this mentoring component.", help="The ID of this question (required). Should be unique within this mentoring component.",
default="", default=UNIQUE_ID,
scope=Scope.content scope=Scope.settings, # Must be scope.settings, or the unique ID will change every time this block is edited
) )
question = String( question = String(
display_name="Question", display_name="Question",
...@@ -85,7 +86,7 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc ...@@ -85,7 +86,7 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
scope=Scope.content, scope=Scope.content,
enforce_type=True enforce_type=True
) )
editable_fields = ('name', 'question', 'message', 'weight') editable_fields = ('question', 'message', 'weight')
has_children = True has_children = True
@classmethod @classmethod
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="add-xblock-component new-component-item adding"> <div class="add-xblock-component new-component-item adding">
<div class="new-component"> <div class="new-component">
<ul class="new-component-type"> <ul class="new-component-type">
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-choice">Add Custom Choice</a></li> <li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-choice" data-boilerplate="studio_default">Add Custom Choice</a></li>
<li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-tip">Add Tip</a></li> <li><a href="#" class="single-template add-xblock-component-button" data-category="mentoring-tip">Add Tip</a></li>
</ul> </ul>
</div> </div>
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
<div class="new-component"> <div class="new-component">
<h5>Add New Component</h5> <h5>Add New Component</h5>
<ul class="new-component-type"> <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-answer" data-boilerplate="studio_default">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-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-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="mentoring-mrq">Multiple Response Question</a></li>
......
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