Commit 25d8d2e6 by E. Kolpakov Committed by David Baumgold

SettingsService for accessing server-wide settings from XBlock

parent 269d6251
...@@ -15,6 +15,7 @@ from xmodule.contentstore.django import contentstore ...@@ -15,6 +15,7 @@ from xmodule.contentstore.django import contentstore
from xmodule.error_module import ErrorDescriptor from xmodule.error_module import ErrorDescriptor
from xmodule.exceptions import NotFoundError, ProcessingError from xmodule.exceptions import NotFoundError, ProcessingError
from xmodule.library_tools import LibraryToolsService from xmodule.library_tools import LibraryToolsService
from xmodule.services import SettingsService
from xmodule.modulestore.django import modulestore, ModuleI18nService from xmodule.modulestore.django import modulestore, ModuleI18nService
from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.keys import UsageKey
from opaque_keys.edx.locator import LibraryUsageLocator from opaque_keys.edx.locator import LibraryUsageLocator
...@@ -191,6 +192,7 @@ def _preview_module_system(request, descriptor, field_data): ...@@ -191,6 +192,7 @@ def _preview_module_system(request, descriptor, field_data):
"i18n": ModuleI18nService(), "i18n": ModuleI18nService(),
"field-data": field_data, "field-data": field_data,
"library_tools": LibraryToolsService(modulestore()), "library_tools": LibraryToolsService(modulestore()),
"settings": SettingsService(),
"user": DjangoXBlockUserService(request.user), "user": DjangoXBlockUserService(request.user),
}, },
) )
......
"""
Module contains various XModule/XBlock services
"""
from django.conf import settings
class SettingsService(object):
"""
Allows server-wide configuration of XBlocks on a per-type basis
XBlock settings are read from XBLOCK_SETTINGS settings key. Each XBlock is allowed access
to single settings bucket. Bucket is determined by this service using the following rules:
* Value of SettingsService.xblock_settings_bucket_selector is examined. If XBlock have attribute/property
with the name of that value this attribute/property is read to get the bucket key (e.g. if XBlock have
`block_settings_key = 'my_block_settings'`, bucket key would be 'my_block_settings').
* Otherwise, XBlock class name is used
Service is content-agnostic: it just returns whatever happen to be in the settings bucket (technically, it returns
the bucket itself).
If `default` argument is specified it is returned if:
* There are no XBLOCK_SETTINGS setting
* XBLOCK_SETTINGS is empty
* XBLOCK_SETTINGS does not contain settings bucket
If `default` is not specified or None, empty dictionary is used for default.
Example:
"XBLOCK_SETTINGS": {
"my_block": {
"setting1": 1,
"setting2": []
},
"my_other_block": [1, 2, 3],
"MyThirdBlock": "QWERTY"
}
class MyBlock: block_settings_key='my_block'
class MyOtherBlock: block_settings_key='my_other_block'
class MyThirdBlock: pass
class MissingBlock: pass
service = SettingsService()
service.get_settings_bucket(MyBlock()) # { "setting1": 1, "setting2": [] }
service.get_settings_bucket(MyOtherBlock()) # [1, 2, 3]
service.get_settings_bucket(MyThirdBlock()) # "QWERTY"
service.get_settings_bucket(MissingBlock()) # {}
service.get_settings_bucket(MissingBlock(), "default") # "default"
service.get_settings_bucket(MissingBlock(), None) # {}
"""
xblock_settings_bucket_selector = 'block_settings_key'
def get_settings_bucket(self, block, default=None):
""" Gets xblock settings dictionary from settings. """
if not block:
raise ValueError("Expected XBlock instance, got {0} of type {1}".format(block, type(block)))
actual_default = default if default is not None else {}
xblock_settings_bucket = getattr(block, self.xblock_settings_bucket_selector, block.unmixed_class.__name__)
xblock_settings = settings.XBLOCK_SETTINGS if hasattr(settings, "XBLOCK_SETTINGS") else {}
return xblock_settings.get(xblock_settings_bucket, actual_default)
"""
Tests for SettingsService
"""
import ddt
import mock
from unittest import TestCase
from django.conf import settings
from django.test.utils import override_settings
from xblock.runtime import Mixologist
from xmodule.services import SettingsService
class _DummyBlock(object):
""" Dummy Xblock class """
pass
@ddt.ddt
class TestSettingsService(TestCase):
""" Test SettingsService """
xblock_setting_key1 = 'dummy_block'
xblock_setting_key2 = 'other_dummy_block'
def setUp(self):
""" Setting up tests """
super(TestSettingsService, self).setUp()
self.settings_service = SettingsService()
self.xblock_mock = mock.Mock()
self.xblock_mock.block_settings_key = self.xblock_setting_key1
self.xblock_mock.unmixed_class = mock.Mock()
self.xblock_mock.unmixed_class.__name__ = self.xblock_setting_key2
def test_get_given_none_throws_value_error(self):
""" Test that given None throws value error """
with self.assertRaises(ValueError):
self.settings_service.get_settings_bucket(None)
def test_get_return_default_if_xblock_settings_is_missing(self):
""" Test that returns default (or None if default not set) if XBLOCK_SETTINGS is not set """
self.assertFalse(hasattr(settings, 'XBLOCK_SETTINGS')) # precondition check
self.assertEqual(self.settings_service.get_settings_bucket(self.xblock_mock, 'zzz'), 'zzz')
def test_get_return_empty_dictionary_if_xblock_settings_and_default_is_missing(self):
""" Test that returns default (or None if default not set) if XBLOCK_SETTINGS is not set """
self.assertFalse(hasattr(settings, 'XBLOCK_SETTINGS')) # precondition check
self.assertEqual(self.settings_service.get_settings_bucket(self.xblock_mock), {})
@override_settings(XBLOCK_SETTINGS={xblock_setting_key2: {'b': 1}})
def test_get_returns_none_or_default_if_bucket_not_found(self):
""" Test if settings service returns default if setting not found """
self.assertEqual(getattr(settings, 'XBLOCK_SETTINGS'), {self.xblock_setting_key2: {'b': 1}})
self.assertEqual(self.settings_service.get_settings_bucket(self.xblock_mock), {})
self.assertEqual(self.settings_service.get_settings_bucket(self.xblock_mock, 123), 123)
@override_settings(XBLOCK_SETTINGS={xblock_setting_key1: 42})
def test_get_returns_correct_value(self):
""" Test if settings service returns correct bucket """
self.assertEqual(getattr(settings, 'XBLOCK_SETTINGS'), {self.xblock_setting_key1: 42})
self.assertEqual(self.settings_service.get_settings_bucket(self.xblock_mock), 42)
@override_settings(XBLOCK_SETTINGS={xblock_setting_key2: "I'm a setting"})
def test_get_respects_block_settings_key(self):
""" Test if settings service respects block_settings_key value """
self.assertEqual(getattr(settings, 'XBLOCK_SETTINGS'), {self.xblock_setting_key2: "I'm a setting"})
self.xblock_mock.block_settings_key = self.xblock_setting_key2
self.assertEqual(self.settings_service.get_settings_bucket(self.xblock_mock), "I'm a setting")
@override_settings(XBLOCK_SETTINGS={_DummyBlock.__name__: [1, 2, 3]})
def test_get_uses_class_name_if_block_settings_key_is_not_set(self):
""" Test if settings service uses class name if block_settings_key attribute does not exist """
mixologist = Mixologist([])
block = mixologist.mix(_DummyBlock)
self.assertEqual(getattr(settings, 'XBLOCK_SETTINGS'), {"_DummyBlock": [1, 2, 3]})
self.assertEqual(self.settings_service.get_settings_bucket(block), [1, 2, 3])
...@@ -10,6 +10,7 @@ from django.conf import settings ...@@ -10,6 +10,7 @@ from django.conf import settings
from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig from lms.djangoapps.lms_xblock.models import XBlockAsidesConfig
from openedx.core.djangoapps.user_api.api import course_tag as user_course_tag_api from openedx.core.djangoapps.user_api.api import course_tag as user_course_tag_api
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from xmodule.services import SettingsService
from xmodule.library_tools import LibraryToolsService from xmodule.library_tools import LibraryToolsService
from xmodule.x_module import ModuleSystem from xmodule.x_module import ModuleSystem
from xmodule.partitions.partitions_service import PartitionService from xmodule.partitions.partitions_service import PartitionService
...@@ -202,6 +203,7 @@ class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract ...@@ -202,6 +203,7 @@ class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract
) )
services['library_tools'] = LibraryToolsService(modulestore()) services['library_tools'] = LibraryToolsService(modulestore())
services['fs'] = xblock.reference.plugins.FSService() services['fs'] = xblock.reference.plugins.FSService()
services['settings'] = SettingsService()
self.request_token = kwargs.pop('request_token', None) self.request_token = kwargs.pop('request_token', None)
super(LmsModuleSystem, self).__init__(**kwargs) super(LmsModuleSystem, self).__init__(**kwargs)
......
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