Commit 323d03b8 by Braden MacDonald

Generate any required IDs automatically in Studio

parent 22ca961c
......@@ -31,9 +31,11 @@ from mentoring.models import Answer
from xblock.core import XBlock
from xblock.fields import Scope, Float, Integer, String
from xblock.fragment import Fragment
from xblock.validation import ValidationMessage
from xblockutils.resources import ResourceLoader
from xblockutils.studio_editable import StudioEditableXBlockMixin
from .step import StepMixin
import uuid
# Globals ###########################################################
......@@ -80,6 +82,18 @@ class AnswerMixin(object):
)
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):
"""
......@@ -196,14 +210,24 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
answer_data.student_input = self.student_input
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):
"""
A block that displays an answer previously entered by the student (read-only).
"""
name = String(
display_name="Answer ID",
help="The ID of the answer to display.",
display_name="Question ID",
help="The ID of the question for which to display the student's answer.",
scope=Scope.content,
)
display_name = String(
......
......@@ -24,6 +24,7 @@
# Imports ###########################################################
from lxml import etree
import uuid
from xblock.core import XBlock
from xblock.fields import Scope, String
......@@ -50,15 +51,19 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
scope=Scope.content,
default="",
)
editable_fields = ('value', 'content')
editable_fields = ('content', )
@property
def display_name(self):
try:
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(self.value, status)
def __getattribute__(self, name):
"""
Provide a read-only display name without adding a display_name field to the class.
"""
if name == "display_name":
try:
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):
return Fragment(u'<span class="choice-text">{}</span>'.format(self.content))
......@@ -77,13 +82,39 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
if not data.content.strip():
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
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)
for field_name in cls.editable_fields:
for field_name in ('value', 'content'):
if field_name in node.attrib:
setattr(block, field_name, node.attrib[field_name])
......
......@@ -25,7 +25,7 @@
from lxml import etree
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.validation import ValidationMessage
from xblockutils.helpers import child_isinstance
......@@ -61,10 +61,11 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
set, with preset choices and author-defined values.
"""
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)",
help="The ID of this question (required). Should be unique within this mentoring component.",
default="",
scope=Scope.content
default=UNIQUE_ID,
scope=Scope.settings, # Must be scope.settings, or the unique ID will change every time this block is edited
)
question = String(
display_name="Question",
......@@ -85,7 +86,7 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
scope=Scope.content,
enforce_type=True
)
editable_fields = ('name', 'question', 'message', 'weight')
editable_fields = ('question', 'message', 'weight')
has_children = True
@classmethod
......
......@@ -3,7 +3,7 @@
<div class="add-xblock-component new-component-item adding">
<div class="new-component">
<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>
</ul>
</div>
......
......@@ -4,7 +4,7 @@
<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-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-rating">Rating 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