Merge pull request #12950 from edx/christina/studio-support-levels
Studio support level of components/xblocks
This commit is contained in:
@@ -691,7 +691,6 @@ class MiscCourseTests(ContentStoreTestCase):
|
||||
# Test that malicious code does not appear in html
|
||||
self.assertNotIn(malicious_code, resp.content)
|
||||
|
||||
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', [])
|
||||
def test_advanced_components_in_edit_unit(self):
|
||||
# This could be made better, but for now let's just assert that we see the advanced modules mentioned in the page
|
||||
# response HTML
|
||||
|
||||
@@ -21,6 +21,7 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
||||
from openedx.core.djangoapps.models.course_details import CourseDetails
|
||||
from student.roles import CourseInstructorRole, CourseStaffRole
|
||||
from student.tests.factories import UserFactory
|
||||
from xblock_django.models import XBlockStudioConfigurationFlag
|
||||
from xmodule.fields import Date
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -784,6 +785,15 @@ class CourseMetadataEditingTest(CourseTestCase):
|
||||
)
|
||||
self.assertNotIn('edxnotes', test_model)
|
||||
|
||||
def test_allow_unsupported_xblocks(self):
|
||||
"""
|
||||
allow_unsupported_xblocks is only shown in Advanced Settings if
|
||||
XBlockStudioConfigurationFlag is enabled.
|
||||
"""
|
||||
self.assertNotIn('allow_unsupported_xblocks', CourseMetadata.fetch(self.fullcourse))
|
||||
XBlockStudioConfigurationFlag(enabled=True).save()
|
||||
self.assertIn('allow_unsupported_xblocks', CourseMetadata.fetch(self.fullcourse))
|
||||
|
||||
def test_validate_from_json_correct_inputs(self):
|
||||
is_valid, errors, test_model = CourseMetadata.validate_and_update_from_json(
|
||||
self.course,
|
||||
|
||||
@@ -27,7 +27,10 @@ from opaque_keys.edx.keys import UsageKey
|
||||
|
||||
from student.auth import has_course_author_access
|
||||
from django.utils.translation import ugettext as _
|
||||
from xblock_django.models import XBlockDisableConfig
|
||||
|
||||
from xblock_django.api import disabled_xblocks, authorable_xblocks
|
||||
from xblock_django.models import XBlockStudioConfigurationFlag
|
||||
|
||||
|
||||
__all__ = [
|
||||
'container_handler',
|
||||
@@ -47,17 +50,41 @@ CONTAINER_TEMPLATES = [
|
||||
"basic-modal", "modal-button", "edit-xblock-modal",
|
||||
"editor-mode-button", "upload-dialog",
|
||||
"add-xblock-component", "add-xblock-component-button", "add-xblock-component-menu",
|
||||
"add-xblock-component-menu-problem", "xblock-string-field-editor", "publish-xblock", "publish-history",
|
||||
"add-xblock-component-support-legend", "add-xblock-component-support-level", "add-xblock-component-menu-problem",
|
||||
"xblock-string-field-editor", "publish-xblock", "publish-history",
|
||||
"unit-outline", "container-message", "license-selector",
|
||||
]
|
||||
|
||||
|
||||
def _advanced_component_types():
|
||||
def _advanced_component_types(show_unsupported):
|
||||
"""
|
||||
Return advanced component types which can be created.
|
||||
|
||||
Args:
|
||||
show_unsupported: if True, unsupported XBlocks may be included in the return value
|
||||
|
||||
Returns:
|
||||
A dict of authorable XBlock types and their support levels (see XBlockStudioConfiguration). For example:
|
||||
{
|
||||
"done": "us", # unsupported
|
||||
"discussion: "fs" # fully supported
|
||||
}
|
||||
Note that the support level will be "True" for all XBlocks if XBlockStudioConfigurationFlag
|
||||
is not enabled.
|
||||
"""
|
||||
disabled_create_block_types = XBlockDisableConfig.disabled_create_block_types()
|
||||
return [c_type for c_type in ADVANCED_COMPONENT_TYPES if c_type not in disabled_create_block_types]
|
||||
enabled_block_types = _filter_disabled_blocks(ADVANCED_COMPONENT_TYPES)
|
||||
if XBlockStudioConfigurationFlag.is_enabled():
|
||||
authorable_blocks = authorable_xblocks(allow_unsupported=show_unsupported)
|
||||
filtered_blocks = {}
|
||||
for block in authorable_blocks:
|
||||
if block.name in enabled_block_types:
|
||||
filtered_blocks[block.name] = block.support_level
|
||||
return filtered_blocks
|
||||
else:
|
||||
all_blocks = {}
|
||||
for block_name in enabled_block_types:
|
||||
all_blocks[block_name] = True
|
||||
return all_blocks
|
||||
|
||||
|
||||
def _load_mixed_class(category):
|
||||
@@ -152,13 +179,14 @@ def get_component_templates(courselike, library=False):
|
||||
"""
|
||||
Returns the applicable component templates that can be used by the specified course or library.
|
||||
"""
|
||||
def create_template_dict(name, cat, boilerplate_name=None, tab="common", hinted=False):
|
||||
def create_template_dict(name, category, support_level, boilerplate_name=None, tab="common", hinted=False):
|
||||
"""
|
||||
Creates a component template dict.
|
||||
|
||||
Parameters
|
||||
display_name: the user-visible name of the component
|
||||
category: the type of component (problem, html, etc.)
|
||||
support_level: the support level of this component
|
||||
boilerplate_name: name of boilerplate for filling in default values. May be None.
|
||||
hinted: True if hinted problem else False
|
||||
tab: common(default)/advanced, which tab it goes in
|
||||
@@ -166,10 +194,50 @@ def get_component_templates(courselike, library=False):
|
||||
"""
|
||||
return {
|
||||
"display_name": name,
|
||||
"category": cat,
|
||||
"category": category,
|
||||
"boilerplate_name": boilerplate_name,
|
||||
"hinted": hinted,
|
||||
"tab": tab
|
||||
"tab": tab,
|
||||
"support_level": support_level
|
||||
}
|
||||
|
||||
def component_support_level(editable_types, name, template=None):
|
||||
"""
|
||||
Returns the support level for the given xblock name/template combination.
|
||||
|
||||
Args:
|
||||
editable_types: a QuerySet of xblocks with their support levels
|
||||
name: the name of the xblock
|
||||
template: optional template for the xblock
|
||||
|
||||
Returns:
|
||||
If XBlockStudioConfigurationFlag is enabled, returns the support level
|
||||
(see XBlockStudioConfiguration) or False if this xblock name/template combination
|
||||
has no Studio support at all. If XBlockStudioConfigurationFlag is disabled,
|
||||
simply returns True.
|
||||
"""
|
||||
# If the Studio support feature is disabled, return True for all.
|
||||
if not XBlockStudioConfigurationFlag.is_enabled():
|
||||
return True
|
||||
if template is None:
|
||||
template = ""
|
||||
extension_index = template.rfind(".yaml")
|
||||
if extension_index >= 0:
|
||||
template = template[0:extension_index]
|
||||
for block in editable_types:
|
||||
if block.name == name and block.template == template:
|
||||
return block.support_level
|
||||
|
||||
return False
|
||||
|
||||
def create_support_legend_dict():
|
||||
"""
|
||||
Returns a dict of settings information for the display of the support level legend.
|
||||
"""
|
||||
return {
|
||||
"show_legend": XBlockStudioConfigurationFlag.is_enabled(),
|
||||
"allow_unsupported_xblocks": allow_unsupported,
|
||||
"documentation_label": _("{platform_name} Support Levels:").format(platform_name=settings.PLATFORM_NAME)
|
||||
}
|
||||
|
||||
component_display_names = {
|
||||
@@ -189,57 +257,92 @@ def get_component_templates(courselike, library=False):
|
||||
if library:
|
||||
component_types = [component for component in component_types if component != 'discussion']
|
||||
|
||||
component_types = _filter_disabled_blocks(component_types)
|
||||
|
||||
# Content Libraries currently don't allow opting in to unsupported xblocks/problem types.
|
||||
allow_unsupported = getattr(courselike, "allow_unsupported_xblocks", False)
|
||||
|
||||
for category in component_types:
|
||||
authorable_variations = authorable_xblocks(allow_unsupported=allow_unsupported, name=category)
|
||||
support_level_without_template = component_support_level(authorable_variations, category)
|
||||
templates_for_category = []
|
||||
component_class = _load_mixed_class(category)
|
||||
# add the default template with localized display name
|
||||
# TODO: Once mixins are defined per-application, rather than per-runtime,
|
||||
# this should use a cms mixed-in class. (cpennington)
|
||||
display_name = xblock_type_display_name(category, _('Blank')) # this is the Blank Advanced problem
|
||||
templates_for_category.append(create_template_dict(display_name, category, None, 'advanced'))
|
||||
categories.add(category)
|
||||
|
||||
if support_level_without_template:
|
||||
# add the default template with localized display name
|
||||
# TODO: Once mixins are defined per-application, rather than per-runtime,
|
||||
# this should use a cms mixed-in class. (cpennington)
|
||||
display_name = xblock_type_display_name(category, _('Blank')) # this is the Blank Advanced problem
|
||||
templates_for_category.append(
|
||||
create_template_dict(display_name, category, support_level_without_template, None, 'advanced')
|
||||
)
|
||||
categories.add(category)
|
||||
|
||||
# add boilerplates
|
||||
if hasattr(component_class, 'templates'):
|
||||
for template in component_class.templates():
|
||||
filter_templates = getattr(component_class, 'filter_templates', None)
|
||||
if not filter_templates or filter_templates(template, courselike):
|
||||
# Tab can be 'common' 'advanced'
|
||||
# Default setting is common/advanced depending on the presence of markdown
|
||||
tab = 'common'
|
||||
if template['metadata'].get('markdown') is None:
|
||||
tab = 'advanced'
|
||||
hinted = template.get('hinted', False)
|
||||
|
||||
templates_for_category.append(
|
||||
create_template_dict(
|
||||
_(template['metadata'].get('display_name')), # pylint: disable=translation-of-non-string
|
||||
category,
|
||||
template.get('template_id'),
|
||||
tab,
|
||||
hinted,
|
||||
)
|
||||
template_id = template.get('template_id')
|
||||
support_level_with_template = component_support_level(
|
||||
authorable_variations, category, template_id
|
||||
)
|
||||
if support_level_with_template:
|
||||
# Tab can be 'common' 'advanced'
|
||||
# Default setting is common/advanced depending on the presence of markdown
|
||||
tab = 'common'
|
||||
if template['metadata'].get('markdown') is None:
|
||||
tab = 'advanced'
|
||||
hinted = template.get('hinted', False)
|
||||
|
||||
# Add any advanced problem types
|
||||
templates_for_category.append(
|
||||
create_template_dict(
|
||||
_(template['metadata'].get('display_name')), # pylint: disable=translation-of-non-string
|
||||
category,
|
||||
support_level_with_template,
|
||||
template_id,
|
||||
tab,
|
||||
hinted,
|
||||
)
|
||||
)
|
||||
|
||||
# Add any advanced problem types. Note that these are different xblocks being stored as Advanced Problems.
|
||||
if category == 'problem':
|
||||
for advanced_problem_type in ADVANCED_PROBLEM_TYPES:
|
||||
disabled_block_names = [block.name for block in disabled_xblocks()]
|
||||
advanced_problem_types = [advanced_problem_type for advanced_problem_type in ADVANCED_PROBLEM_TYPES
|
||||
if advanced_problem_type['component'] not in disabled_block_names]
|
||||
for advanced_problem_type in advanced_problem_types:
|
||||
component = advanced_problem_type['component']
|
||||
boilerplate_name = advanced_problem_type['boilerplate_name']
|
||||
try:
|
||||
component_display_name = xblock_type_display_name(component)
|
||||
except PluginMissingError:
|
||||
log.warning('Unable to load xblock type %s to read display_name', component, exc_info=True)
|
||||
else:
|
||||
templates_for_category.append(
|
||||
create_template_dict(component_display_name, component, boilerplate_name, 'advanced')
|
||||
)
|
||||
categories.add(component)
|
||||
|
||||
authorable_advanced_component_variations = authorable_xblocks(
|
||||
allow_unsupported=allow_unsupported, name=component
|
||||
)
|
||||
advanced_component_support_level = component_support_level(
|
||||
authorable_advanced_component_variations, component, boilerplate_name
|
||||
)
|
||||
if advanced_component_support_level:
|
||||
try:
|
||||
component_display_name = xblock_type_display_name(component)
|
||||
except PluginMissingError:
|
||||
log.warning('Unable to load xblock type %s to read display_name', component, exc_info=True)
|
||||
else:
|
||||
templates_for_category.append(
|
||||
create_template_dict(
|
||||
component_display_name,
|
||||
component,
|
||||
advanced_component_support_level,
|
||||
boilerplate_name,
|
||||
'advanced'
|
||||
)
|
||||
)
|
||||
categories.add(component)
|
||||
|
||||
component_templates.append({
|
||||
"type": category,
|
||||
"templates": templates_for_category,
|
||||
"display_name": component_display_names[category]
|
||||
"display_name": component_display_names[category],
|
||||
"support_legend": create_support_legend_dict()
|
||||
})
|
||||
|
||||
# Libraries do not support advanced components at this time.
|
||||
@@ -251,19 +354,25 @@ def get_component_templates(courselike, library=False):
|
||||
# are the names of the modules in ADVANCED_COMPONENT_TYPES that should be
|
||||
# enabled for the course.
|
||||
course_advanced_keys = courselike.advanced_modules
|
||||
advanced_component_templates = {"type": "advanced", "templates": [], "display_name": _("Advanced")}
|
||||
advanced_component_types = _advanced_component_types()
|
||||
advanced_component_templates = {
|
||||
"type": "advanced",
|
||||
"templates": [],
|
||||
"display_name": _("Advanced"),
|
||||
"support_legend": create_support_legend_dict()
|
||||
}
|
||||
advanced_component_types = _advanced_component_types(allow_unsupported)
|
||||
# Set component types according to course policy file
|
||||
if isinstance(course_advanced_keys, list):
|
||||
for category in course_advanced_keys:
|
||||
if category in advanced_component_types and category not in categories:
|
||||
if category in advanced_component_types.keys() and category not in categories:
|
||||
# boilerplates not supported for advanced components
|
||||
try:
|
||||
component_display_name = xblock_type_display_name(category, default_display_name=category)
|
||||
advanced_component_templates['templates'].append(
|
||||
create_template_dict(
|
||||
component_display_name,
|
||||
category
|
||||
category,
|
||||
advanced_component_types[category]
|
||||
)
|
||||
)
|
||||
categories.add(category)
|
||||
@@ -288,6 +397,14 @@ def get_component_templates(courselike, library=False):
|
||||
return component_templates
|
||||
|
||||
|
||||
def _filter_disabled_blocks(all_blocks):
|
||||
"""
|
||||
Filter out disabled xblocks from the provided list of xblock names.
|
||||
"""
|
||||
disabled_block_names = [block.name for block in disabled_xblocks()]
|
||||
return [block_name for block_name in all_blocks if block_name not in disabled_block_names]
|
||||
|
||||
|
||||
@login_required
|
||||
def _get_item_in_course(request, usage_key):
|
||||
"""
|
||||
|
||||
@@ -24,7 +24,7 @@ from contentstore.views.item import (
|
||||
)
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
from student.tests.factories import UserFactory
|
||||
from xblock_django.models import XBlockDisableConfig
|
||||
from xblock_django.models import XBlockConfiguration, XBlockStudioConfiguration, XBlockStudioConfigurationFlag
|
||||
from xmodule.capa_module import CapaDescriptor
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.django import modulestore
|
||||
@@ -1458,12 +1458,19 @@ class TestComponentTemplates(CourseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestComponentTemplates, self).setUp()
|
||||
self.templates = get_component_templates(self.course)
|
||||
# Advanced Module support levels.
|
||||
XBlockStudioConfiguration.objects.create(name='poll', enabled=True, support_level="fs")
|
||||
XBlockStudioConfiguration.objects.create(name='survey', enabled=True, support_level="ps")
|
||||
XBlockStudioConfiguration.objects.create(name='annotatable', enabled=True, support_level="us")
|
||||
# Basic component support levels.
|
||||
XBlockStudioConfiguration.objects.create(name='html', enabled=True, support_level="fs")
|
||||
XBlockStudioConfiguration.objects.create(name='discussion', enabled=True, support_level="ps")
|
||||
XBlockStudioConfiguration.objects.create(name='problem', enabled=True, support_level="us")
|
||||
XBlockStudioConfiguration.objects.create(name='video', enabled=True, support_level="us")
|
||||
# XBlock masquerading as a problem
|
||||
XBlockStudioConfiguration.objects.create(name='openassessment', enabled=True, support_level="us")
|
||||
|
||||
# Initialize the deprecated modules settings with empty list
|
||||
XBlockDisableConfig.objects.create(
|
||||
disabled_create_blocks='', enabled=True
|
||||
)
|
||||
self.templates = get_component_templates(self.course)
|
||||
|
||||
def get_templates_of_type(self, template_type):
|
||||
"""
|
||||
@@ -1482,12 +1489,40 @@ class TestComponentTemplates(CourseTestCase):
|
||||
"""
|
||||
Test the handling of the basic component templates.
|
||||
"""
|
||||
self.assertIsNotNone(self.get_templates_of_type('discussion'))
|
||||
self.assertIsNotNone(self.get_templates_of_type('html'))
|
||||
self.assertIsNotNone(self.get_templates_of_type('problem'))
|
||||
self.assertIsNotNone(self.get_templates_of_type('video'))
|
||||
self._verify_basic_component("discussion", "Discussion")
|
||||
self._verify_basic_component("video", "Video")
|
||||
self.assertGreater(self.get_templates_of_type('html'), 0)
|
||||
self.assertGreater(self.get_templates_of_type('problem'), 0)
|
||||
self.assertIsNone(self.get_templates_of_type('advanced'))
|
||||
|
||||
# Now fully disable video through XBlockConfiguration
|
||||
XBlockConfiguration.objects.create(name='video', enabled=False)
|
||||
self.templates = get_component_templates(self.course)
|
||||
self.assertIsNone(self.get_templates_of_type('video'))
|
||||
|
||||
def test_basic_components_support_levels(self):
|
||||
"""
|
||||
Test that support levels can be set on basic component templates.
|
||||
"""
|
||||
XBlockStudioConfigurationFlag.objects.create(enabled=True)
|
||||
self.templates = get_component_templates(self.course)
|
||||
self._verify_basic_component("discussion", "Discussion", "ps")
|
||||
self.assertEqual([], self.get_templates_of_type("video"))
|
||||
self.assertEqual([], self.get_templates_of_type("problem"))
|
||||
|
||||
self.course.allow_unsupported_xblocks = True
|
||||
self.templates = get_component_templates(self.course)
|
||||
self._verify_basic_component("video", "Video", "us")
|
||||
problem_templates = self.get_templates_of_type('problem')
|
||||
problem_no_boilerplate = self.get_template(problem_templates, u'Blank Advanced Problem')
|
||||
self.assertIsNotNone(problem_no_boilerplate)
|
||||
self.assertEqual('us', problem_no_boilerplate['support_level'])
|
||||
|
||||
# Now fully disable video through XBlockConfiguration
|
||||
XBlockConfiguration.objects.create(name='video', enabled=False)
|
||||
self.templates = get_component_templates(self.course)
|
||||
self.assertIsNone(self.get_templates_of_type('video'))
|
||||
|
||||
def test_advanced_components(self):
|
||||
"""
|
||||
Test the handling of advanced component templates.
|
||||
@@ -1511,6 +1546,11 @@ class TestComponentTemplates(CourseTestCase):
|
||||
self.assertNotEqual(only_template.get('category'), 'video')
|
||||
self.assertNotEqual(only_template.get('category'), 'openassessment')
|
||||
|
||||
# Now fully disable word_cloud through XBlockConfiguration
|
||||
XBlockConfiguration.objects.create(name='word_cloud', enabled=False)
|
||||
self.templates = get_component_templates(self.course)
|
||||
self.assertIsNone(self.get_templates_of_type('advanced'))
|
||||
|
||||
def test_advanced_problems(self):
|
||||
"""
|
||||
Test the handling of advanced problem templates.
|
||||
@@ -1521,44 +1561,101 @@ class TestComponentTemplates(CourseTestCase):
|
||||
self.assertEqual(circuit_template.get('category'), 'problem')
|
||||
self.assertEqual(circuit_template.get('boilerplate_name'), 'circuitschematic.yaml')
|
||||
|
||||
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', [])
|
||||
def test_deprecated_no_advance_component_button(self):
|
||||
"""
|
||||
Test that there will be no `Advanced` button on unit page if units are
|
||||
deprecated provided that they are the only modules in `Advanced Module List`
|
||||
Test that there will be no `Advanced` button on unit page if xblocks have disabled
|
||||
Studio support given that they are the only modules in `Advanced Module List`
|
||||
"""
|
||||
XBlockDisableConfig.objects.create(disabled_create_blocks='poll survey', enabled=True)
|
||||
# Update poll and survey to have "enabled=False".
|
||||
XBlockStudioConfiguration.objects.create(name='poll', enabled=False, support_level="fs")
|
||||
XBlockStudioConfiguration.objects.create(name='survey', enabled=False, support_level="fs")
|
||||
XBlockStudioConfigurationFlag.objects.create(enabled=True)
|
||||
self.course.advanced_modules.extend(['poll', 'survey'])
|
||||
templates = get_component_templates(self.course)
|
||||
button_names = [template['display_name'] for template in templates]
|
||||
self.assertNotIn('Advanced', button_names)
|
||||
|
||||
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', [])
|
||||
def test_cannot_create_deprecated_problems(self):
|
||||
"""
|
||||
Test that we can't create problems if they are deprecated
|
||||
Test that xblocks that have Studio support disabled do not show on the "new component" menu.
|
||||
"""
|
||||
XBlockDisableConfig.objects.create(disabled_create_blocks='poll survey', enabled=True)
|
||||
# Update poll to have "enabled=False".
|
||||
XBlockStudioConfiguration.objects.create(name='poll', enabled=False, support_level="fs")
|
||||
XBlockStudioConfigurationFlag.objects.create(enabled=True)
|
||||
self.course.advanced_modules.extend(['annotatable', 'poll', 'survey'])
|
||||
templates = get_component_templates(self.course)
|
||||
button_names = [template['display_name'] for template in templates]
|
||||
self.assertIn('Advanced', button_names)
|
||||
self.assertEqual(len(templates[0]['templates']), 1)
|
||||
template_display_names = [template['display_name'] for template in templates[0]['templates']]
|
||||
self.assertEqual(template_display_names, ['Annotation'])
|
||||
# Annotatable doesn't show up because it is unsupported (in test setUp).
|
||||
self._verify_advanced_xblocks(['Survey'], ['ps'])
|
||||
|
||||
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', ['poll'])
|
||||
def test_create_non_deprecated_problems(self):
|
||||
# Now enable unsupported components.
|
||||
self.course.allow_unsupported_xblocks = True
|
||||
self._verify_advanced_xblocks(['Annotation', 'Survey'], ['us', 'ps'])
|
||||
|
||||
# Now disable Annotatable completely through XBlockConfiguration
|
||||
XBlockConfiguration.objects.create(name='annotatable', enabled=False)
|
||||
self._verify_advanced_xblocks(['Survey'], ['ps'])
|
||||
|
||||
def test_create_support_level_flag_off(self):
|
||||
"""
|
||||
Test that we can create problems if they are not deprecated
|
||||
Test that we can create any advanced xblock (that isn't completely disabled through
|
||||
XBlockConfiguration) if XBlockStudioConfigurationFlag is False.
|
||||
"""
|
||||
XBlockStudioConfigurationFlag.objects.create(enabled=False)
|
||||
self.course.advanced_modules.extend(['annotatable', 'survey'])
|
||||
self._verify_advanced_xblocks(['Annotation', 'Survey'], [True, True])
|
||||
|
||||
def test_xblock_masquerading_as_problem(self):
|
||||
"""
|
||||
Test the integration of xblocks masquerading as problems.
|
||||
"""
|
||||
def get_openassessment():
|
||||
""" Helper method to return the openassessment template from problem list """
|
||||
self.templates = get_component_templates(self.course)
|
||||
problem_templates = self.get_templates_of_type('problem')
|
||||
return self.get_template(problem_templates, u'Peer Assessment')
|
||||
|
||||
def verify_openassessment_present(support_level):
|
||||
""" Helper method to verify that openassessment template is present """
|
||||
openassessment = get_openassessment()
|
||||
self.assertIsNotNone(openassessment)
|
||||
self.assertEqual(openassessment.get('category'), 'openassessment')
|
||||
self.assertEqual(openassessment.get('support_level'), support_level)
|
||||
|
||||
verify_openassessment_present(True)
|
||||
|
||||
# Now enable XBlockStudioConfigurationFlag. The openassessment block is marked
|
||||
# unsupported, so will no longer show up.
|
||||
XBlockStudioConfigurationFlag.objects.create(enabled=True)
|
||||
self.assertIsNone(get_openassessment())
|
||||
|
||||
# Now allow unsupported components.
|
||||
self.course.allow_unsupported_xblocks = True
|
||||
verify_openassessment_present('us')
|
||||
|
||||
# Now disable openassessment completely through XBlockConfiguration
|
||||
XBlockConfiguration.objects.create(name='openassessment', enabled=False)
|
||||
self.assertIsNone(get_openassessment())
|
||||
|
||||
def _verify_advanced_xblocks(self, expected_xblocks, expected_support_levels):
|
||||
"""
|
||||
Verify the names of the advanced xblocks showing in the "new component" menu.
|
||||
"""
|
||||
self.course.advanced_modules.extend(['annotatable', 'poll', 'survey'])
|
||||
templates = get_component_templates(self.course)
|
||||
button_names = [template['display_name'] for template in templates]
|
||||
self.assertIn('Advanced', button_names)
|
||||
self.assertEqual(len(templates[0]['templates']), 2)
|
||||
self.assertEqual(len(templates[0]['templates']), len(expected_xblocks))
|
||||
template_display_names = [template['display_name'] for template in templates[0]['templates']]
|
||||
self.assertEqual(template_display_names, ['Annotation', 'Survey'])
|
||||
self.assertEqual(template_display_names, expected_xblocks)
|
||||
template_support_levels = [template['support_level'] for template in templates[0]['templates']]
|
||||
self.assertEqual(template_support_levels, expected_support_levels)
|
||||
|
||||
def _verify_basic_component(self, component_type, display_name, support_level=True):
|
||||
"""
|
||||
Verify the display name and support level of basic components (that have no boilerplates).
|
||||
"""
|
||||
templates = self.get_templates_of_type(component_type)
|
||||
self.assertEqual(1, len(templates))
|
||||
self.assertEqual(display_name, templates[0]['display_name'])
|
||||
self.assertEqual(support_level, templates[0]['support_level'])
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
Django module for Course Metadata class -- manages advanced settings and related parameters
|
||||
"""
|
||||
from xblock.fields import Scope
|
||||
from xblock_django.models import XBlockStudioConfigurationFlag
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.conf import settings
|
||||
|
||||
@@ -93,6 +95,11 @@ class CourseMetadata(object):
|
||||
filtered_list.append('enable_ccx')
|
||||
filtered_list.append('ccx_connector')
|
||||
|
||||
# If the XBlockStudioConfiguration table is not being used, there is no need to
|
||||
# display the "Allow Unsupported XBlocks" setting.
|
||||
if not XBlockStudioConfigurationFlag.is_enabled():
|
||||
filtered_list.append('allow_unsupported_xblocks')
|
||||
|
||||
return filtered_list
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -398,9 +398,6 @@ ADVANCED_SECURITY_CONFIG = ENV_TOKENS.get('ADVANCED_SECURITY_CONFIG', {})
|
||||
################ ADVANCED COMPONENT/PROBLEM TYPES ###############
|
||||
|
||||
ADVANCED_PROBLEM_TYPES = ENV_TOKENS.get('ADVANCED_PROBLEM_TYPES', ADVANCED_PROBLEM_TYPES)
|
||||
DEPRECATED_ADVANCED_COMPONENT_TYPES = ENV_TOKENS.get(
|
||||
'DEPRECATED_ADVANCED_COMPONENT_TYPES', DEPRECATED_ADVANCED_COMPONENT_TYPES
|
||||
)
|
||||
|
||||
################ VIDEO UPLOAD PIPELINE ###############
|
||||
|
||||
|
||||
@@ -1125,19 +1125,6 @@ XBLOCK_SETTINGS = {
|
||||
}
|
||||
}
|
||||
|
||||
################################ XBlock Deprecation ################################
|
||||
|
||||
# The following settings are used for deprecating XBlocks.
|
||||
|
||||
# Adding components in this list will disable the creation of new problems for
|
||||
# those advanced components in Studio. Existing problems will work fine
|
||||
# and one can edit them in Studio.
|
||||
# DEPRECATED. Please use /admin/xblock_django/xblockdisableconfig instead.
|
||||
DEPRECATED_ADVANCED_COMPONENT_TYPES = []
|
||||
|
||||
# XBlocks can be disabled from rendering in LMS Courseware by adding them to
|
||||
# /admin/xblock_django/xblockdisableconfig/.
|
||||
|
||||
################################ Settings for Credit Course Requirements ################################
|
||||
# Initial delay used for retrying tasks.
|
||||
# Additional retries use longer delays.
|
||||
|
||||
@@ -10,7 +10,8 @@ define(["backbone"], function (Backbone) {
|
||||
// category (may or may not match "type")
|
||||
// boilerplate_name (may be null)
|
||||
// is_common (only used for problems)
|
||||
templates: []
|
||||
templates: [],
|
||||
support_legend: {}
|
||||
},
|
||||
parse: function (response) {
|
||||
// Returns true only for templates that both have no boilerplate and are of
|
||||
@@ -24,6 +25,7 @@ define(["backbone"], function (Backbone) {
|
||||
this.type = response.type;
|
||||
this.templates = response.templates;
|
||||
this.display_name = response.display_name;
|
||||
this.support_legend = response.support_legend;
|
||||
|
||||
// Sort the templates.
|
||||
this.templates.sort(function (a, b) {
|
||||
|
||||
@@ -49,7 +49,8 @@ define(["js/models/component_template"],
|
||||
"boilerplate_name": "alternate_word_cloud.yaml",
|
||||
"display_name": "Word Cloud"
|
||||
}],
|
||||
"type": "problem"
|
||||
"type": "problem",
|
||||
"support_legend": {"show_legend": false}
|
||||
};
|
||||
|
||||
it('orders templates correctly', function () {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
define(["jquery", "underscore", "underscore.string", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers",
|
||||
"common/js/spec_helpers/template_helpers", "js/spec_helpers/edit_helpers",
|
||||
"js/views/pages/container", "js/views/pages/paged_container", "js/models/xblock_info", "jquery.simulate"],
|
||||
function ($, _, str, AjaxHelpers, TemplateHelpers, EditHelpers, ContainerPage, PagedContainerPage, XBlockInfo) {
|
||||
"js/views/pages/container", "js/views/pages/paged_container", "js/models/xblock_info",
|
||||
"js/collections/component_template", "jquery.simulate"],
|
||||
function ($, _, str, AjaxHelpers, TemplateHelpers, EditHelpers, ContainerPage, PagedContainerPage,
|
||||
XBlockInfo, ComponentTemplates) {
|
||||
'use strict';
|
||||
|
||||
function parameterized_suite(label, globalPageOptions) {
|
||||
@@ -55,18 +57,19 @@ define(["jquery", "underscore", "underscore.string", "edx-ui-toolkit/js/utils/sp
|
||||
);
|
||||
};
|
||||
|
||||
getContainerPage = function (options) {
|
||||
getContainerPage = function (options, componentTemplates) {
|
||||
var default_options = {
|
||||
model: model,
|
||||
templates: EditHelpers.mockComponentTemplates,
|
||||
templates: componentTemplates === undefined ?
|
||||
EditHelpers.mockComponentTemplates : componentTemplates,
|
||||
el: $('#content')
|
||||
};
|
||||
return new PageClass(_.extend(options || {}, globalPageOptions, default_options));
|
||||
};
|
||||
|
||||
renderContainerPage = function (test, html, options) {
|
||||
renderContainerPage = function (test, html, options, componentTemplates) {
|
||||
requests = AjaxHelpers.requests(test);
|
||||
containerPage = getContainerPage(options);
|
||||
containerPage = getContainerPage(options, componentTemplates);
|
||||
containerPage.render();
|
||||
respondWithHtml(html);
|
||||
AjaxHelpers.expectJsonRequest(requests, 'GET', '/xblock/locator-container');
|
||||
@@ -652,6 +655,138 @@ define(["jquery", "underscore", "underscore.string", "edx-ui-toolkit/js/utils/sp
|
||||
"parent_locator": "locator-group-A"
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show the support legend if show_legend is false', function () {
|
||||
// By default, show_legend is false in the mock component Templates.
|
||||
renderContainerPage(this, mockContainerXBlockHtml);
|
||||
showTemplatePicker();
|
||||
expect(containerPage.$('.support-documentation').length).toBe(0);
|
||||
});
|
||||
|
||||
it('does show the support legend if show_legend is true', function () {
|
||||
var templates = new ComponentTemplates([
|
||||
{
|
||||
"templates": [
|
||||
{
|
||||
"category": "html",
|
||||
"boilerplate_name": null,
|
||||
"display_name": "Text"
|
||||
}, {
|
||||
"category": "html",
|
||||
"boilerplate_name": "announcement.yaml",
|
||||
"display_name": "Announcement"
|
||||
}, {
|
||||
"category": "html",
|
||||
"boilerplate_name": "raw.yaml",
|
||||
"display_name": "Raw HTML"
|
||||
}],
|
||||
"type": "html",
|
||||
"support_legend": {
|
||||
"show_legend": true,
|
||||
"documentation_label": "Documentation Label:",
|
||||
"allow_unsupported_xblocks": false
|
||||
}
|
||||
}],
|
||||
{
|
||||
parse: true
|
||||
}), supportDocumentation;
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {}, templates);
|
||||
showTemplatePicker();
|
||||
supportDocumentation = containerPage.$('.support-documentation');
|
||||
// On this page, groups are being shown, each of which has a new component menu.
|
||||
expect(supportDocumentation.length).toBeGreaterThan(0);
|
||||
|
||||
// check that the documentation label is displayed
|
||||
expect($(supportDocumentation[0]).find('.support-documentation-link').text().trim())
|
||||
.toBe('Documentation Label:');
|
||||
|
||||
// show_unsupported_xblocks is false, so only 2 support levels should be shown
|
||||
expect($(supportDocumentation[0]).find('.support-documentation-level').length).toBe(2);
|
||||
});
|
||||
|
||||
it('does show unsupported level if enabled', function () {
|
||||
var templates = new ComponentTemplates([
|
||||
{
|
||||
"templates": [
|
||||
{
|
||||
"category": "html",
|
||||
"boilerplate_name": null,
|
||||
"display_name": "Text"
|
||||
}, {
|
||||
"category": "html",
|
||||
"boilerplate_name": "announcement.yaml",
|
||||
"display_name": "Announcement"
|
||||
}, {
|
||||
"category": "html",
|
||||
"boilerplate_name": "raw.yaml",
|
||||
"display_name": "Raw HTML"
|
||||
}],
|
||||
"type": "html",
|
||||
"support_legend": {
|
||||
"show_legend": true,
|
||||
"documentation_label": "Documentation Label:",
|
||||
"allow_unsupported_xblocks": true
|
||||
}
|
||||
}],
|
||||
{
|
||||
parse: true
|
||||
}), supportDocumentation;
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {}, templates);
|
||||
showTemplatePicker();
|
||||
supportDocumentation = containerPage.$('.support-documentation');
|
||||
|
||||
// show_unsupported_xblocks is true, so 3 support levels should be shown
|
||||
expect($(supportDocumentation[0]).find('.support-documentation-level').length).toBe(3);
|
||||
|
||||
// verify only one has the unsupported item
|
||||
expect($(supportDocumentation[0]).find('.fa-circle-o').length).toBe(1);
|
||||
});
|
||||
|
||||
it('does render support level indicators if present in JSON', function () {
|
||||
var templates = new ComponentTemplates([
|
||||
{
|
||||
"templates": [
|
||||
{
|
||||
"category": "html",
|
||||
"boilerplate_name": null,
|
||||
"display_name": "Text",
|
||||
"support_level": "fs"
|
||||
}, {
|
||||
"category": "html",
|
||||
"boilerplate_name": "announcement.yaml",
|
||||
"display_name": "Announcement",
|
||||
"support_level": "ps"
|
||||
}, {
|
||||
"category": "html",
|
||||
"boilerplate_name": "raw.yaml",
|
||||
"display_name": "Raw HTML",
|
||||
"support_level": "us"
|
||||
}],
|
||||
"type": "html",
|
||||
"support_legend": {
|
||||
"show_legend": true,
|
||||
"documentation_label": "Documentation Label:",
|
||||
"allow_unsupported_xblocks": true
|
||||
}
|
||||
}],
|
||||
{
|
||||
parse: true
|
||||
}), supportLevelIndicators, getScreenReaderText;
|
||||
renderContainerPage(this, mockContainerXBlockHtml, {}, templates);
|
||||
showTemplatePicker();
|
||||
|
||||
supportLevelIndicators = $(containerPage.$('.new-component-template')[0])
|
||||
.find('.support-level');
|
||||
expect(supportLevelIndicators.length).toBe(3);
|
||||
|
||||
getScreenReaderText = function(index){
|
||||
return $($(supportLevelIndicators[index]).siblings()[0]).text().trim();
|
||||
};
|
||||
// Verify one level of each type was rendered.
|
||||
expect(getScreenReaderText(0)).toBe('Fully Supported');
|
||||
expect(getScreenReaderText(1)).toBe('Provisionally Supported');
|
||||
expect(getScreenReaderText(2)).toBe('Not Supported');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,12 +41,13 @@ define(["jquery", "underscore", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpe
|
||||
|
||||
mockComponentTemplates = new ComponentTemplates([
|
||||
{
|
||||
templates: [
|
||||
"templates": [
|
||||
{
|
||||
category: 'discussion',
|
||||
display_name: 'Discussion'
|
||||
"category": "discussion",
|
||||
"display_name": "Discussion"
|
||||
}],
|
||||
type: 'discussion'
|
||||
"type": "discussion",
|
||||
"support_legend": {"show_legend": false}
|
||||
}, {
|
||||
"templates": [
|
||||
{
|
||||
@@ -62,7 +63,8 @@ define(["jquery", "underscore", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpe
|
||||
"boilerplate_name": "raw.yaml",
|
||||
"display_name": "Raw HTML"
|
||||
}],
|
||||
"type": "html"
|
||||
"type": "html",
|
||||
"support_legend": {"show_legend": false}
|
||||
}],
|
||||
{
|
||||
parse: true
|
||||
@@ -76,6 +78,8 @@ define(["jquery", "underscore", "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpe
|
||||
TemplateHelpers.installTemplate('add-xblock-component-button');
|
||||
TemplateHelpers.installTemplate('add-xblock-component-menu');
|
||||
TemplateHelpers.installTemplate('add-xblock-component-menu-problem');
|
||||
TemplateHelpers.installTemplate('add-xblock-component-support-legend');
|
||||
TemplateHelpers.installTemplate('add-xblock-component-support-level');
|
||||
|
||||
// Add templates needed by the edit XBlock modal
|
||||
TemplateHelpers.installTemplate('edit-xblock-modal');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
define(["jquery", "js/views/baseview"],
|
||||
function ($, BaseView) {
|
||||
define(["jquery", "js/views/baseview", 'edx-ui-toolkit/js/utils/html-utils'],
|
||||
function ($, BaseView, HtmlUtils) {
|
||||
|
||||
return BaseView.extend({
|
||||
className: function () {
|
||||
@@ -9,8 +9,19 @@ define(["jquery", "js/views/baseview"],
|
||||
BaseView.prototype.initialize.call(this);
|
||||
var template_name = this.model.type === "problem" ? "add-xblock-component-menu-problem" :
|
||||
"add-xblock-component-menu";
|
||||
var support_indicator_template = this.loadTemplate("add-xblock-component-support-level");
|
||||
var support_legend_template = this.loadTemplate("add-xblock-component-support-legend");
|
||||
this.template = this.loadTemplate(template_name);
|
||||
this.$el.html(this.template({type: this.model.type, templates: this.model.templates}));
|
||||
HtmlUtils.setHtml(
|
||||
this.$el,
|
||||
HtmlUtils.HTML(this.template({
|
||||
type: this.model.type, templates: this.model.templates,
|
||||
support_legend: this.model.support_legend,
|
||||
support_indicator_template: support_indicator_template,
|
||||
support_legend_template: support_legend_template,
|
||||
HtmlUtils: HtmlUtils
|
||||
}))
|
||||
);
|
||||
// Make the tabs on problems into "real tabs"
|
||||
this.$('.tab-group').tabs();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
// +Base - Utilities
|
||||
// ====================
|
||||
@import 'variables';
|
||||
@import 'partials/variables';
|
||||
@import 'mixins';
|
||||
@import 'mixins-inherited';
|
||||
|
||||
|
||||
@@ -168,18 +168,47 @@
|
||||
|
||||
// specific menu types
|
||||
&.new-component-problem {
|
||||
padding-bottom: ($baseline/2);
|
||||
|
||||
.problem-type-tabs {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.support-documentation {
|
||||
float: right;
|
||||
@include margin($baseline, 0, ($baseline/2), ($baseline/2));
|
||||
@include font-size(14);
|
||||
|
||||
.support-documentation-level {
|
||||
padding-right: ($baseline/2);
|
||||
}
|
||||
|
||||
.support-documentation-link {
|
||||
// Override JQuery ui-widget-content link color (black) with our usual link color and hover action.
|
||||
color: $uxpl-blue-base;
|
||||
text-decoration: none;
|
||||
padding-right: ($baseline/2);
|
||||
|
||||
&:hover {
|
||||
color: $uxpl-blue-hover-active;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.support-level {
|
||||
padding-right: ($baseline/2);
|
||||
}
|
||||
|
||||
.icon {
|
||||
color: $uxpl-primary-accent;
|
||||
}
|
||||
}
|
||||
|
||||
// individual menus
|
||||
// --------------------
|
||||
.new-component-template {
|
||||
@include clearfix();
|
||||
margin-bottom: 0;
|
||||
|
||||
li {
|
||||
border: none;
|
||||
@@ -190,7 +219,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.button-component {
|
||||
.button-component {
|
||||
@include clearfix();
|
||||
@include transition(none);
|
||||
@extend %t-demi-strong;
|
||||
@@ -201,11 +230,16 @@
|
||||
background: $white;
|
||||
color: $gray-d3;
|
||||
text-align: left;
|
||||
font-family: $f-sans-serif;
|
||||
|
||||
&:hover {
|
||||
@include transition(background-color $tmg-f2 linear 0s);
|
||||
background: tint($green,30%);
|
||||
color: $white;
|
||||
|
||||
.icon {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,16 @@ $f-monospace: 'Bitstream Vera Sans Mono', Consolas, Courier, monospace;
|
||||
// ====================
|
||||
$transparent: rgba(0,0,0,0); // used when color value is needed for UI width/transitions but element is transparent
|
||||
|
||||
// +Colors - UXPL new pattern library colors
|
||||
// ====================
|
||||
$uxpl-blue-base: rgba(0, 116, 180, 1); // wcag2a compliant
|
||||
$uxpl-blue-hover-active: lighten($uxpl-blue-base, 7%); // wcag2a compliant
|
||||
|
||||
$uxpl-green-base: rgba(0, 129, 0, 1); // wcag2a compliant
|
||||
$uxpl-green-hover-active: lighten($uxpl-green-base, 7%); // wcag2a compliant
|
||||
|
||||
$uxpl-primary-accent: rgb(14, 166, 236);
|
||||
|
||||
// +Colors - Primary
|
||||
// ====================
|
||||
$black: rgb(0,0,0);
|
||||
@@ -88,12 +98,6 @@ $blue-t1: rgba($blue, 0.25);
|
||||
$blue-t2: rgba($blue, 0.50);
|
||||
$blue-t3: rgba($blue, 0.75);
|
||||
|
||||
$uxpl-blue-base: rgba(0, 116, 180, 1); // wcag2a compliant
|
||||
$uxpl-blue-hover-active: lighten($uxpl-blue-base, 7%); // wcag2a compliant
|
||||
|
||||
$uxpl-green-base: rgba(0, 129, 0, 1); // wcag2a compliant
|
||||
$uxpl-green-hover-active: lighten($uxpl-green-base, 7%); // wcag2a compliant
|
||||
|
||||
$pink: rgb(183, 37, 103); // #b72567;
|
||||
$pink-l1: tint($pink,20%);
|
||||
$pink-l2: tint($pink,40%);
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
%>">
|
||||
<ul class="problem-type-tabs nav-tabs" tabindex='-1'>
|
||||
<li class="current">
|
||||
<a class="link-tab" href="#tab1"><%= gettext("Common Problem Types") %></a>
|
||||
<a class="link-tab" href="#tab1"><%- gettext("Common Problem Types") %></a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-tab" href="#tab2"><%= gettext("Advanced") %></a>
|
||||
<a class="link-tab" href="#tab2"><%- gettext("Advanced") %></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab current" id="tab1">
|
||||
@@ -19,15 +19,17 @@
|
||||
<% if (templates[i].tab == "common") { %>
|
||||
<% if (!templates[i].boilerplate_name) { %>
|
||||
<li class="editor-md empty">
|
||||
<button type="button" class="button-component" data-category="<%= templates[i].category %>">
|
||||
<span class="name"><%= templates[i].display_name %></span>
|
||||
<button type="button" class="button-component" data-category="<%- templates[i].category %>">
|
||||
<%= HtmlUtils.HTML(support_indicator_template({support_level: templates[i].support_level})) %>
|
||||
<span class="name"><%- templates[i].display_name %></span>
|
||||
</button>
|
||||
</li>
|
||||
<% } else { %>
|
||||
<li class="editor-md">
|
||||
<button type="button" class="button-component" data-category="<%= templates[i].category %>"
|
||||
data-boilerplate="<%= templates[i].boilerplate_name %>">
|
||||
<span class="name"><%= templates[i].display_name %></span>
|
||||
<button type="button" class="button-component" data-category="<%- templates[i].category %>"
|
||||
data-boilerplate="<%- templates[i].boilerplate_name %>">
|
||||
<%= HtmlUtils.HTML(support_indicator_template({support_level: templates[i].support_level})) %>
|
||||
<span class="name"><%- templates[i].display_name %></span>
|
||||
</button>
|
||||
</li>
|
||||
<% } %>
|
||||
@@ -40,14 +42,16 @@
|
||||
<% for (var i = 0; i < templates.length; i++) { %>
|
||||
<% if (templates[i].tab == "advanced") { %>
|
||||
<li class="editor-manual">
|
||||
<button type="button" class="button-component" data-category="<%= templates[i].category %>"
|
||||
data-boilerplate="<%= templates[i].boilerplate_name %>">
|
||||
<span class="name"><%= templates[i].display_name %></span>
|
||||
<button type="button" class="button-component" data-category="<%- templates[i].category %>"
|
||||
data-boilerplate="<%- templates[i].boilerplate_name %>">
|
||||
<%= HtmlUtils.HTML(support_indicator_template({support_level: templates[i].support_level})) %>
|
||||
<span class="name"><%- templates[i].display_name %></span>
|
||||
</button>
|
||||
</li>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</ul>
|
||||
</div>
|
||||
<button class="cancel-button" data-type="<%= type %>"><%= gettext("Cancel") %></button>
|
||||
<button class="cancel-button" data-type="<%- type %>"><%- gettext("Cancel") %></button>
|
||||
<%= HtmlUtils.HTML(support_legend_template({support_legend: support_legend})) %>
|
||||
</div>
|
||||
|
||||
@@ -10,20 +10,23 @@
|
||||
<% for (var i = 0; i < templates.length; i++) { %>
|
||||
<% if (!templates[i].boilerplate_name) { %>
|
||||
<li class="editor-md empty">
|
||||
<button type="button" class="button-component" data-category="<%= templates[i].category %>">
|
||||
<span class="name"><%= templates[i].display_name %></span>
|
||||
<button type="button" class="button-component" data-category="<%- templates[i].category %>">
|
||||
<%= HtmlUtils.HTML(support_indicator_template({support_level: templates[i].support_level})) %>
|
||||
<span class="name"><%- templates[i].display_name %></span>
|
||||
</button>
|
||||
</li>
|
||||
<% } else { %>
|
||||
<li class="editor-md">
|
||||
<button type="button" class="button-component" data-category="<%= templates[i].category %>"
|
||||
data-boilerplate="<%= templates[i].boilerplate_name %>">
|
||||
<span class="name"><%= templates[i].display_name %></span>
|
||||
<button type="button" class="button-component" data-category="<%- templates[i].category %>"
|
||||
data-boilerplate="<%- templates[i].boilerplate_name %>">
|
||||
<%= HtmlUtils.HTML(support_indicator_template({support_level: templates[i].support_level})) %>
|
||||
<span class="name"><%- templates[i].display_name %></span>
|
||||
</button>
|
||||
</li>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</ul>
|
||||
<button class="cancel-button" data-type="<%= type %>"><%= gettext("Cancel") %></button>
|
||||
<button class="cancel-button" data-type="<%- type %>"><%- gettext("Cancel") %></button>
|
||||
<%= HtmlUtils.HTML(support_legend_template({support_legend: support_legend})) %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<% if (support_legend.show_legend) { %>
|
||||
<span class="support-documentation">
|
||||
<a class="support-documentation-link"
|
||||
href="http://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/create_exercises_and_tools.html#levels-of-support-for-tools" target="_blank">
|
||||
<%- support_legend.documentation_label %>
|
||||
</a>
|
||||
<span class="support-documentation-level">
|
||||
<span class="icon fa fa-circle" aria-hidden="true"></span>
|
||||
<span><%- gettext('Supported') %></span>
|
||||
</span>
|
||||
<span class="support-documentation-level">
|
||||
<span class="icon fa fa-adjust" aria-hidden="true"></span>
|
||||
<span><%- gettext('Provisional') %></span>
|
||||
</span>
|
||||
<% if (support_legend.allow_unsupported_xblocks) { %>
|
||||
<span class="support-documentation-level">
|
||||
<span class="icon fa fa-circle-o" aria-hidden="true"></span>
|
||||
<span><%- gettext('Not Supported') %></span>
|
||||
</span>
|
||||
<% } %>
|
||||
</span>
|
||||
<% } %>
|
||||
@@ -0,0 +1,10 @@
|
||||
<% if (support_level === "fs"){ %>
|
||||
<span class="icon support-level fa fa-circle" aria-hidden="true"></span>
|
||||
<span class="sr"><%- gettext('Fully Supported') %></span>
|
||||
<% } else if (support_level === "ps"){ %>
|
||||
<span class="icon support-level fa fa-adjust" aria-hidden="true"></span>
|
||||
<span class="sr"><%- gettext('Provisionally Supported') %></span>
|
||||
<% } else if (support_level === "us"){ %>
|
||||
<span class="icon support-level fa fa-circle-o" aria-hidden="true"></span>
|
||||
<span class="sr"><%- gettext('Not Supported') %></span>
|
||||
<% } %>
|
||||
@@ -22,11 +22,11 @@ def disabled_xblocks():
|
||||
|
||||
def authorable_xblocks(allow_unsupported=False, name=None):
|
||||
"""
|
||||
If Studio XBlock support state is enabled (via `XBlockStudioConfigurationFlag`), this method returns
|
||||
the QuerySet of XBlocks that can be created in Studio (by default, only fully supported and provisionally
|
||||
supported). If `XBlockStudioConfigurationFlag` is not enabled, this method returns None.
|
||||
Note that this method does not take into account fully disabled xblocks (as returned
|
||||
by `disabled_xblocks`) or deprecated xblocks (as returned by `deprecated_xblocks`).
|
||||
This method returns the QuerySet of XBlocks that can be created in Studio (by default, only fully supported
|
||||
and provisionally supported XBlocks), as stored in `XBlockStudioConfiguration`.
|
||||
Note that this method does NOT check the value `XBlockStudioConfigurationFlag`, nor does it take into account
|
||||
fully disabled xblocks (as returned by `disabled_xblocks`) or deprecated xblocks
|
||||
(as returned by `deprecated_xblocks`).
|
||||
|
||||
Arguments:
|
||||
allow_unsupported (bool): If `True`, enabled but unsupported XBlocks will also be returned.
|
||||
@@ -36,13 +36,10 @@ def authorable_xblocks(allow_unsupported=False, name=None):
|
||||
name (str): If provided, filters the returned XBlocks to those with the provided name. This is
|
||||
useful for XBlocks with lots of template types.
|
||||
Returns:
|
||||
QuerySet: If `XBlockStudioConfigurationFlag` is enabled, returns authorable XBlocks,
|
||||
taking into account `support_level`, `enabled` and `name` (if specified).
|
||||
If `XBlockStudioConfigurationFlag` is disabled, returns None.
|
||||
QuerySet: Returns authorable XBlocks, taking into account `support_level`, `enabled` and `name`
|
||||
(if specified) as specified by `XBlockStudioConfiguration`. Does not take into account whether or not
|
||||
`XBlockStudioConfigurationFlag` is enabled.
|
||||
"""
|
||||
if not XBlockStudioConfigurationFlag.is_enabled():
|
||||
return None
|
||||
|
||||
blocks = XBlockStudioConfiguration.objects.current_set().filter(enabled=True)
|
||||
if not allow_unsupported:
|
||||
blocks = blocks.exclude(support_level=XBlockStudioConfiguration.UNSUPPORTED)
|
||||
|
||||
@@ -41,27 +41,10 @@ class XBlockDisableConfig(ConfigurationModel):
|
||||
|
||||
return block_type in config.disabled_blocks.split()
|
||||
|
||||
@classmethod
|
||||
def disabled_create_block_types(cls):
|
||||
""" Return list of deprecated XBlock types. Merges types in settings file and field. """
|
||||
|
||||
config = cls.current()
|
||||
xblock_types = config.disabled_create_blocks.split() if config.enabled else []
|
||||
|
||||
# Merge settings list with one in the admin config;
|
||||
if hasattr(settings, 'DEPRECATED_ADVANCED_COMPONENT_TYPES'):
|
||||
xblock_types.extend(
|
||||
xblock_type for xblock_type in settings.DEPRECATED_ADVANCED_COMPONENT_TYPES
|
||||
if xblock_type not in xblock_types
|
||||
)
|
||||
|
||||
return xblock_types
|
||||
|
||||
def __unicode__(self):
|
||||
config = XBlockDisableConfig.current()
|
||||
return u"Disabled xblocks = {disabled_xblocks}\nDeprecated xblocks = {disabled_create_block_types}".format(
|
||||
disabled_xblocks=config.disabled_blocks,
|
||||
disabled_create_block_types=config.disabled_create_block_types
|
||||
return u"Disabled xblocks = {disabled_xblocks}".format(
|
||||
disabled_xblocks=config.disabled_blocks
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -61,28 +61,21 @@ class XBlockSupportTestCase(CacheIsolationTestCase):
|
||||
disabled_xblock_names = [block.name for block in disabled_xblocks()]
|
||||
self.assertItemsEqual(["survey", "poll"], disabled_xblock_names)
|
||||
|
||||
def test_authorable_blocks_flag_disabled(self):
|
||||
"""
|
||||
Tests authorable_xblocks returns None if the configuration flag is not enabled.
|
||||
"""
|
||||
self.assertFalse(XBlockStudioConfigurationFlag.is_enabled())
|
||||
self.assertIsNone(authorable_xblocks())
|
||||
|
||||
def test_authorable_blocks_empty_model(self):
|
||||
"""
|
||||
Tests authorable_xblocks returns an empty list if the configuration flag is enabled but
|
||||
the XBlockStudioConfiguration table is empty.
|
||||
Tests authorable_xblocks returns an empty list if XBlockStudioConfiguration table is empty, regardless
|
||||
of whether or not XBlockStudioConfigurationFlag is enabled.
|
||||
"""
|
||||
XBlockStudioConfigurationFlag(enabled=True).save()
|
||||
XBlockStudioConfiguration.objects.all().delete()
|
||||
self.assertFalse(XBlockStudioConfigurationFlag.is_enabled())
|
||||
self.assertEqual(0, len(authorable_xblocks(allow_unsupported=True)))
|
||||
XBlockStudioConfigurationFlag(enabled=True).save()
|
||||
self.assertEqual(0, len(authorable_xblocks(allow_unsupported=True)))
|
||||
|
||||
def test_authorable_blocks(self):
|
||||
"""
|
||||
Tests authorable_xblocks when configuration flag is enabled and name is not specified.
|
||||
Tests authorable_xblocks when name is not specified.
|
||||
"""
|
||||
XBlockStudioConfigurationFlag(enabled=True).save()
|
||||
|
||||
authorable_xblock_names = [block.name for block in authorable_xblocks()]
|
||||
self.assertItemsEqual(["done", "problem", "problem", "html"], authorable_xblock_names)
|
||||
|
||||
@@ -99,7 +92,7 @@ class XBlockSupportTestCase(CacheIsolationTestCase):
|
||||
|
||||
def test_authorable_blocks_by_name(self):
|
||||
"""
|
||||
Tests authorable_xblocks when configuration flag is enabled and name is specified.
|
||||
Tests authorable_xblocks when name is specified.
|
||||
"""
|
||||
def verify_xblock_fields(name, template, support_level, block):
|
||||
"""
|
||||
@@ -109,8 +102,6 @@ class XBlockSupportTestCase(CacheIsolationTestCase):
|
||||
self.assertEqual(template, block.template)
|
||||
self.assertEqual(support_level, block.support_level)
|
||||
|
||||
XBlockStudioConfigurationFlag(enabled=True).save()
|
||||
|
||||
# There are no xblocks with name video.
|
||||
authorable_blocks = authorable_xblocks(name="video")
|
||||
self.assertEqual(0, len(authorable_blocks))
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
"""
|
||||
Tests for deprecated xblocks in XBlockDisableConfig.
|
||||
"""
|
||||
import ddt
|
||||
|
||||
from mock import patch
|
||||
from django.test import TestCase
|
||||
from xblock_django.models import XBlockDisableConfig
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class XBlockDisableConfigTestCase(TestCase):
|
||||
"""
|
||||
Tests for the DjangoXBlockUserService.
|
||||
"""
|
||||
def setUp(self):
|
||||
super(XBlockDisableConfigTestCase, self).setUp()
|
||||
|
||||
# Initialize the deprecated modules settings with empty list
|
||||
XBlockDisableConfig.objects.create(
|
||||
disabled_blocks='', enabled=True
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
('poll', ['poll']),
|
||||
('poll survey annotatable textannotation', ['poll', 'survey', 'annotatable', 'textannotation']),
|
||||
('', [])
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_deprecated_blocks_splitting(self, xblocks, expected_result):
|
||||
"""
|
||||
Tests that it correctly splits the xblocks defined in field.
|
||||
"""
|
||||
XBlockDisableConfig.objects.create(
|
||||
disabled_create_blocks=xblocks, enabled=True
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
XBlockDisableConfig.disabled_create_block_types(), expected_result
|
||||
)
|
||||
|
||||
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', ['poll', 'survey'])
|
||||
def test_deprecated_blocks_file(self):
|
||||
"""
|
||||
Tests that deprecated modules contain entries from settings file DEPRECATED_ADVANCED_COMPONENT_TYPES
|
||||
"""
|
||||
self.assertEqual(XBlockDisableConfig.disabled_create_block_types(), ['poll', 'survey'])
|
||||
|
||||
@patch('django.conf.settings.DEPRECATED_ADVANCED_COMPONENT_TYPES', ['poll', 'survey'])
|
||||
def test_deprecated_blocks_file_and_config(self):
|
||||
"""
|
||||
Tests that deprecated types defined in both settings and config model are read.
|
||||
"""
|
||||
XBlockDisableConfig.objects.create(
|
||||
disabled_create_blocks='annotatable', enabled=True
|
||||
)
|
||||
|
||||
self.assertEqual(XBlockDisableConfig.disabled_create_block_types(), ['annotatable', 'poll', 'survey'])
|
||||
@@ -408,7 +408,7 @@ class CourseFields(object):
|
||||
)
|
||||
advanced_modules = List(
|
||||
display_name=_("Advanced Module List"),
|
||||
help=_("Enter the names of the advanced components to use in your course."),
|
||||
help=_("Enter the names of the advanced modules to use in your course."),
|
||||
scope=Scope.settings
|
||||
)
|
||||
has_children = True
|
||||
@@ -830,6 +830,15 @@ class CourseFields(object):
|
||||
},
|
||||
scope=Scope.settings
|
||||
)
|
||||
allow_unsupported_xblocks = Boolean(
|
||||
display_name=_("Add Unsupported Problems and Tools"),
|
||||
help=_(
|
||||
"Enter true or false. If true, you can add unsupported problems and tools to your course in Studio. "
|
||||
"Unsupported problems and tools are not recommended for use in courses due to non-compliance with one or "
|
||||
"more of the base requirements, such as testing, accessibility, internationalization, and documentation."
|
||||
),
|
||||
scope=Scope.settings, default=False
|
||||
)
|
||||
|
||||
|
||||
class CourseModule(CourseFields, SequenceModule): # pylint: disable=abstract-method
|
||||
|
||||
@@ -2901,10 +2901,6 @@ APP_UPGRADE_CACHE_TIMEOUT = 3600
|
||||
# if you want to avoid an overlap in ids while searching for history across the two tables.
|
||||
STUDENTMODULEHISTORYEXTENDED_OFFSET = 10000
|
||||
|
||||
# Deprecated xblock types
|
||||
DEPRECATED_ADVANCED_COMPONENT_TYPES = []
|
||||
|
||||
|
||||
# Cutoff date for granting audit certificates
|
||||
|
||||
AUDIT_CERT_CUTOFF_DATE = None
|
||||
|
||||
Reference in New Issue
Block a user