Commit 2be036fe by Braden MacDonald Committed by E. Kolpakov

Simplifications and changes to reduce conflicts with PR 6399

parent d82da9e7
...@@ -323,7 +323,7 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe ...@@ -323,7 +323,7 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
js_module_name = "VerticalDescriptor" js_module_name = "VerticalDescriptor"
@XBlock.handler @XBlock.handler
def refresh_children(self, request, suffix, update_db=True): # pylint: disable=unused-argument def refresh_children(self, request=None, suffix=None, update_db=True): # pylint: disable=unused-argument
""" """
Refresh children: Refresh children:
This method is to be used when any of the libraries that this block This method is to be used when any of the libraries that this block
...@@ -402,17 +402,12 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe ...@@ -402,17 +402,12 @@ class LibraryContentDescriptor(LibraryContentFields, MakoModuleDescriptor, XmlDe
) )
return validation return validation
lib_tools = self.runtime.service(self, 'library_tools') lib_tools = self.runtime.service(self, 'library_tools')
matching_children_count = 0
for library_key, version in self.source_libraries: for library_key, version in self.source_libraries:
if not self._validate_library_version(validation, lib_tools, version, library_key): if not self._validate_library_version(validation, lib_tools, version, library_key):
break break
library = lib_tools.get_library(library_key) # Note: we assume refresh_children() has been called since the last time fields like source_libraries or capa_types were changed.
children_matching_filter = lib_tools.get_filtered_children(library, self.capa_type) matching_children_count = len(self.children) # pylint: disable=no-member
# get_filtered_children returns generator, so can't use len.
# And we don't actually need those children, so no point of constructing a list
matching_children_count += sum(1 for child in children_matching_filter)
if matching_children_count == 0: if matching_children_count == 0:
self._set_validation_error_if_empty( self._set_validation_error_if_empty(
validation, validation,
......
...@@ -18,7 +18,7 @@ class LibraryToolsService(object): ...@@ -18,7 +18,7 @@ class LibraryToolsService(object):
def __init__(self, modulestore): def __init__(self, modulestore):
self.store = modulestore self.store = modulestore
def get_library(self, library_key): def _get_library(self, library_key):
""" """
Given a library key like "library-v1:ProblemX+PR0B", return the Given a library key like "library-v1:ProblemX+PR0B", return the
'library' XBlock with meta-information about the library. 'library' XBlock with meta-information about the library.
...@@ -39,39 +39,26 @@ class LibraryToolsService(object): ...@@ -39,39 +39,26 @@ class LibraryToolsService(object):
Get the version (an ObjectID) of the given library. Get the version (an ObjectID) of the given library.
Returns None if the library does not exist. Returns None if the library does not exist.
""" """
library = self.get_library(lib_key) library = self._get_library(lib_key)
if library: if library:
# We need to know the library's version so ensure it's set in library.location.library_key.version_guid # We need to know the library's version so ensure it's set in library.location.library_key.version_guid
assert library.location.library_key.version_guid is not None assert library.location.library_key.version_guid is not None
return library.location.library_key.version_guid return library.location.library_key.version_guid
return None return None
def _filter_child(self, capa_type, child_descriptor): def _filter_child(self, usage_key, capa_type):
""" """
Filters children by CAPA problem type, if configured Filters children by CAPA problem type, if configured
""" """
if capa_type == ANY_CAPA_TYPE_VALUE: if capa_type == ANY_CAPA_TYPE_VALUE:
return True return True
if not isinstance(child_descriptor, CapaDescriptor): if usage_key.block_type != "problem":
return False return False
return capa_type in child_descriptor.problem_types descriptor = self.store.get_item(usage_key, depth=0)
assert isinstance(descriptor, CapaDescriptor)
def get_filtered_children(self, from_block, capa_type=ANY_CAPA_TYPE_VALUE): return capa_type in descriptor.problem_types
"""
Filters children of `from_block` that satisfy filter criteria
Returns generator containing (child_key, child) for all children matching filter criteria
"""
children = (
(child_key, self.store.get_item(child_key, depth=None))
for child_key in from_block.children
)
return (
(child_key, child)
for child_key, child in children
if self._filter_child(capa_type, child)
)
def update_children(self, dest_block, user_id, user_perms=None, update_db=True): def update_children(self, dest_block, user_id, user_perms=None, update_db=True):
""" """
...@@ -104,7 +91,7 @@ class LibraryToolsService(object): ...@@ -104,7 +91,7 @@ class LibraryToolsService(object):
# First, load and validate the source_libraries: # First, load and validate the source_libraries:
libraries = [] libraries = []
for library_key, old_version in dest_block.source_libraries: # pylint: disable=unused-variable for library_key, old_version in dest_block.source_libraries: # pylint: disable=unused-variable
library = self.get_library(library_key) library = self._get_library(library_key)
if library is None: if library is None:
raise ValueError("Required library not found.") raise ValueError("Required library not found.")
if user_perms and not user_perms.can_read(library_key): if user_perms and not user_perms.can_read(library_key):
...@@ -124,9 +111,12 @@ class LibraryToolsService(object): ...@@ -124,9 +111,12 @@ class LibraryToolsService(object):
Internal method to copy blocks from the library recursively Internal method to copy blocks from the library recursively
""" """
new_children = [] new_children = []
target_capa_type = dest_block.capa_type if filter_problem_type else ANY_CAPA_TYPE_VALUE if filter_problem_type:
filtered_children = self.get_filtered_children(from_block, target_capa_type) filtered_children = [key for key in from_block.children if self._filter_child(key, dest_block.capa_type)]
for child_key, child in filtered_children: else:
filtered_children = from_block.children
for child_key in filtered_children:
child = self.store.get_item(child_key, depth=None)
# We compute a block_id for each matching child block found in the library. # We compute a block_id for each matching child block found in the library.
# block_ids are unique within any branch, but are not unique per-course or globally. # block_ids are unique within any branch, but are not unique per-course or globally.
# We need our block_ids to be consistent when content in the library is updated, so # We need our block_ids to be consistent when content in the library is updated, so
......
...@@ -138,9 +138,10 @@ class TestLibraries(MixedSplitTestCase): ...@@ -138,9 +138,10 @@ class TestLibraries(MixedSplitTestCase):
# Check that get_content_titles() doesn't return titles for hidden/unused children # Check that get_content_titles() doesn't return titles for hidden/unused children
self.assertEqual(len(self.lc_block.get_content_titles()), 1) self.assertEqual(len(self.lc_block.get_content_titles()), 1)
def test_validation(self): def test_validation_of_course_libraries(self):
""" """
Test that the validation method of LibraryContent blocks is working. Test that the validation method of LibraryContent blocks can validate
the source_libraries setting.
""" """
# When source_libraries is blank, the validation summary should say this block needs to be configured: # When source_libraries is blank, the validation summary should say this block needs to be configured:
self.lc_block.source_libraries = [] self.lc_block.source_libraries = []
...@@ -166,11 +167,17 @@ class TestLibraries(MixedSplitTestCase): ...@@ -166,11 +167,17 @@ class TestLibraries(MixedSplitTestCase):
self.assertIn("out of date", result.summary.text) self.assertIn("out of date", result.summary.text)
# Now if we update the block, all validation should pass: # Now if we update the block, all validation should pass:
self.lc_block.refresh_children(None, None) self.lc_block.refresh_children()
self.assertTrue(self.lc_block.validate()) self.assertTrue(self.lc_block.validate())
def test_validation_of_matching_blocks(self):
"""
Test that the validation method of LibraryContent blocks can warn
the user about problems with other settings (max_count and capa_type).
"""
# Set max_count to higher value than exists in library # Set max_count to higher value than exists in library
self.lc_block.max_count = 50 self.lc_block.max_count = 50
self.lc_block.refresh_children() # In the normal studio editing process, editor_saved() calls refresh_children at this point
result = self.lc_block.validate() result = self.lc_block.validate()
self.assertFalse(result) # Validation fails due to at least one warning/message self.assertFalse(result) # Validation fails due to at least one warning/message
self.assertTrue(result.summary) self.assertTrue(result.summary)
...@@ -180,17 +187,19 @@ class TestLibraries(MixedSplitTestCase): ...@@ -180,17 +187,19 @@ class TestLibraries(MixedSplitTestCase):
# Add some capa problems so we can check problem type validation messages # Add some capa problems so we can check problem type validation messages
self.lc_block.max_count = 1 self.lc_block.max_count = 1
self._create_capa_problems() self._create_capa_problems()
self.lc_block.refresh_children(None, None) self.lc_block.refresh_children()
self.assertTrue(self.lc_block.validate()) self.assertTrue(self.lc_block.validate())
# Existing problem type should pass validation # Existing problem type should pass validation
self.lc_block.max_count = 1 self.lc_block.max_count = 1
self.lc_block.capa_type = 'multiplechoiceresponse' self.lc_block.capa_type = 'multiplechoiceresponse'
self.lc_block.refresh_children()
self.assertTrue(self.lc_block.validate()) self.assertTrue(self.lc_block.validate())
# ... unless requested more blocks than exists in library # ... unless requested more blocks than exists in library
self.lc_block.max_count = 10 self.lc_block.max_count = 10
self.lc_block.capa_type = 'multiplechoiceresponse' self.lc_block.capa_type = 'multiplechoiceresponse'
self.lc_block.refresh_children()
result = self.lc_block.validate() result = self.lc_block.validate()
self.assertFalse(result) # Validation fails due to at least one warning/message self.assertFalse(result) # Validation fails due to at least one warning/message
self.assertTrue(result.summary) self.assertTrue(result.summary)
...@@ -200,6 +209,7 @@ class TestLibraries(MixedSplitTestCase): ...@@ -200,6 +209,7 @@ class TestLibraries(MixedSplitTestCase):
# Missing problem type should always fail validation # Missing problem type should always fail validation
self.lc_block.max_count = 1 self.lc_block.max_count = 1
self.lc_block.capa_type = 'customresponse' self.lc_block.capa_type = 'customresponse'
self.lc_block.refresh_children()
result = self.lc_block.validate() result = self.lc_block.validate()
self.assertFalse(result) # Validation fails due to at least one warning/message self.assertFalse(result) # Validation fails due to at least one warning/message
self.assertTrue(result.summary) self.assertTrue(result.summary)
...@@ -213,21 +223,21 @@ class TestLibraries(MixedSplitTestCase): ...@@ -213,21 +223,21 @@ class TestLibraries(MixedSplitTestCase):
self._create_capa_problems() self._create_capa_problems()
self.assertEqual(len(self.lc_block.children), 0) # precondition check self.assertEqual(len(self.lc_block.children), 0) # precondition check
self.lc_block.capa_type = "multiplechoiceresponse" self.lc_block.capa_type = "multiplechoiceresponse"
self.lc_block.refresh_children(None, None) self.lc_block.refresh_children()
self.assertEqual(len(self.lc_block.children), 1) self.assertEqual(len(self.lc_block.children), 1)
self.lc_block.capa_type = "optionresponse" self.lc_block.capa_type = "optionresponse"
self.lc_block.refresh_children(None, None) self.lc_block.refresh_children()
self.assertEqual(len(self.lc_block.children), 3) self.assertEqual(len(self.lc_block.children), 3)
self.lc_block.capa_type = "coderesponse" self.lc_block.capa_type = "coderesponse"
self.lc_block.refresh_children(None, None) self.lc_block.refresh_children()
self.assertEqual(len(self.lc_block.children), 2) self.assertEqual(len(self.lc_block.children), 2)
self.lc_block.capa_type = "customresponse" self.lc_block.capa_type = "customresponse"
self.lc_block.refresh_children(None, None) self.lc_block.refresh_children()
self.assertEqual(len(self.lc_block.children), 0) self.assertEqual(len(self.lc_block.children), 0)
self.lc_block.capa_type = ANY_CAPA_TYPE_VALUE self.lc_block.capa_type = ANY_CAPA_TYPE_VALUE
self.lc_block.refresh_children(None, None) self.lc_block.refresh_children()
self.assertEqual(len(self.lc_block.children), len(self.lib_blocks) + 4) self.assertEqual(len(self.lc_block.children), len(self.lib_blocks) + 4)
...@@ -43,16 +43,6 @@ class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest): ...@@ -43,16 +43,6 @@ class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest):
XBlockFixtureDesc("html", "Html1"), XBlockFixtureDesc("html", "Html1"),
XBlockFixtureDesc("html", "Html2"), XBlockFixtureDesc("html", "Html2"),
XBlockFixtureDesc("html", "Html3"), XBlockFixtureDesc("html", "Html3"),
XBlockFixtureDesc(
"problem", "Dropdown",
data=textwrap.dedent("""
<problem>
<p>Dropdown</p>
<optionresponse><optioninput label="Dropdown" options="('1', '2')" correct="'2'"></optioninput></optionresponse>
</problem>
""")
)
) )
def populate_course_fixture(self, course_fixture): def populate_course_fixture(self, course_fixture):
...@@ -189,9 +179,20 @@ class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest): ...@@ -189,9 +179,20 @@ class StudioLibraryContainerTest(StudioLibraryTest, UniqueCourseTest):
When I go to studio unit page for library content block When I go to studio unit page for library content block
And I set Problem Type selector so that no libraries have matching content And I set Problem Type selector so that no libraries have matching content
Then I can see that "No matching content" warning is shown Then I can see that "No matching content" warning is shown
When I set Problem Type selector so that there are matching content When I set Problem Type selector so that there is matching content
Then I can see that warning messages are not shown Then I can see that warning messages are not shown
""" """
# Add a single "Dropdown" type problem to the library (which otherwise has only HTML blocks):
self.library_fixture.create_xblock(self.library_fixture.library_location, XBlockFixtureDesc(
"problem", "Dropdown",
data=textwrap.dedent("""
<problem>
<p>Dropdown</p>
<optionresponse><optioninput label="Dropdown" options="('1', '2')" correct="'2'"></optioninput></optionresponse>
</problem>
""")
))
expected_text = 'There are no matching problem types in the specified libraries. Select another problem type' expected_text = 'There are no matching problem types in the specified libraries. Select another problem type'
library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0]) library_container = self._get_library_xblock_wrapper(self.unit_page.xblocks[0])
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment