Merge pull request #24365 from edx/ddumesnil/AA-220
AA-220: Making Library Content an Aggregator CompletionMode
This commit is contained in:
@@ -19,6 +19,7 @@ from six import text_type
|
||||
from six.moves import zip
|
||||
from web_fragments.fragment import Fragment
|
||||
from webob import Response
|
||||
from xblock.completable import XBlockCompletionMode
|
||||
from xblock.core import XBlock
|
||||
from xblock.fields import Integer, List, Scope, String
|
||||
|
||||
@@ -65,6 +66,7 @@ class LibraryContentFields(object):
|
||||
Separated out for now because they need to be added to the module and the
|
||||
descriptor.
|
||||
"""
|
||||
completion_mode = XBlockCompletionMode.AGGREGATOR
|
||||
# Please note the display_name of each field below is used in
|
||||
# common/test/acceptance/pages/studio/library.py:StudioLibraryContentXBlockEditModal
|
||||
# to locate input elements - keep synchronized
|
||||
|
||||
@@ -64,9 +64,10 @@ class BlockCompletionTransformer(BlockStructureTransformer):
|
||||
children = block_structure.get_children(block_key)
|
||||
non_discussion_children = (child_key for child_key in children
|
||||
if block_structure.get_xblock_field(child_key, 'category') != 'discussion')
|
||||
child_complete = (block_structure.get_xblock_field(child_key, self.COMPLETE)
|
||||
for child_key in non_discussion_children)
|
||||
if children and all(child_complete):
|
||||
all_children_complete = all(block_structure.get_xblock_field(child_key, self.COMPLETE)
|
||||
for child_key in non_discussion_children)
|
||||
|
||||
if children and all_children_complete:
|
||||
block_structure.override_xblock_field(block_key, self.COMPLETE, True)
|
||||
|
||||
if any(block_structure.get_xblock_field(child_key, self.RESUME_BLOCK) for child_key in children):
|
||||
|
||||
@@ -12,8 +12,10 @@ from six.moves import range
|
||||
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from student.tests.factories import UserFactory
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
from xmodule.library_tools import LibraryToolsService
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, LibraryFactory
|
||||
from xmodule.tests import get_test_system
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -22,6 +24,8 @@ class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTest
|
||||
"""
|
||||
Test the data returned by the CompletionService.
|
||||
"""
|
||||
MODULESTORE = TEST_DATA_SPLIT_MODULESTORE
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(CompletionServiceTestCase, cls).setUpClass()
|
||||
@@ -30,38 +34,47 @@ class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTest
|
||||
cls.chapter = ItemFactory.create(
|
||||
parent=cls.course,
|
||||
category="chapter",
|
||||
publish_item=False,
|
||||
)
|
||||
cls.sequence = ItemFactory.create(
|
||||
parent=cls.chapter,
|
||||
category='sequential',
|
||||
publish_item=False,
|
||||
)
|
||||
cls.vertical = ItemFactory.create(
|
||||
parent=cls.sequence,
|
||||
category='vertical',
|
||||
publish_item=False,
|
||||
)
|
||||
cls.html = ItemFactory.create(
|
||||
parent=cls.vertical,
|
||||
category='html',
|
||||
publish_item=False,
|
||||
)
|
||||
cls.problem = ItemFactory.create(
|
||||
parent=cls.vertical,
|
||||
category="problem",
|
||||
publish_item=False,
|
||||
)
|
||||
cls.problem2 = ItemFactory.create(
|
||||
parent=cls.vertical,
|
||||
category="problem",
|
||||
publish_item=False,
|
||||
)
|
||||
cls.problem3 = ItemFactory.create(
|
||||
parent=cls.vertical,
|
||||
category="problem",
|
||||
publish_item=False,
|
||||
)
|
||||
cls.problem4 = ItemFactory.create(
|
||||
parent=cls.vertical,
|
||||
category="problem",
|
||||
publish_item=False,
|
||||
)
|
||||
cls.problem5 = ItemFactory.create(
|
||||
parent=cls.vertical,
|
||||
category="problem",
|
||||
publish_item=False,
|
||||
)
|
||||
cls.store.update_item(cls.course, UserFactory().id)
|
||||
cls.problems = [cls.problem, cls.problem2, cls.problem3, cls.problem4, cls.problem5]
|
||||
@@ -105,6 +118,25 @@ class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTest
|
||||
completion=0.75,
|
||||
)
|
||||
|
||||
def _bind_course_module(self, module):
|
||||
"""
|
||||
Bind a module (part of self.course) so we can access student-specific data.
|
||||
"""
|
||||
module_system = get_test_system(course_id=module.location.course_key)
|
||||
module_system.descriptor_runtime = module.runtime._descriptor_system # pylint: disable=protected-access
|
||||
module_system._services['library_tools'] = LibraryToolsService(self.store) # pylint: disable=protected-access
|
||||
|
||||
def get_module(descriptor):
|
||||
"""Mocks module_system get_module function"""
|
||||
sub_module_system = get_test_system(course_id=module.location.course_key)
|
||||
sub_module_system.get_module = get_module
|
||||
sub_module_system.descriptor_runtime = descriptor._runtime # pylint: disable=protected-access
|
||||
descriptor.bind_for_student(sub_module_system, self.user.id)
|
||||
return descriptor
|
||||
|
||||
module_system.get_module = get_module
|
||||
module.xmodule_runtime = module_system
|
||||
|
||||
def test_completion_service(self):
|
||||
# Only the completions for the user and course specified for the CompletionService
|
||||
# are returned. Values are returned for all keys provided.
|
||||
@@ -165,3 +197,78 @@ class CompletionServiceTestCase(CompletionWaffleTestMixin, SharedModuleStoreTest
|
||||
self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.vertical), False)
|
||||
self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.html), True)
|
||||
self.assertEqual(self.completion_service.can_mark_block_complete_on_view(self.problem), False)
|
||||
|
||||
def test_vertical_completion_with_library_content(self):
|
||||
library = LibraryFactory.create(modulestore=self.store)
|
||||
ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id)
|
||||
ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id)
|
||||
ItemFactory.create(parent=library, category='problem', publish_item=False, user_id=self.user.id)
|
||||
lib_vertical = ItemFactory.create(parent=self.sequence, category='vertical', publish_item=False)
|
||||
library_content_block = ItemFactory.create(
|
||||
parent=lib_vertical,
|
||||
category='library_content',
|
||||
max_count=1,
|
||||
source_library_id=str(library.location.library_key),
|
||||
user_id=self.user.id,
|
||||
)
|
||||
library_content_block.refresh_children()
|
||||
lib_vertical = self.store.get_item(lib_vertical.location)
|
||||
self._bind_course_module(lib_vertical)
|
||||
# We need to refetch the library_content_block to retrieve the
|
||||
# fresh version from the call to get_item for lib_vertical
|
||||
library_content_block = [child for child in lib_vertical.get_children()
|
||||
if child.scope_ids.block_type == 'library_content'][0]
|
||||
|
||||
## Ensure the library_content_block is properly set up
|
||||
# This is needed so we can call get_child_descriptors
|
||||
self._bind_course_module(library_content_block)
|
||||
# Make sure the runtime knows that the block's children vary per-user:
|
||||
self.assertTrue(library_content_block.has_dynamic_children())
|
||||
self.assertEqual(len(library_content_block.children), 3)
|
||||
# Check how many children each user will see:
|
||||
self.assertEqual(len(library_content_block.get_child_descriptors()), 1)
|
||||
|
||||
# No problems are complete yet
|
||||
self.assertFalse(self.completion_service.vertical_is_complete(lib_vertical))
|
||||
|
||||
for block_key in self.block_keys:
|
||||
BlockCompletion.objects.submit_completion(
|
||||
user=self.user,
|
||||
block_key=block_key,
|
||||
completion=1.0
|
||||
)
|
||||
# Library content problems aren't complete yet
|
||||
self.assertFalse(self.completion_service.vertical_is_complete(lib_vertical))
|
||||
|
||||
for child in library_content_block.get_child_descriptors():
|
||||
BlockCompletion.objects.submit_completion(
|
||||
user=self.user,
|
||||
block_key=child.scope_ids.usage_id,
|
||||
completion=1.0
|
||||
)
|
||||
self.assertTrue(self.completion_service.vertical_is_complete(lib_vertical))
|
||||
|
||||
def test_vertical_completion_with_nested_children(self):
|
||||
parent_vertical = ItemFactory(parent=self.sequence, category='vertical')
|
||||
extra_vertical = ItemFactory(parent=parent_vertical, category='vertical')
|
||||
problem = ItemFactory(parent=extra_vertical, category='problem')
|
||||
parent_vertical = self.store.get_item(parent_vertical.location)
|
||||
|
||||
# Nothing is complete
|
||||
self.assertFalse(self.completion_service.vertical_is_complete(parent_vertical))
|
||||
|
||||
for block_key in self.block_keys:
|
||||
BlockCompletion.objects.submit_completion(
|
||||
user=self.user,
|
||||
block_key=block_key,
|
||||
completion=1.0
|
||||
)
|
||||
# The nested child isn't complete yet
|
||||
self.assertFalse(self.completion_service.vertical_is_complete(parent_vertical))
|
||||
|
||||
BlockCompletion.objects.submit_completion(
|
||||
user=self.user,
|
||||
block_key=problem.location,
|
||||
completion=1.0
|
||||
)
|
||||
self.assertTrue(self.completion_service.vertical_is_complete(parent_vertical))
|
||||
|
||||
@@ -40,9 +40,6 @@ drf-yasg<1.17.1
|
||||
# drf-jwt 1.15.0 contains a migration that breaks on MySQL: https://github.com/Styria-Digital/django-rest-framework-jwt/issues/40
|
||||
drf-jwt==1.14.0
|
||||
|
||||
# Tests are failing with 3.2.2: https://build.testeng.edx.org/job/edx-platform-python-pipeline-pr/19138/
|
||||
edx-completion==3.2.1
|
||||
|
||||
# Upgrading to 2.12.0 breaks several test classes due to API changes, need to update our code accordingly
|
||||
factory-boy==2.8.1
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ edx-api-doc-tools==1.3.1 # via -r requirements/edx/base.in
|
||||
edx-bulk-grades==0.7.0 # via -r requirements/edx/base.in, staff-graded-xblock
|
||||
edx-ccx-keys==1.1.0 # via -r requirements/edx/base.in
|
||||
edx-celeryutils==0.5.1 # via -r requirements/edx/base.in, super-csv
|
||||
edx-completion==3.2.1 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.in
|
||||
edx-completion==3.2.3 # via -r requirements/edx/base.in
|
||||
edx-django-release-util==0.4.4 # via -r requirements/edx/base.in
|
||||
edx-django-sites-extensions==2.5.1 # via -r requirements/edx/base.in
|
||||
edx-django-utils==3.2.3 # via -r requirements/edx/base.in, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when
|
||||
|
||||
@@ -106,7 +106,7 @@ edx-api-doc-tools==1.3.1 # via -r requirements/edx/testing.txt
|
||||
edx-bulk-grades==0.7.0 # via -r requirements/edx/testing.txt, staff-graded-xblock
|
||||
edx-ccx-keys==1.1.0 # via -r requirements/edx/testing.txt
|
||||
edx-celeryutils==0.5.1 # via -r requirements/edx/testing.txt, super-csv
|
||||
edx-completion==3.2.1 # via -c requirements/edx/../constraints.txt, -r requirements/edx/testing.txt
|
||||
edx-completion==3.2.3 # via -r requirements/edx/testing.txt
|
||||
edx-django-release-util==0.4.4 # via -r requirements/edx/testing.txt
|
||||
edx-django-sites-extensions==2.5.1 # via -r requirements/edx/testing.txt
|
||||
edx-django-utils==3.2.3 # via -r requirements/edx/testing.txt, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when
|
||||
|
||||
@@ -103,7 +103,7 @@ edx-api-doc-tools==1.3.1 # via -r requirements/edx/base.txt
|
||||
edx-bulk-grades==0.7.0 # via -r requirements/edx/base.txt, staff-graded-xblock
|
||||
edx-ccx-keys==1.1.0 # via -r requirements/edx/base.txt
|
||||
edx-celeryutils==0.5.1 # via -r requirements/edx/base.txt, super-csv
|
||||
edx-completion==3.2.1 # via -c requirements/edx/../constraints.txt, -r requirements/edx/base.txt
|
||||
edx-completion==3.2.3 # via -r requirements/edx/base.txt
|
||||
edx-django-release-util==0.4.4 # via -r requirements/edx/base.txt
|
||||
edx-django-sites-extensions==2.5.1 # via -r requirements/edx/base.txt
|
||||
edx-django-utils==3.2.3 # via -r requirements/edx/base.txt, django-config-models, edx-drf-extensions, edx-enterprise, edx-rest-api-client, edx-when
|
||||
|
||||
Reference in New Issue
Block a user