diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index 0f87ced3f1..e30ef1ff57 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -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 diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 5171d59059..f18e1c98b3 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -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, diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py index 68c7d78e18..d46760b0f6 100644 --- a/cms/djangoapps/contentstore/views/component.py +++ b/cms/djangoapps/contentstore/views/component.py @@ -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): """ diff --git a/cms/djangoapps/contentstore/views/tests/test_item.py b/cms/djangoapps/contentstore/views/tests/test_item.py index 490a7bdfe6..41c8650491 100644 --- a/cms/djangoapps/contentstore/views/tests/test_item.py +++ b/cms/djangoapps/contentstore/views/tests/test_item.py @@ -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 diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 2095dfd18f..157bb3c7d1 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -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 diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 3a07499311..8c0f75a8dd 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -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 ############### diff --git a/cms/envs/common.py b/cms/envs/common.py index 20efab36b0..28795075f4 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -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. diff --git a/cms/static/js/models/component_template.js b/cms/static/js/models/component_template.js index 3b55f559dc..39356d9feb 100644 --- a/cms/static/js/models/component_template.js +++ b/cms/static/js/models/component_template.js @@ -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) { diff --git a/cms/static/js/spec/models/component_template_spec.js b/cms/static/js/spec/models/component_template_spec.js index 1e544e1a97..871cf88456 100644 --- a/cms/static/js/spec/models/component_template_spec.js +++ b/cms/static/js/spec/models/component_template_spec.js @@ -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 () { diff --git a/cms/static/js/spec/views/pages/container_spec.js b/cms/static/js/spec/views/pages/container_spec.js index 9d6ec66a11..af49575a78 100644 --- a/cms/static/js/spec/views/pages/container_spec.js +++ b/cms/static/js/spec/views/pages/container_spec.js @@ -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'); + }); }); }); }); diff --git a/cms/static/js/spec_helpers/edit_helpers.js b/cms/static/js/spec_helpers/edit_helpers.js index 7c355efc62..885e5f7bf9 100644 --- a/cms/static/js/spec_helpers/edit_helpers.js +++ b/cms/static/js/spec_helpers/edit_helpers.js @@ -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'); diff --git a/cms/static/js/views/components/add_xblock_menu.js b/cms/static/js/views/components/add_xblock_menu.js index e3a148e5b3..811528afd0 100644 --- a/cms/static/js/views/components/add_xblock_menu.js +++ b/cms/static/js/views/components/add_xblock_menu.js @@ -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(); } diff --git a/cms/static/sass/_build-v1.scss b/cms/static/sass/_build-v1.scss index e65d9ee191..23b66d8db7 100644 --- a/cms/static/sass/_build-v1.scss +++ b/cms/static/sass/_build-v1.scss @@ -10,7 +10,7 @@ // +Base - Utilities // ==================== -@import 'variables'; +@import 'partials/variables'; @import 'mixins'; @import 'mixins-inherited'; diff --git a/cms/static/sass/elements/_modules.scss b/cms/static/sass/elements/_modules.scss index 33805dec34..af82c1c87f 100644 --- a/cms/static/sass/elements/_modules.scss +++ b/cms/static/sass/elements/_modules.scss @@ -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; + } } } } diff --git a/cms/static/sass/partials/_variables.scss b/cms/static/sass/partials/_variables.scss index 44931cdb0c..746be35ea1 100644 --- a/cms/static/sass/partials/_variables.scss +++ b/cms/static/sass/partials/_variables.scss @@ -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%); diff --git a/cms/templates/js/add-xblock-component-menu-problem.underscore b/cms/templates/js/add-xblock-component-menu-problem.underscore index 40f3169d91..de166bda4d 100644 --- a/cms/templates/js/add-xblock-component-menu-problem.underscore +++ b/cms/templates/js/add-xblock-component-menu-problem.underscore @@ -7,10 +7,10 @@ %>">
@@ -19,15 +19,17 @@ <% if (templates[i].tab == "common") { %> <% if (!templates[i].boilerplate_name) { %>
  • -
  • <% } else { %>
  • -
  • <% } %> @@ -40,14 +42,16 @@ <% for (var i = 0; i < templates.length; i++) { %> <% if (templates[i].tab == "advanced") { %>
  • -
  • <% } %> <% } %>
    - + + <%= HtmlUtils.HTML(support_legend_template({support_legend: support_legend})) %> diff --git a/cms/templates/js/add-xblock-component-menu.underscore b/cms/templates/js/add-xblock-component-menu.underscore index f5f9760648..2e31f06bf5 100644 --- a/cms/templates/js/add-xblock-component-menu.underscore +++ b/cms/templates/js/add-xblock-component-menu.underscore @@ -10,20 +10,23 @@ <% for (var i = 0; i < templates.length; i++) { %> <% if (!templates[i].boilerplate_name) { %>
  • -
  • <% } else { %>
  • -
  • <% } %> <% } %> - + + <%= HtmlUtils.HTML(support_legend_template({support_legend: support_legend})) %> <% } %> diff --git a/cms/templates/js/add-xblock-component-support-legend.underscore b/cms/templates/js/add-xblock-component-support-legend.underscore new file mode 100644 index 0000000000..488d0e1f69 --- /dev/null +++ b/cms/templates/js/add-xblock-component-support-legend.underscore @@ -0,0 +1,22 @@ +<% if (support_legend.show_legend) { %> + + + <%- support_legend.documentation_label %> + + + + <%- gettext('Supported') %> + + + + <%- gettext('Provisional') %> + + <% if (support_legend.allow_unsupported_xblocks) { %> + + + <%- gettext('Not Supported') %> + + <% } %> + +<% } %> diff --git a/cms/templates/js/add-xblock-component-support-level.underscore b/cms/templates/js/add-xblock-component-support-level.underscore new file mode 100644 index 0000000000..f2b8ebc090 --- /dev/null +++ b/cms/templates/js/add-xblock-component-support-level.underscore @@ -0,0 +1,10 @@ +<% if (support_level === "fs"){ %> + + <%- gettext('Fully Supported') %> +<% } else if (support_level === "ps"){ %> + + <%- gettext('Provisionally Supported') %> +<% } else if (support_level === "us"){ %> + + <%- gettext('Not Supported') %> +<% } %> diff --git a/common/djangoapps/xblock_django/api.py b/common/djangoapps/xblock_django/api.py index 1abb431916..1173ade5c2 100644 --- a/common/djangoapps/xblock_django/api.py +++ b/common/djangoapps/xblock_django/api.py @@ -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) diff --git a/common/djangoapps/xblock_django/models.py b/common/djangoapps/xblock_django/models.py index 5a2442d5c9..bdac48d963 100644 --- a/common/djangoapps/xblock_django/models.py +++ b/common/djangoapps/xblock_django/models.py @@ -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 ) diff --git a/common/djangoapps/xblock_django/tests/test_api.py b/common/djangoapps/xblock_django/tests/test_api.py index a1e56eafc9..2778bab620 100644 --- a/common/djangoapps/xblock_django/tests/test_api.py +++ b/common/djangoapps/xblock_django/tests/test_api.py @@ -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)) diff --git a/common/djangoapps/xblock_django/tests/test_models.py b/common/djangoapps/xblock_django/tests/test_models.py deleted file mode 100644 index 72b73f642a..0000000000 --- a/common/djangoapps/xblock_django/tests/test_models.py +++ /dev/null @@ -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']) diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index f35d40e431..b1ef5a2f0a 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -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 diff --git a/lms/envs/common.py b/lms/envs/common.py index 4dadbdad10..44831d1b3f 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -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