Commit 4cb45571 by John Eskew

Merge pull request #7151 from open-craft/ekolpakov/settings-service

SettingsService for accessing django settings from XBlock
parents c430eaed c225d86e
...@@ -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),
}, },
) )
......
...@@ -329,3 +329,5 @@ VIDEO_CDN_URL = ENV_TOKENS.get('VIDEO_CDN_URL', {}) ...@@ -329,3 +329,5 @@ VIDEO_CDN_URL = ENV_TOKENS.get('VIDEO_CDN_URL', {})
if FEATURES['ENABLE_COURSEWARE_INDEX']: if FEATURES['ENABLE_COURSEWARE_INDEX']:
# Use ElasticSearch for the search engine # Use ElasticSearch for the search engine
SEARCH_ENGINE = "search.elastic.ElasticSearchEngine" SEARCH_ENGINE = "search.elastic.ElasticSearchEngine"
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
"""
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])
...@@ -5,12 +5,12 @@ Module implementing `xblock.runtime.Runtime` functionality for the LMS ...@@ -5,12 +5,12 @@ Module implementing `xblock.runtime.Runtime` functionality for the LMS
import re import re
import xblock.reference.plugins import xblock.reference.plugins
from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.conf import settings 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
...@@ -204,6 +204,7 @@ class LmsModuleSystem(LmsHandlerUrls, ModuleSystem): # pylint: disable=abstract ...@@ -204,6 +204,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)
......
...@@ -522,3 +522,5 @@ if FEATURES.get('ENABLE_COURSEWARE_SEARCH'): ...@@ -522,3 +522,5 @@ if FEATURES.get('ENABLE_COURSEWARE_SEARCH'):
FACEBOOK_API_VERSION = AUTH_TOKENS.get("FACEBOOK_API_VERSION") FACEBOOK_API_VERSION = AUTH_TOKENS.get("FACEBOOK_API_VERSION")
FACEBOOK_APP_SECRET = AUTH_TOKENS.get("FACEBOOK_APP_SECRET") FACEBOOK_APP_SECRET = AUTH_TOKENS.get("FACEBOOK_APP_SECRET")
FACEBOOK_APP_ID = AUTH_TOKENS.get("FACEBOOK_APP_ID") FACEBOOK_APP_ID = AUTH_TOKENS.get("FACEBOOK_APP_ID")
XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
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