diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py index efff1760f9..86fd30e1c6 100644 --- a/cms/djangoapps/contentstore/views/tests/test_preview.py +++ b/cms/djangoapps/contentstore/views/tests/test_preview.py @@ -110,10 +110,10 @@ class GetPreviewHtmlTestCase(ModuleStoreTestCase): self.assertNotRegex(html, r"data-block-type=[\"\']test_aside[\"\']") self.assertNotRegex(html, "Aside rendered") - @mock.patch('xmodule.conditional_module.ConditionalModule.is_condition_satisfied') + @mock.patch('xmodule.conditional_module.ConditionalBlock.is_condition_satisfied') def test_preview_conditional_module_children_context(self, mock_is_condition_satisfied): """ - Testst that when empty context is pass to children of ConditionalModule it will not raise KeyError. + Tests that when empty context is pass to children of ConditionalBlock it will not raise KeyError. """ mock_is_condition_satisfied.return_value = True client = Client() diff --git a/common/lib/xmodule/setup.py b/common/lib/xmodule/setup.py index 2a54bac6f9..38a738089c 100644 --- a/common/lib/xmodule/setup.py +++ b/common/lib/xmodule/setup.py @@ -5,7 +5,6 @@ from setuptools import find_packages, setup XMODULES = [ "book = xmodule.backcompat_module:TranslateCustomTagDescriptor", "chapter = xmodule.seq_module:SectionDescriptor", - "conditional = xmodule.conditional_module:ConditionalDescriptor", "course = xmodule.course_module:CourseDescriptor", "customtag = xmodule.template_module:CustomTagDescriptor", "discuss = xmodule.backcompat_module:TranslateCustomTagDescriptor", @@ -29,6 +28,7 @@ XMODULES = [ ] XBLOCKS = [ "about = xmodule.html_module:AboutBlock", + "conditional = xmodule.conditional_module:ConditionalBlock", "course_info = xmodule.html_module:CourseInfoBlock", "html = xmodule.html_module:HtmlBlock", "library = xmodule.library_root_xblock:LibraryRoot", diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py index fc9c0fb609..f63044de57 100644 --- a/common/lib/xmodule/xmodule/capa_base.py +++ b/common/lib/xmodule/xmodule/capa_base.py @@ -934,6 +934,8 @@ class CapaMixin(ScorableXBlockMixin, CapaFields): """ True iff full points """ + # self.score is initialized in self.lcp but in this method is accessed before self.lcp so just call it first. + self.lcp # pylint: disable=pointless-statement return self.score.raw_earned == self.score.raw_possible def answer_available(self): diff --git a/common/lib/xmodule/xmodule/conditional_module.py b/common/lib/xmodule/xmodule/conditional_module.py index cd45d7b835..fb01733930 100644 --- a/common/lib/xmodule/xmodule/conditional_module.py +++ b/common/lib/xmodule/xmodule/conditional_module.py @@ -1,5 +1,5 @@ -"""Conditional module is the xmodule, which you can use for disabling -some xmodules by conditions. +""" +ConditionalBlock is an XBlock which you can use for disabling some XBlocks by conditions. """ @@ -16,11 +16,23 @@ from web_fragments.fragment import Fragment from xblock.fields import ReferenceList, Scope, String from openedx.core.djangolib.markup import HTML, Text +from xmodule.mako_module import MakoTemplateBlockBase from xmodule.modulestore.exceptions import ItemNotFoundError -from xmodule.seq_module import SequenceDescriptor -from xmodule.studio_editable import StudioEditableDescriptor, StudioEditableModule +from xmodule.seq_module import SequenceMixin +from xmodule.studio_editable import StudioEditableBlock +from xmodule.util.xmodule_django import add_webpack_to_fragment from xmodule.validation import StudioValidation, StudioValidationMessage -from xmodule.x_module import STUDENT_VIEW, XModule +from xmodule.xml_module import XmlMixin +from xmodule.x_module import ( + HTMLSnippet, + ResourceTemplates, + shim_xmodule_js, + STUDENT_VIEW, + XModuleDescriptorToXBlockMixin, + XModuleMixin, + XModuleToXBlockMixin, +) + log = logging.getLogger('edx.' + __name__) @@ -28,8 +40,55 @@ log = logging.getLogger('edx.' + __name__) _ = lambda text: text -class ConditionalFields(object): - has_children = True +class ConditionalBlock( + SequenceMixin, + MakoTemplateBlockBase, + XmlMixin, + XModuleDescriptorToXBlockMixin, + XModuleToXBlockMixin, + HTMLSnippet, + ResourceTemplates, + XModuleMixin, + StudioEditableBlock, +): + """ + Blocks child blocks from showing unless certain conditions are met. + + Example: + + + + + + tag attributes: + sources - location id of required modules, separated by ';' + + submitted - map to `is_submitted` module method. + (pressing RESET button makes this function to return False.) + + attempted - map to `is_attempted` module method + correct - map to `is_correct` module method + poll_answer - map to `poll_answer` module attribute + voted - map to `voted` module attribute + + tag attributes: + sources - location id of required modules, separated by ';' + + You can add you own rules for tag, like + "completed", "attempted" etc. To do that yo must extend + `ConditionalBlock.conditions_map` variable and add pair: + my_attr: my_property/my_method + + After that you can use it: + + ... + + + And my_property/my_method will be called for required modules. + + """ + display_name = String( display_name=_("Display Name"), help=_("The display name for this component."), @@ -57,7 +116,7 @@ class ConditionalFields(object): scope=Scope.content, default='correct', values=lambda: [{'display_name': xml_attr, 'value': xml_attr} - for xml_attr in ConditionalModule.conditions_map.keys()] + for xml_attr in ConditionalBlock.conditions_map] ) conditional_value = String( @@ -77,56 +136,39 @@ class ConditionalFields(object): default=_('You must complete {link} before you can access this unit.') ) + has_children = True -class ConditionalModule(ConditionalFields, XModule, StudioEditableModule): - """ - Blocks child module from showing unless certain conditions are met. + _tag_name = 'conditional' - Example: + resources_dir = None - - - + filename_extension = "xml" - tag attributes: - sources - location id of required modules, separated by ';' + has_score = False - submitted - map to `is_submitted` module method. - (pressing RESET button makes this function to return False.) + show_in_read_only_mode = True - attempted - map to `is_attempted` module method - correct - map to `is_correct` module method - poll_answer - map to `poll_answer` module attribute - voted - map to `voted` module attribute - - tag attributes: - sources - location id of required modules, separated by ';' - - You can add you own rules for tag, like - "completed", "attempted" etc. To do that yo must extend - `ConditionalModule.conditions_map` variable and add pair: - my_attr: my_property/my_method - - After that you can use it: - - ... - - - And my_property/my_method will be called for required modules. - - """ - - js = { + preview_view_js = { 'js': [ resource_string(__name__, 'js/src/conditional/display.js'), resource_string(__name__, 'js/src/javascript_loader.js'), resource_string(__name__, 'js/src/collapsible.js'), - ] + ], + 'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'), + } + preview_view_css = { + 'scss': [], } - js_module_name = "Conditional" - css = {'scss': [resource_string(__name__, 'css/capa/display.scss')]} + mako_template = 'widgets/metadata-edit.html' + studio_js_module_name = 'SequenceDescriptor' + studio_view_js = { + 'js': [resource_string(__name__, 'js/src/sequence/edit.js')], + 'xmodule_js': resource_string(__name__, 'js/src/xmodule.js'), + } + studio_view_css = { + 'scss': [], + } # Map # key: @@ -148,16 +190,29 @@ class ConditionalModule(ConditionalFields, XModule, StudioEditableModule): 'voted': 'voted' # poll_question attr } - @lazy - def required_modules(self): - return [self.system.get_module(descriptor) for - descriptor in self.descriptor.get_required_module_descriptors()] + def __init__(self, *args, **kwargs): + """ + Create an instance of the Conditional XBlock. + """ + super(ConditionalBlock, self).__init__(*args, **kwargs) + # Convert sources xml_attribute to a ReferenceList field type so Location/Locator + # substitution can be done. + if not self.sources_list: + if 'sources' in self.xml_attributes and isinstance(self.xml_attributes['sources'], six.string_types): + self.sources_list = [ + # TODO: it is not clear why we are replacing the run here (which actually is a no-op + # for old-style course locators. However, this is the implementation of + # CourseLocator.make_usage_key_from_deprecated_string, which was previously + # being called in this location. + BlockUsageLocator.from_string(item).replace(run=self.location.course_key.run) + for item in ConditionalBlock.parse_sources(self.xml_attributes) + ] def is_condition_satisfied(self): attr_name = self.conditions_map[self.conditional_attr] - if self.conditional_value and self.required_modules: - for module in self.required_modules: + if self.conditional_value and self.get_required_blocks: + for module in self.get_required_blocks: if not hasattr(module, attr_name): # We don't throw an exception here because it is possible for # the descriptor of a required module to have a property but @@ -180,15 +235,22 @@ class ConditionalModule(ConditionalFields, XModule, StudioEditableModule): return True return False - def get_html(self): - # Calculate html ids of dependencies - self.required_html_ids = [descriptor.location.html_id() for - descriptor in self.descriptor.get_required_module_descriptors()] + def student_view(self, _context): + """ + Renders the student view. + """ + fragment = Fragment() + fragment.add_content(self.get_html()) + add_webpack_to_fragment(fragment, 'ConditionalBlockPreview') + shim_xmodule_js(fragment, 'Conditional') + return fragment + def get_html(self): + required_html_ids = [descriptor.location.html_id() for descriptor in self.get_required_blocks] return self.system.render_template('conditional_ajax.html', { 'element_id': self.location.html_id(), - 'ajax_url': self.system.ajax_url, - 'depends': ';'.join(self.required_html_ids) + 'ajax_url': self.ajax_url, + 'depends': ';'.join(required_html_ids) }) def author_view(self, context): @@ -206,6 +268,17 @@ class ConditionalModule(ConditionalFields, XModule, StudioEditableModule): return fragment + def studio_view(self, _context): + """ + Return the studio view. + """ + fragment = Fragment( + self.system.render_template(self.mako_template, self.get_context()) + ) + add_webpack_to_fragment(fragment, 'ConditionalBlockStudio') + shim_xmodule_js(fragment, self.studio_js_module_name) + return fragment + def handle_ajax(self, _dispatch, _data): """This is called by courseware.moduleodule_render, to handle an AJAX call. @@ -227,55 +300,14 @@ class ConditionalModule(ConditionalFields, XModule, StudioEditableModule): # OBSOLETE: This obsoletes 'type' class_priority = ['video', 'problem'] - child_classes = [self.system.get_module(child_descriptor).get_icon_class() - for child_descriptor in self.descriptor.get_children()] + child_classes = [ + child_descriptor.get_icon_class() for child_descriptor in self.get_children() + ] for c in class_priority: if c in child_classes: new_class = c return new_class - def validate(self): - """ - Message for either error or warning validation message/s. - - Returns message and type. Priority given to error type message. - """ - return self.descriptor.validate() - - -class ConditionalDescriptor(ConditionalFields, SequenceDescriptor, StudioEditableDescriptor): - """Descriptor for conditional xmodule.""" - _tag_name = 'conditional' - - module_class = ConditionalModule - - resources_dir = None - - filename_extension = "xml" - - has_score = False - - show_in_read_only_mode = True - - def __init__(self, *args, **kwargs): - """ - Create an instance of the conditional module. - """ - super(ConditionalDescriptor, self).__init__(*args, **kwargs) - - # Convert sources xml_attribute to a ReferenceList field type so Location/Locator - # substitution can be done. - if not self.sources_list: - if 'sources' in self.xml_attributes and isinstance(self.xml_attributes['sources'], six.string_types): - self.sources_list = [ - # TODO: it is not clear why we are replacing the run here (which actually is a no-op - # for old-style course locators. However, this is the implementation of - # CourseLocator.make_usage_key_from_deprecated_string, which was previously - # being called in this location. - BlockUsageLocator.from_string(item).replace(run=self.location.course_key.run) - for item in ConditionalDescriptor.parse_sources(self.xml_attributes) - ] - @staticmethod def parse_sources(xml_element): """ Parse xml_element 'sources' attr and return a list of location strings. """ @@ -283,9 +315,16 @@ class ConditionalDescriptor(ConditionalFields, SequenceDescriptor, StudioEditabl if sources: return [location.strip() for location in sources.split(';')] + @lazy + def get_required_blocks(self): + """ + Returns a list of bound XBlocks instances upon which XBlock depends. + """ + return [self.system.get_module(descriptor) for descriptor in self.get_required_module_descriptors()] + def get_required_module_descriptors(self): - """Returns a list of XModuleDescriptor instances upon - which this module depends. + """ + Returns a list of unbound XBlocks instances upon which this XBlock depends. """ descriptors = [] for location in self.sources_list: @@ -304,7 +343,7 @@ class ConditionalDescriptor(ConditionalFields, SequenceDescriptor, StudioEditabl children = [] show_tag_list = [] definition = {} - for conditional_attr in six.iterkeys(ConditionalModule.conditions_map): + for conditional_attr in six.iterkeys(cls.conditions_map): conditional_value = xml_object.get(conditional_attr) if conditional_value is not None: definition.update({ @@ -313,7 +352,7 @@ class ConditionalDescriptor(ConditionalFields, SequenceDescriptor, StudioEditabl }) for child in xml_object: if child.tag == 'show': - locations = ConditionalDescriptor.parse_sources(child) + locations = cls.parse_sources(child) for location in locations: children.append(location) show_tag_list.append(location) @@ -351,7 +390,7 @@ class ConditionalDescriptor(ConditionalFields, SequenceDescriptor, StudioEditabl return xml_object def validate(self): - validation = super(ConditionalDescriptor, self).validate() + validation = super(ConditionalBlock, self).validate() if not self.sources_list: conditional_validation = StudioValidation(self.location) conditional_validation.add( @@ -368,14 +407,9 @@ class ConditionalDescriptor(ConditionalFields, SequenceDescriptor, StudioEditabl @property def non_editable_metadata_fields(self): - non_editable_fields = super(ConditionalDescriptor, self).non_editable_metadata_fields + non_editable_fields = super(ConditionalBlock, self).non_editable_metadata_fields non_editable_fields.extend([ - ConditionalDescriptor.due, - ConditionalDescriptor.is_practice_exam, - ConditionalDescriptor.is_proctored_enabled, - ConditionalDescriptor.is_time_limited, - ConditionalDescriptor.default_time_limit_minutes, - ConditionalDescriptor.show_tag_list, - ConditionalDescriptor.exam_review_rules, + ConditionalBlock.due, + ConditionalBlock.show_tag_list, ]) return non_editable_fields diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 1210294b97..7d56dc576c 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -12,7 +12,6 @@ from datetime import datetime from functools import reduce import six -from django.contrib.auth.models import User from lxml import etree from opaque_keys.edx.keys import UsageKey from pkg_resources import resource_string @@ -281,6 +280,13 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): datetime.now(UTC) < date ) + def _get_user(self): + """ + Return the current runtime Django user. + """ + from django.contrib.auth.models import User + return User.objects.get(id=self.runtime.user_id) + def gate_sequence_if_it_is_a_timed_exam_and_contains_content_type_gated_problems(self): """ Problem: @@ -316,7 +322,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): return try: - user = User.objects.get(id=self.runtime.user_id) + user = self._get_user() course_id = self.runtime.course_id content_type_gating_service = self.runtime.service(self, 'content_type_gating') if not (content_type_gating_service and @@ -806,7 +812,46 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): return new_class -class SequenceDescriptor(SequenceFields, ProctoringFields, MakoModuleDescriptor, XmlDescriptor): +class SequenceMixin(SequenceFields): + """ + A mixin of shared code between the SequenceDescriptor and XBlocks + converted from XModules which inherited from SequenceDescriptor. + """ + @classmethod + def definition_from_xml(cls, xml_object, system): + children = [] + for child in xml_object: + try: + child_block = system.process_xml(etree.tostring(child, encoding='unicode')) + children.append(child_block.scope_ids.usage_id) + except Exception as e: + log.exception("Unable to load child when parsing Sequence. Continuing...") + if system.error_tracker is not None: + system.error_tracker(u"ERROR: {0}".format(e)) + continue + return {}, children + + def index_dictionary(self): + """ + Return dictionary prepared with module content and type for indexing. + """ + # return key/value fields in a Python dict object + # values may be numeric / string or dict + # default implementation is an empty dict + xblock_body = super(SequenceMixin, self).index_dictionary() + html_body = { + "display_name": self.display_name, + } + if "content" in xblock_body: + xblock_body["content"].update(html_body) + else: + xblock_body["content"] = html_body + xblock_body["content_type"] = "Sequence" + + return xblock_body + + +class SequenceDescriptor(SequenceMixin, ProctoringFields, MakoModuleDescriptor, XmlDescriptor): """ A Sequence's Descriptor object """ @@ -822,20 +867,6 @@ class SequenceDescriptor(SequenceFields, ProctoringFields, MakoModuleDescriptor, } js_module_name = "SequenceDescriptor" - @classmethod - def definition_from_xml(cls, xml_object, system): - children = [] - for child in xml_object: - try: - child_block = system.process_xml(etree.tostring(child, encoding='unicode')) - children.append(child_block.scope_ids.usage_id) - except Exception as e: - log.exception("Unable to load child when parsing Sequence. Continuing...") - if system.error_tracker is not None: - system.error_tracker(u"ERROR: {0}".format(e)) - continue - return {}, children - def definition_to_xml(self, resource_fs): xml_object = etree.Element('sequential') for child in self.get_children(): @@ -848,28 +879,9 @@ class SequenceDescriptor(SequenceFields, ProctoringFields, MakoModuleDescriptor, `is_entrance_exam` should not be editable in the Studio settings editor. """ non_editable_fields = super(SequenceDescriptor, self).non_editable_metadata_fields - non_editable_fields.append(self.fields['is_entrance_exam']) + non_editable_fields.append(self.fields['is_entrance_exam']) # pylint:disable=unsubscriptable-object return non_editable_fields - def index_dictionary(self): - """ - Return dictionary prepared with module content and type for indexing. - """ - # return key/value fields in a Python dict object - # values may be numeric / string or dict - # default implementation is an empty dict - xblock_body = super(SequenceDescriptor, self).index_dictionary() - html_body = { - "display_name": self.display_name, - } - if "content" in xblock_body: - xblock_body["content"].update(html_body) - else: - xblock_body["content"] = html_body - xblock_body["content_type"] = "Sequence" - - return xblock_body - class HighlightsFields(object): """Only Sections have summaries now, but we may expand that later.""" diff --git a/common/lib/xmodule/xmodule/static_content.py b/common/lib/xmodule/xmodule/static_content.py index 44d8fa4033..607c3e90d8 100755 --- a/common/lib/xmodule/xmodule/static_content.py +++ b/common/lib/xmodule/xmodule/static_content.py @@ -21,6 +21,7 @@ from docopt import docopt from path import Path as path from xmodule.capa_module import ProblemBlock +from xmodule.conditional_module import ConditionalBlock from xmodule.html_module import AboutBlock, CourseInfoBlock, HtmlBlock, StaticTabBlock from xmodule.library_content_module import LibraryContentBlock from xmodule.word_cloud_module import WordCloudBlock @@ -66,6 +67,7 @@ class VideoBlock(HTMLSnippet): # Should only be used for XModules being converted to XBlocks. XBLOCK_CLASSES = [ AboutBlock, + ConditionalBlock, CourseInfoBlock, HtmlBlock, LibraryContentBlock, diff --git a/common/lib/xmodule/xmodule/tests/test_conditional.py b/common/lib/xmodule/xmodule/tests/test_conditional.py index 9b248f6e81..5155a0f787 100644 --- a/common/lib/xmodule/xmodule/tests/test_conditional.py +++ b/common/lib/xmodule/xmodule/tests/test_conditional.py @@ -13,7 +13,7 @@ from web_fragments.fragment import Fragment from xblock.field_data import DictFieldData from xblock.fields import ScopeIds -from xmodule.conditional_module import ConditionalDescriptor +from xmodule.conditional_module import ConditionalBlock from xmodule.error_module import NonStaffErrorDescriptor from xmodule.modulestore.xml import CourseLocationManager, ImportSystem, XMLModuleStore from xmodule.tests import DATA_DIR, get_test_descriptor_system, get_test_system @@ -45,9 +45,9 @@ class DummySystem(ImportSystem): raise Exception("Shouldn't be called") -class ConditionalModuleFactory(xml.XmlImportFactory): +class ConditionalBlockFactory(xml.XmlImportFactory): """ - Factory for generating ConditionalModule for testing purposes + Factory for generating ConditionalBlock for testing purposes """ tag = 'conditional' @@ -125,17 +125,15 @@ class ConditionalFactory(object): 'children': [child_descriptor.location], }) - cond_descriptor = ConditionalDescriptor( + cond_descriptor = ConditionalBlock( descriptor_system, field_data, ScopeIds(None, None, cond_location, cond_location) ) cond_descriptor.xmodule_runtime = system system.get_module = lambda desc: desc if visible_to_nonstaff_users(desc) else None - cond_descriptor.get_required_module_descriptors = Mock(return_value=[source_descriptor]) - cond_descriptor.required_modules = [ - system.get_module(descriptor) - for descriptor in cond_descriptor.get_required_module_descriptors() + cond_descriptor.get_required_blocks = [ + system.get_module(source_descriptor), ] # return dict: @@ -144,14 +142,14 @@ class ConditionalFactory(object): 'child_module': child_descriptor} -class ConditionalModuleBasicTest(unittest.TestCase): +class ConditionalBlockBasicTest(unittest.TestCase): """ Make sure that conditional module works, using mocks for other modules. """ def setUp(self): - super(ConditionalModuleBasicTest, self).setUp() + super(ConditionalBlockBasicTest, self).setUp() self.test_system = get_test_system() def test_icon_class(self): @@ -169,7 +167,7 @@ class ConditionalModuleBasicTest(unittest.TestCase): # we reverse it here html = modules['cond_module'].render(STUDENT_VIEW).content expected = modules['cond_module'].xmodule_runtime.render_template('conditional_ajax.html', { - 'ajax_url': modules['cond_module'].xmodule_runtime.ajax_url, + 'ajax_url': modules['cond_module'].ajax_url, 'element_id': u'i4x-edX-conditional_test-conditional-SampleConditional', 'depends': u'i4x-edX-conditional_test-problem-SampleProblem', }) @@ -214,12 +212,12 @@ class ConditionalModuleBasicTest(unittest.TestCase): cond_module.is_attempted = "false" cond_module.handle_ajax('', '') self.assertFalse(mock_log.warn.called) - self.assertIn(None, cond_module.required_modules) + self.assertIn(None, cond_module.get_required_blocks) -class ConditionalModuleXmlTest(unittest.TestCase): +class ConditionalBlockXmlTest(unittest.TestCase): """ - Make sure ConditionalModule works, by loading data in from an XML-defined course. + Make sure ConditionalBlock works, by loading data in from an XML-defined course. """ @staticmethod @@ -228,7 +226,7 @@ class ConditionalModuleXmlTest(unittest.TestCase): return DummySystem(load_error_modules) def setUp(self): - super(ConditionalModuleXmlTest, self).setUp() + super(ConditionalBlockXmlTest, self).setUp() self.test_system = get_test_system() def get_course(self, name): @@ -241,7 +239,8 @@ class ConditionalModuleXmlTest(unittest.TestCase): self.assertEqual(len(courses), 1) return courses[0] - def test_conditional_module(self): + @patch('xmodule.x_module.descriptor_global_local_resource_url') + def test_conditional_module(self, _): """Make sure that conditional module works""" print("Starting import") @@ -309,9 +308,11 @@ class ConditionalModuleXmlTest(unittest.TestCase): fragments = ajax['fragments'] self.assertTrue(any(['This is a secret' in item['content'] for item in fragments])) + maxDiff = None + def test_conditional_module_with_empty_sources_list(self): """ - If a ConditionalDescriptor is initialized with an empty sources_list, we assert that the sources_list is set + If a ConditionalBlock is initialized with an empty sources_list, we assert that the sources_list is set via generating UsageKeys from the values in xml_attributes['sources'] """ dummy_system = Mock() @@ -323,16 +324,18 @@ class ConditionalModuleXmlTest(unittest.TestCase): 'xml_attributes': {'sources': 'i4x://HarvardX/ER22x/poll_question/T15_poll'}, 'children': None, }) - conditional = ConditionalDescriptor( + conditional = ConditionalBlock( dummy_system, dummy_field_data, dummy_scope_ids, ) + new_run = conditional.location.course_key.run self.assertEqual( conditional.sources_list[0], - # Matching what is in ConditionalDescriptor.__init__. - BlockUsageLocator.from_string(conditional.xml_attributes['sources']).replace(run=new_run) + BlockUsageLocator.from_string( + conditional.xml_attributes['sources'] + ).replace(run=dummy_location.course_key.run) ) def test_conditional_module_parse_sources(self): @@ -345,7 +348,7 @@ class ConditionalModuleXmlTest(unittest.TestCase): 'xml_attributes': {'sources': 'i4x://HarvardX/ER22x/poll_question/T15_poll;i4x://HarvardX/ER22x/poll_question/T16_poll'}, 'children': None, }) - conditional = ConditionalDescriptor( + conditional = ConditionalBlock( dummy_system, dummy_field_data, dummy_scope_ids, @@ -358,7 +361,7 @@ class ConditionalModuleXmlTest(unittest.TestCase): def test_conditional_module_parse_attr_values(self): root = '' xml_object = etree.XML(root) - definition = ConditionalDescriptor.definition_from_xml(xml_object, Mock())[0] + definition = ConditionalBlock.definition_from_xml(xml_object, Mock())[0] expected_definition = { 'show_tag_list': [], 'conditional_attr': 'attempted', @@ -380,16 +383,16 @@ class ConditionalModuleXmlTest(unittest.TestCase): self.assertDictEqual(modules['cond_module'].xml_attributes, expected_xml_attributes) -class ConditionalModuleStudioTest(XModuleXmlImportTest): +class ConditionalBlockStudioTest(XModuleXmlImportTest): """ Unit tests for how conditional test interacts with Studio. """ def setUp(self): - super(ConditionalModuleStudioTest, self).setUp() + super().setUp() course = xml.CourseFactory.build() sequence = xml.SequenceFactory.build(parent=course) - conditional = ConditionalModuleFactory( + conditional = ConditionalBlockFactory( parent=sequence, attribs={ 'group_id_to_child': '{"0": "i4x://edX/xml_test_course/html/conditional_0"}' @@ -438,11 +441,7 @@ class ConditionalModuleStudioTest(XModuleXmlImportTest): Test the settings that are marked as "non-editable". """ non_editable_metadata_fields = self.conditional.non_editable_metadata_fields - self.assertIn(ConditionalDescriptor.due, non_editable_metadata_fields) - self.assertIn(ConditionalDescriptor.is_practice_exam, non_editable_metadata_fields) - self.assertIn(ConditionalDescriptor.is_time_limited, non_editable_metadata_fields) - self.assertIn(ConditionalDescriptor.default_time_limit_minutes, non_editable_metadata_fields) - self.assertIn(ConditionalDescriptor.show_tag_list, non_editable_metadata_fields) + self.assertIn(ConditionalBlock.due, non_editable_metadata_fields) def test_validation_messages(self): """ diff --git a/common/lib/xmodule/xmodule/tests/test_conditional_logic.py b/common/lib/xmodule/xmodule/tests/test_conditional_logic.py deleted file mode 100644 index 0f0eb20daa..0000000000 --- a/common/lib/xmodule/xmodule/tests/test_conditional_logic.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- -"""Test for Conditional Xmodule functional logic.""" - - -from xmodule.conditional_module import ConditionalDescriptor - -from . import LogicTest - - -class ConditionalModuleTest(LogicTest): - """Logic tests for Conditional Xmodule.""" - descriptor_class = ConditionalDescriptor - - def test_ajax_request(self): - "Make sure that ajax request works correctly" - # Mock is_condition_satisfied - self.xmodule.is_condition_satisfied = lambda: True - self.xmodule.descriptor.get_children = lambda: [] - - response = self.ajax_request('No', {}) - fragments = response['fragments'] - - self.assertEqual(fragments, []) diff --git a/common/lib/xmodule/xmodule/tests/test_sequence.py b/common/lib/xmodule/xmodule/tests/test_sequence.py index f866ed8556..1e07e83ff5 100644 --- a/common/lib/xmodule/xmodule/tests/test_sequence.py +++ b/common/lib/xmodule/xmodule/tests/test_sequence.py @@ -160,7 +160,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest): self.assertIn("'prev_url': 'PrevSequential'", html) self.assertNotIn("fa fa-check-circle check-circle is-hidden", html) - @patch('xmodule.seq_module.User.objects.get', return_value=UserFactory.build()) + @patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build()) def test_timed_exam_gating_waffle_flag(self, mocked_user): """ Verify the code inside the waffle flag is not executed with the flag off @@ -185,7 +185,7 @@ class SequenceBlockTestCase(XModuleXmlImportTest): mocked_user.assert_called_once() @override_waffle_flag(TIMED_EXAM_GATING_WAFFLE_FLAG, active=True) - @patch('xmodule.seq_module.User.objects.get', return_value=UserFactory.build()) + @patch('xmodule.seq_module.SequenceModule._get_user', return_value=UserFactory.build()) def test_that_timed_sequence_gating_respects_access_configurations(self, mocked_user): # pylint: disable=unused-argument """ Verify that if a time limited sequence contains content type gated problems, we gate the sequence diff --git a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py index 12cdaecb75..dfb30941f5 100644 --- a/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py +++ b/common/lib/xmodule/xmodule/tests/test_xblock_wrappers.py @@ -31,7 +31,7 @@ from xblock.field_data import DictFieldData from xblock.fields import ScopeIds from xmodule.annotatable_module import AnnotatableDescriptor -from xmodule.conditional_module import ConditionalDescriptor +from xmodule.conditional_module import ConditionalBlock from xmodule.course_module import CourseDescriptor from xmodule.html_module import HtmlBlock from xmodule.poll_module import PollDescriptor @@ -66,7 +66,7 @@ LEAF_XMODULES = { # to a list of sample field values to test with. # TODO: Add more types of sample data CONTAINER_XMODULES = { - ConditionalDescriptor: [{}], + ConditionalBlock: [{}], CourseDescriptor: [{}], RandomizeDescriptor: [{'display_name': 'Test String Display'}], SequenceDescriptor: [{'display_name': u'Test Unicode हिंदी Display'}], diff --git a/lms/templates/conditional_module.html b/lms/templates/conditional_module.html index 90c5ccdfde..06a055e2a0 100644 --- a/lms/templates/conditional_module.html +++ b/lms/templates/conditional_module.html @@ -14,7 +14,7 @@ def _message(reqm, message): url_name = reqm.display_name_with_default)) %> % if message: - % for reqm in module.required_modules: + % for reqm in module.get_required_blocks: % if reqm:

${_message(reqm, message)}

% else: