diff --git a/nau_openedx_extensions/custom_registration_form/context_extender.py b/nau_openedx_extensions/custom_registration_form/context_extender.py index b04fb45..74ecd1c 100644 --- a/nau_openedx_extensions/custom_registration_form/context_extender.py +++ b/nau_openedx_extensions/custom_registration_form/context_extender.py @@ -9,9 +9,9 @@ from django.core.exceptions import ObjectDoesNotExist from django.db import models from django.utils.translation import ugettext as _ -from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers # pylint: disable=import-error from nau_openedx_extensions.custom_registration_form.models import NauUserExtendedModel +from nau_openedx_extensions.edxapp_wrapper import site_configuration_helpers as configuration_helpers log = logging.getLogger(__name__) diff --git a/nau_openedx_extensions/scorm/__init__.py b/nau_openedx_extensions/scorm/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nau_openedx_extensions/scorm/storage.py b/nau_openedx_extensions/scorm/storage.py new file mode 100644 index 0000000..51a623c --- /dev/null +++ b/nau_openedx_extensions/scorm/storage.py @@ -0,0 +1,89 @@ +""" +NAU additional configuration of Overhangio scorm xblock for openedx. +https://github.com/overhangio/openedx-scorm-xblock + +On NAU we store the static assets on a S3 bucket. +By default the Django storage is configured so the user connects and get the files from the +S3 server / domain. +But, to make the SCORM score work, we need that the SCORM files need to be delivered on the +same domain. +This fixes: +``` +Error: Unable to aquire LMS API +``` + +JS console error: +``` +Uncaught DOMException: Blocked a frame with origin "https://" from accessing a cross-origin frame +``` +""" +from django.conf import settings +from django.utils.module_loading import import_string + +from nau_openedx_extensions.edxapp_wrapper import site_configuration_helpers as configuration_helpers + + +def scorm_xblock_storage(xblock): # pylint: disable=unused-argument + """ + Custom SCORM storage configuration. + So S3 storage to save the bucket + """ + return get_scorm_storage() if hasattr(settings, 'NAU_SCORM_XBLOCK_STORAGE') else None + + +def get_scorm_storage(): + """ + Return the configured django storage backend for SCORM xblock. + It requires the `NAU_SCORM_XBLOCK_STORAGE` setting and inside it uses additional settings: + - `STORAGE_CLASS` - the class of the Django storage to use + - `STORAGE_KWARGS` - the additional kwargs for the Django storage + The location can be changed by directly changing the location inside the xblock. + + ```python + XBLOCK_SETTINGS["ScormXBlock"] = { + "LOCATION": "alternatevalue", + } + ``` + + It automatically creates and adds the `custom_domain` on the kwargs of the storage. + ```yaml + NAU_SCORM_XBLOCK_STORAGE: + STORAGE_CLASS: storages.backends.s3boto3.S3Boto3Storage + STORAGE_KWARGS: + default_acl: public-read + querystring_expire: 86400 + access_key: XXXX + secret_key: YYYY + bucket: ZZZZ + ``` + """ + if hasattr(settings, 'NAU_SCORM_XBLOCK_STORAGE'): + storage_kwargs = get_storage_kwargs() + return get_storage_class( + settings.NAU_SCORM_XBLOCK_STORAGE.get('STORAGE_CLASS'), + )(**storage_kwargs) + + # during edx-platform loading this method gets called but settings are not ready yet + # so in that case we will return default(FileSystemStorage) storage class instance + return get_storage_class()() + + +def get_storage_kwargs(): + """ + Build Django storage class additional kwargs with a dynamic `custom_domain` parameter + that depends on the current in use LMS URL. + """ + + if settings.SERVICE_VARIANT == "lms": + domain = configuration_helpers.get_value('LMS_BASE', settings.LMS_BASE) + else: + domain = configuration_helpers.get_value('CMS_BASE', settings.CMS_BASE) + storage_kwargs_settings = settings.NAU_SCORM_XBLOCK_STORAGE.get('STORAGE_KWARGS', {}) + return {**storage_kwargs_settings, **{'custom_domain': domain}} + + +def get_storage_class(import_path=None): + """ + Get the Django storage class to be used by custom SCORM storage. + """ + return import_string(import_path or settings.DEFAULT_FILE_STORAGE) diff --git a/nau_openedx_extensions/settings/production.py b/nau_openedx_extensions/settings/production.py index f1df1e9..eb2f22f 100644 --- a/nau_openedx_extensions/settings/production.py +++ b/nau_openedx_extensions/settings/production.py @@ -76,3 +76,11 @@ def plugin_settings(settings): settings.SCORMXBLOCK_ASYNC_THRESHOLD = getattr(settings, "ENV_TOKENS", {}).get( "SCORMXBLOCK_ASYNC_THRESHOLD", settings.SCORMXBLOCK_ASYNC_THRESHOLD ) + + settings.NAU_SCORM_XBLOCK_STORAGE = getattr(settings, "ENV_TOKENS", {}).get( + "NAU_SCORM_XBLOCK_STORAGE", None + ) + from nau_openedx_extensions.scorm.storage import scorm_xblock_storage # pylint: disable=import-outside-toplevel + settings.XBLOCK_SETTINGS["ScormXBlock"] = { + "STORAGE_FUNC": scorm_xblock_storage, + } diff --git a/nau_openedx_extensions/tests/test_scorm.py b/nau_openedx_extensions/tests/test_scorm.py new file mode 100644 index 0000000..3c8e5da --- /dev/null +++ b/nau_openedx_extensions/tests/test_scorm.py @@ -0,0 +1,43 @@ +""" +Tests that check the extended settings for overhangio SCORM xblock. +""" +from django.test import TestCase +from django.test.utils import override_settings + +from nau_openedx_extensions.scorm.storage import get_storage_kwargs, scorm_xblock_storage + + +class SCORMXblockTest(TestCase): + """ + Tests that check the extended settings for overhangio SCORM xblock. + """ + + def test_scorm_storage_no_scorm_storage_setting(self): + """ + Test if `NAU_SCORM_XBLOCK_STORAGE` is not defined then we shouldn't create a custom storage for the + SCORM Xblock. + """ + self.assertIsNone(scorm_xblock_storage(None)) + + @override_settings(LMS_BASE='lms.example.com', SERVICE_VARIANT="lms", NAU_SCORM_XBLOCK_STORAGE={ + 'STORAGE_CLASS': 'storages.backends.s3boto3.S3Boto3Storage', + 'STORAGE_KWARGS': { + 'default_acl': 'public-read', + 'querystring_expire': 86400 + } + }) + def test_scorm_storage_custom_storage_with_kwargs_service_variant_lms(self): + """ + Test if NAU_SCORM_XBLOCK_STORAGE setting is defined we should have a custom storage. + """ + self.assertEqual(get_storage_kwargs().get('custom_domain'), 'lms.example.com') + + @override_settings(CMS_BASE='studio.example.com', SERVICE_VARIANT="cms", NAU_SCORM_XBLOCK_STORAGE={ + 'STORAGE_CLASS': 'storages.backends.s3boto3.S3Boto3Storage', + }) + def test_scorm_storage_custom_storage_with_kwargs_service_variant_studio(self): + """ + Test if NAU_SCORM_XBLOCK_STORAGE setting is defined, and we are running the studio instead + of lms, then we should return the studio domain on the custom domain. + """ + self.assertEqual(get_storage_kwargs().get('custom_domain'), 'studio.example.com')