diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 0f67cf480d3c79d94ec02b045d082e43263dec76..9a22b279addb7800b33fcffccae3ab4f3701d612 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,6 +15,14 @@ Please See the [releases tab](https://github.com/edx/xblock-lti-consumer/release Unreleased ~~~~~~~~~~ +6.1.0 - 2022-11-08 +------------------ +* 6.0.0 broke studio functionality because it leaned more heavily on the xblock load which only worked in the LMS. + + * Fix by greatly limiting when we attempt a full xblock load and bind + + + 6.0.0 - 2022-10-24 ------------------ BREAKING CHANGE: diff --git a/lti_consumer/__init__.py b/lti_consumer/__init__.py index 0e810af59c09127ad16a744f219d15bd300706e3..f96a3a207c0d000c2692d93375dad49a0d5c42a8 100644 --- a/lti_consumer/__init__.py +++ b/lti_consumer/__init__.py @@ -4,4 +4,4 @@ Runtime will load the XBlock class from here. from .apps import LTIConsumerApp from .lti_xblock import LtiConsumerXBlock -__version__ = '6.0.0' +__version__ = '6.1.0' diff --git a/lti_consumer/api.py b/lti_consumer/api.py index dc1eed0776ea016eabf7bfbcff4999b21bf348d6..f0f3dd7059dc72d8a392f07c68c0a96a26d19cfa 100644 --- a/lti_consumer/api.py +++ b/lti_consumer/api.py @@ -83,11 +83,6 @@ def _get_lti_config_for_block(block): block.location, LtiConfiguration.CONFIG_ON_XBLOCK, ) - - # Since the block was passed, preload it to avoid - # having to instance the modulestore and fetch it again. - lti_config.block = block - return lti_config diff --git a/lti_consumer/models.py b/lti_consumer/models.py index acf8eb267b3e0cac38f6a7fd398f3f2ddf908dc5..0254f92ce6eb3fc00ed8fb5d3c14a8baf8597e0e 100644 --- a/lti_consumer/models.py +++ b/lti_consumer/models.py @@ -227,17 +227,14 @@ class LtiConfiguration(models.Model): 'grades.' ) - # Empty variable that'll hold the block once it's retrieved - # from the modulestore or preloaded - _block = None - def clean(self): if self.config_store == self.CONFIG_ON_XBLOCK and self.location is None: raise ValidationError({ "config_store": _("LTI Configuration stores on XBlock needs a block location set."), }) if self.version == self.LTI_1P3 and self.config_store == self.CONFIG_ON_DB: - if not database_config_enabled(self.block.location.course_key): + block = compat.load_enough_xblock(self.location) + if not database_config_enabled(block.location.course_key): raise ValidationError({ "config_store": _("LTI Configuration stores on database is not enabled."), }) @@ -255,25 +252,6 @@ class LtiConfiguration(models.Model): if consumer is None: raise ValidationError(_("Invalid LTI configuration.")) - @property - def block(self): - """ - Return instance of block (either preloaded or directly from the modulestore). - """ - block = getattr(self, '_block', None) - if block is None: - if self.location is None: - raise ValueError(_("Block location not set, it's not possible to retrieve the block.")) - block = self._block = compat.load_block_as_user(self.location) - return block - - @block.setter - def block(self, block): - """ - Allows preloading the block instead of fetching it from the modulestore. - """ - self._block = block - def _generate_lti_1p3_keys_if_missing(self): """ Generate LTI 1.3 RSA256 keys if missing. @@ -336,8 +314,9 @@ class LtiConfiguration(models.Model): """ # If LTI configuration is stored in the XBlock. if self.config_store == self.CONFIG_ON_XBLOCK: - key, secret = self.block.lti_provider_key_secret - launch_url = self.block.launch_url + block = compat.load_enough_xblock(self.location) + key, secret = block.lti_provider_key_secret + launch_url = block.launch_url elif self.config_store == self.CONFIG_EXTERNAL: config = get_external_config_from_filter({}, self.external_id) key = config.get("lti_1p1_client_key") @@ -360,7 +339,8 @@ class LtiConfiguration(models.Model): if self.config_store == self.CONFIG_ON_DB: return self.lti_advantage_ags_mode else: - return self.block.lti_advantage_ags_mode + block = compat.load_enough_xblock(self.location) + return block.lti_advantage_ags_mode def get_lti_advantage_deep_linking_enabled(self): """ @@ -372,7 +352,8 @@ class LtiConfiguration(models.Model): if self.config_store == self.CONFIG_ON_DB: return self.lti_advantage_deep_linking_enabled else: - return self.block.lti_advantage_deep_linking_enabled + block = compat.load_enough_xblock(self.location) + return block.lti_advantage_deep_linking_enabled def get_lti_advantage_deep_linking_launch_url(self): """ @@ -384,7 +365,8 @@ class LtiConfiguration(models.Model): if self.config_store == self.CONFIG_ON_DB: return self.lti_advantage_deep_linking_launch_url else: - return self.block.lti_advantage_deep_linking_launch_url + block = compat.load_enough_xblock(self.location) + return block.lti_advantage_deep_linking_launch_url def get_lti_advantage_nrps_enabled(self): """ @@ -396,7 +378,8 @@ class LtiConfiguration(models.Model): if self.config_store == self.CONFIG_ON_DB: return self.lti_advantage_enable_nrps else: - return self.block.lti_1p3_enable_nrps + block = compat.load_enough_xblock(self.location) + return block.lti_1p3_enable_nrps def _setup_lti_1p3_ags(self, consumer): """ @@ -419,21 +402,21 @@ class LtiConfiguration(models.Model): # and manage lineitems using the AGS endpoints. if not lineitem and lti_advantage_ags_mode == self.LTI_ADVANTAGE_AGS_DECLARATIVE: try: - block = self.block + block = compat.load_enough_xblock(self.location) except ValueError: # There is no location to load the block block = None if block: default_values = { 'resource_id': self.location, - 'score_maximum': self.block.weight, - 'label': self.block.display_name, + 'score_maximum': block.weight, + 'label': block.display_name, } - if hasattr(self.block, 'start'): - default_values['start_date_time'] = self.block.start + if hasattr(block, 'start'): + default_values['start_date_time'] = block.start - if hasattr(self.block, 'due'): - default_values['end_date_time'] = self.block.due + if hasattr(block, 'due'): + default_values['end_date_time'] = block.due else: # TODO find a way to make these defaults more sensible default_values = { @@ -488,10 +471,11 @@ class LtiConfiguration(models.Model): look for the configuration and instance the class. """ if self.config_store == self.CONFIG_ON_XBLOCK: + block = compat.load_enough_xblock(self.location) consumer = LtiAdvantageConsumer( iss=get_lms_base(), - lti_oidc_url=self.block.lti_1p3_oidc_url, - lti_launch_url=self.block.lti_1p3_launch_url, + lti_oidc_url=block.lti_1p3_oidc_url, + lti_launch_url=block.lti_1p3_launch_url, client_id=self.lti_1p3_client_id, # Deployment ID hardcoded to 1 since # we're not using multi-tenancy. @@ -500,8 +484,8 @@ class LtiConfiguration(models.Model): rsa_key=self.lti_1p3_private_key, rsa_key_id=self.lti_1p3_private_key_id, # LTI 1.3 Tool key/keyset url - tool_key=self.block.lti_1p3_tool_public_key, - tool_keyset_url=self.block.lti_1p3_tool_keyset_url, + tool_key=block.lti_1p3_tool_public_key, + tool_keyset_url=block.lti_1p3_tool_keyset_url, ) elif self.config_store == self.CONFIG_ON_DB: consumer = LtiAdvantageConsumer( diff --git a/lti_consumer/plugin/compat.py b/lti_consumer/plugin/compat.py index 1ce76a0fb4b3d0559520f7fe1ea5928053feb145..5a37686618436f79ccdec1147563f5f665307e33 100644 --- a/lti_consumer/plugin/compat.py +++ b/lti_consumer/plugin/compat.py @@ -60,18 +60,30 @@ def get_database_config_waffle_flag(): return CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.{ENABLE_DATABASE_CONFIG}', __name__) +def load_enough_xblock(location): # pragma: nocover + """ + Load enough of an xblock to read from for LTI values stored on the block. + The block may or may not be bound to the user for actual use depending on + what has happened in the request so far. + """ + # pylint: disable=import-error,import-outside-toplevel + from xmodule.modulestore.django import modulestore + + # Retrieve descriptor from modulestore + return modulestore().get_item(location) + + def load_block_as_user(location): # pragma: nocover """ Load a block as the current user, or load as the anonymous user if no user is available. """ # pylint: disable=import-error,import-outside-toplevel from crum import get_current_user, get_current_request - from xmodule.modulestore.django import modulestore from lms.djangoapps.courseware.module_render import get_module_for_descriptor_internal from openedx.core.lib.xblock_utils import request_token # Retrieve descriptor from modulestore - descriptor = modulestore().get_item(location) + descriptor = load_enough_xblock(location) user = get_current_user() request = get_current_request() if user and request: diff --git a/lti_consumer/plugin/views.py b/lti_consumer/plugin/views.py index 805cd9a5d3acc2489674019da47781dd1be67b97..bc896a46d8f362301ca21edcaed4434788a96240 100644 --- a/lti_consumer/plugin/views.py +++ b/lti_consumer/plugin/views.py @@ -480,7 +480,8 @@ def deep_linking_content_endpoint(request, lti_config_id): raise Http404 from exc # check if user has proper access - if not has_block_access(request.user, lti_config.block, lti_config.location.course_key): + block = compat.load_block_as_user(lti_config.location) + if not has_block_access(request.user, block, lti_config.location.course_key): log.warning( "Permission on LTI Config %r denied for user %r.", lti_config_id, @@ -499,7 +500,7 @@ def deep_linking_content_endpoint(request, lti_config_id): # Render LTI-DL contents return render(request, 'html/lti-dl/render_dl_content.html', { 'content_items': content_items, - 'block': lti_config.block, + 'block': block, 'launch_data': launch_data, }) diff --git a/lti_consumer/tests/unit/plugin/test_views.py b/lti_consumer/tests/unit/plugin/test_views.py index 4d7e9e99818fae94675a3ce2d9514d58d4e19d19..e1bce12dfd4c6de283d42f487a68301ec7200e0c 100644 --- a/lti_consumer/tests/unit/plugin/test_views.py +++ b/lti_consumer/tests/unit/plugin/test_views.py @@ -133,19 +133,21 @@ class TestLti1p3LaunchGateEndpoint(TestCase): ) self.launch_data_key = cache_lti_1p3_launch_data(self.launch_data) - self.compat_patcher = patch("lti_consumer.plugin.views.compat") - self.compat = self.compat_patcher.start() - self.addCleanup(self.compat.stop) + compat_patcher = patch("lti_consumer.plugin.views.compat") + self.addCleanup(compat_patcher.stop) + self.compat = compat_patcher.start() course = Mock(name="course") course.display_name_with_default = "course_display_name" course.display_org_with_default = "course_display_org" self.compat.get_course_by_id.return_value = course self.compat.get_user_role.return_value = "student" self.compat.get_external_id_for_user.return_value = "12345" + model_compat_patcher = patch("lti_consumer.models.compat") + self.addCleanup(model_compat_patcher.stop) model_compat = model_compat_patcher.start() + model_compat.load_enough_xblock.return_value = self.xblock model_compat.load_block_as_user.return_value = self.xblock - self.addCleanup(model_compat_patcher.stop) def test_invalid_lti_version(self): """ diff --git a/lti_consumer/tests/unit/plugin/test_views_lti_ags.py b/lti_consumer/tests/unit/plugin/test_views_lti_ags.py index 856419b9bfc9e73f5639ee4fbc090538867f3e4e..a3dd32845daa67c2d345f1889b96758cbefa42a7 100644 --- a/lti_consumer/tests/unit/plugin/test_views_lti_ags.py +++ b/lti_consumer/tests/unit/plugin/test_views_lti_ags.py @@ -3,7 +3,7 @@ Tests for LTI Advantage Assignments and Grades Service views. """ import json from datetime import timedelta -from unittest.mock import patch, PropertyMock, Mock +from unittest.mock import patch, Mock from Cryptodome.PublicKey import RSA import ddt @@ -54,17 +54,14 @@ class LtiAgsLineItemViewSetTestCase(APITransactionTestCase): location=self.xblock.location, # pylint: disable=no-member version=LtiConfiguration.LTI_1P3, ) - # Preload XBlock to avoid calls to modulestore - self.lti_config.block = self.xblock # Patch internal method to avoid calls to modulestore patcher = patch( - 'lti_consumer.models.LtiConfiguration.block', - new_callable=PropertyMock, - return_value=self.xblock + 'lti_consumer.plugin.compat.load_enough_xblock', ) self.addCleanup(patcher.stop) - self._lti_block_patch = patcher.start() + self._load_block_patch = patcher.start() + self._load_block_patch.return_value = self.xblock self._mock_user = Mock() compat_mock = patch("lti_consumer.signals.compat") diff --git a/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py b/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py index 445730f038f58697d8a8bbb5559019eaa80a86f6..6f1c5eeb956f8efad7f979c86293ce75e8ee564d 100644 --- a/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py +++ b/lti_consumer/tests/unit/plugin/test_views_lti_deep_linking.py @@ -1,7 +1,7 @@ """ Tests for LTI Advantage Assignments and Grades Service views. """ -from unittest.mock import patch, PropertyMock, Mock +from unittest.mock import patch, Mock import re import ddt @@ -63,24 +63,27 @@ class LtiDeepLinkingTestCase(APITransactionTestCase): for key, value in self.xblock_attributes.items(): setattr(self.xblock, key, value) - # Preload XBlock to avoid calls to modulestore - self.lti_config.block = self.xblock + # Patch internal methods to avoid calls to modulestore + enough_mock = patch( + 'lti_consumer.plugin.compat.load_enough_xblock', + ) + self.addCleanup(enough_mock.stop) + self._load_block_patch = enough_mock.start() + self._load_block_patch.return_value = self.xblock - # Patch internal method to avoid calls to modulestore - patcher = patch( - 'lti_consumer.models.LtiConfiguration.block', - new_callable=PropertyMock, - return_value=self.xblock + # some deep linking endpoints still load the xblock as its user for access check + as_user_mock = patch( + 'lti_consumer.plugin.compat.load_block_as_user', ) - self.addCleanup(patcher.stop) - self._lti_block_patch = patcher.start() + self.addCleanup(as_user_mock.stop) + self._load_block_as_user_patch = as_user_mock.start() + self._load_block_as_user_patch.return_value = self.xblock self._mock_user = Mock() - compat_mock = patch("lti_consumer.signals.compat") - self.addCleanup(compat_mock.stop) - self._compat_mock = compat_mock.start() - self._compat_mock.get_user_from_external_user_id.return_value = self._mock_user - self._compat_mock.load_block_as_user.return_value = self.xblock + get_user_mock = patch("lti_consumer.plugin.compat.get_user_from_external_user_id") + self.addCleanup(get_user_mock.stop) + self._get_user_patch = get_user_mock.start() + self._get_user_patch.return_value = self._mock_user @ddt.ddt @@ -485,7 +488,7 @@ class LtiDeepLinkingContentEndpointTestCase(LtiDeepLinkingTestCase): resp = self.client.get(self.url) self.assertEqual(resp.status_code, 200) - expected_title = '{} | Deep Linking Contents'.format(self.lti_config.block.display_name) + expected_title = '{} | Deep Linking Contents'.format(self.xblock.display_name) self.assertContains(resp, expected_title) @ddt.data( diff --git a/lti_consumer/tests/unit/plugin/test_views_lti_nrps.py b/lti_consumer/tests/unit/plugin/test_views_lti_nrps.py index 2a40014e2fefb7e86b6e2275326ad11a35b53281..a9d3e1e2a76c0256eaf55884266747f61148805f 100644 --- a/lti_consumer/tests/unit/plugin/test_views_lti_nrps.py +++ b/lti_consumer/tests/unit/plugin/test_views_lti_nrps.py @@ -1,7 +1,7 @@ """ Tests for LTI Names and Role Provisioning Service views. """ -from unittest.mock import Mock, patch, PropertyMock +from unittest.mock import Mock, patch from Cryptodome.PublicKey import RSA from jwkest.jwk import RSAKey from rest_framework.test import APITransactionTestCase @@ -140,17 +140,14 @@ class LtiNrpsTestCase(APITransactionTestCase): location=self.xblock.location, # pylint: disable=no-member version=LtiConfiguration.LTI_1P3, ) - # Preload XBlock to avoid calls to modulestore - self.lti_config.block = self.xblock # Patch internal method to avoid calls to modulestore patcher = patch( - 'lti_consumer.models.LtiConfiguration.block', - new_callable=PropertyMock, - return_value=self.xblock + 'lti_consumer.plugin.compat.load_enough_xblock', ) self.addCleanup(patcher.stop) - self._lti_block_patch = patcher.start() + self._load_block_patch = patcher.start() + self._load_block_patch.return_value = self.xblock self.context_membership_endpoint = reverse( 'lti_consumer:lti-nrps-memberships-view-list', diff --git a/lti_consumer/tests/unit/test_api.py b/lti_consumer/tests/unit/test_api.py index 54c8e0fad143f0086af49bfae4295ee21a095a2f..446e07743ec9b09728a9dacfdc27f033662b71d8 100644 --- a/lti_consumer/tests/unit/test_api.py +++ b/lti_consumer/tests/unit/test_api.py @@ -41,7 +41,7 @@ class Lti1P3TestCase(TestCase): # Patch compat method to avoid calls to modulestore patcher = patch( - 'lti_consumer.plugin.compat.load_block_as_user', + 'lti_consumer.plugin.compat.load_enough_xblock', ) self.addCleanup(patcher.stop) self._load_block_patch = patcher.start() @@ -72,7 +72,6 @@ class Lti1P3TestCase(TestCase): self.lti_config = LtiConfiguration.objects.create( config_id=_test_config_id, location=self.xblock.location, # pylint: disable=no-member - block=self.xblock, version=LtiConfiguration.LTI_1P3, config_store=LtiConfiguration.CONFIG_ON_XBLOCK, ) diff --git a/lti_consumer/tests/unit/test_lti_xblock.py b/lti_consumer/tests/unit/test_lti_xblock.py index b960b2073f21daf5ac33af705032c5dbd6096f55..adbf3d2e533caa539760682fc942e094e8599e42 100644 --- a/lti_consumer/tests/unit/test_lti_xblock.py +++ b/lti_consumer/tests/unit/test_lti_xblock.py @@ -598,7 +598,7 @@ class TestGetLti1p1Consumer(TestLtiConsumerXBlock): self.xblock.lti_id = provider type(mock_course).lti_passports = PropertyMock(return_value=[f"{provider}:{key}:{secret}"]) - with patch('lti_consumer.plugin.compat.load_block_as_user', return_value=self.xblock): + with patch('lti_consumer.plugin.compat.load_enough_xblock', return_value=self.xblock): self.xblock._get_lti_consumer() # pylint: disable=protected-access mock_lti_consumer.assert_called_with(self.xblock.launch_url, key, secret) @@ -1573,15 +1573,15 @@ class TestLti1p3AccessTokenEndpoint(TestLtiConsumerXBlock): self.xblock = make_xblock('lti_consumer', LtiConsumerXBlock, self.xblock_attributes) patcher = patch( - 'lti_consumer.models.compat', - **{'load_block_as_user.return_value': self.xblock} + 'lti_consumer.plugin.compat.load_enough_xblock', ) - patcher.start() self.addCleanup(patcher.stop) + self._load_block_patch = patcher.start() + self._load_block_patch.return_value = self.xblock def test_access_token_endpoint_when_using_lti_1p1(self): """ - Test that the LTI 1.3 access token endpoind is unavailable when using 1.1. + Test that the LTI 1.3 access token endpoint is unavailable when using 1.1. """ self.xblock.lti_version = 'lti_1p1' self.xblock.save() @@ -1594,7 +1594,7 @@ class TestLti1p3AccessTokenEndpoint(TestLtiConsumerXBlock): def test_access_token_endpoint_no_post(self): """ - Test that the LTI 1.3 access token endpoind is unavailable when using 1.1. + Test that the LTI 1.3 access token endpoint is unavailable when using 1.1. """ request = make_request('', 'GET') @@ -1747,12 +1747,13 @@ class TestLti1p3AccessTokenJWK(TestCase): jwt = create_jwt(self.key, {}) self.request = make_jwt_request(jwt) + patcher = patch( - 'lti_consumer.models.compat', - **{'load_block_as_user.return_value': self.xblock} + 'lti_consumer.plugin.compat.load_enough_xblock', ) - patcher.start() self.addCleanup(patcher.stop) + self._load_block_patch = patcher.start() + self._load_block_patch.return_value = self.xblock def make_keyset(self, keys): """ diff --git a/lti_consumer/tests/unit/test_models.py b/lti_consumer/tests/unit/test_models.py index f1562bc4989cc126e8e64b23b3c8770b6694e463..e6d1298e3aad65eb5588e8423e7843022cd2595b 100644 --- a/lti_consumer/tests/unit/test_models.py +++ b/lti_consumer/tests/unit/test_models.py @@ -55,6 +55,13 @@ class TestLtiConfigurationModel(TestCase): } self.xblock = make_xblock('lti_consumer', LtiConsumerXBlock, self.xblock_attributes) + patcher = patch( + 'lti_consumer.plugin.compat.load_enough_xblock', + ) + self.addCleanup(patcher.stop) + self._load_block_patch = patcher.start() + self._load_block_patch.return_value = self.xblock + # Creates an LTI configuration objects for testing self.lti_1p1_config = LtiConfiguration.objects.create( location=self.xblock.location, # pylint: disable=no-member @@ -157,7 +164,6 @@ class TestLtiConfigurationModel(TestCase): config_store=config_store, lti_advantage_ags_mode='programmatic' ) - config.block = self.xblock # Get LTI 1.3 consumer consumer = config.get_lti_consumer() @@ -189,7 +195,6 @@ class TestLtiConfigurationModel(TestCase): Check if LTI AGS is properly returned. """ config = self._get_1p3_config(config_store=config_store, lti_advantage_ags_mode='disabled') - config.block = self.xblock self.xblock.lti_advantage_ags_mode = 'XBlock' @@ -212,7 +217,6 @@ class TestLtiConfigurationModel(TestCase): # Get LTI 1.3 consumer config = self._get_1p3_config(config_store=config_store, lti_advantage_ags_mode='declarative') - config.block = self.xblock consumer = config.get_lti_consumer() @@ -242,7 +246,6 @@ class TestLtiConfigurationModel(TestCase): config_store=config_store, lti_advantage_deep_linking_enabled=True ) - config.block = self.xblock # Get LTI 1.3 consumer consumer = config.get_lti_consumer() @@ -261,7 +264,6 @@ class TestLtiConfigurationModel(TestCase): Check if LTI Deep Linking enabled is properly returned. """ config = self._get_1p3_config(config_store=config_store, lti_advantage_deep_linking_enabled=True) - config.block = self.xblock self.xblock.lti_advantage_deep_linking_enabled = False @@ -282,7 +284,6 @@ class TestLtiConfigurationModel(TestCase): Check if LTI Deep Linking launch URL is properly returned. """ config = self._get_1p3_config(config_store=config_store, lti_advantage_deep_linking_launch_url='database') - config.block = self.xblock self.xblock.lti_advantage_deep_linking_launch_url = 'XBlock' @@ -303,7 +304,6 @@ class TestLtiConfigurationModel(TestCase): Check if LTI Deep Linking launch URL is properly returned. """ config = self._get_1p3_config(config_store=config_store, lti_advantage_enable_nrps=True) - config.block = self.xblock self.xblock.lti_advantage_enable_nrps = False @@ -313,24 +313,6 @@ class TestLtiConfigurationModel(TestCase): with self.assertRaises(NotImplementedError): config.get_lti_advantage_nrps_enabled() - @patch("lti_consumer.models.compat") - def test_block_property(self, compat_mock): - """ - Check if a block is properly loaded when calling the `block` property. - """ - compat_mock.load_block_as_user.return_value = self.xblock - - block = self.lti_1p3_config.block - self.assertEqual(block, self.xblock) - - def test_block_property_missing_location(self): - """ - Check the `block` property raises when failing to retrieve a block. - """ - self.lti_1p3_config.location = None - with self.assertRaises(ValueError): - _ = self.lti_1p3_config.block - def test_generate_private_key(self): """ Checks if a private key is correctly generated. @@ -381,9 +363,6 @@ class TestLtiConfigurationModel(TestCase): self.lti_1p3_config.clean() self.lti_1p3_config.config_store = self.lti_1p3_config.CONFIG_ON_DB - self.lti_1p3_config.block = self.xblock - - self.lti_1p3_config_db.block = self.xblock with patch("lti_consumer.models.database_config_enabled", return_value=False),\ self.assertRaises(ValidationError):