Skip to content

Commit

Permalink
Extend overhangio scorm xblock with custom storage
Browse files Browse the repository at this point in the history
GN-1174
  • Loading branch information
igobranco committed May 26, 2023
1 parent 19cf32d commit 546425d
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down
Empty file.
89 changes: 89 additions & 0 deletions nau_openedx_extensions/scorm/storage.py
Original file line number Diff line number Diff line change
@@ -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://<bucket_domain>" 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)
8 changes: 8 additions & 0 deletions nau_openedx_extensions/settings/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
43 changes: 43 additions & 0 deletions nau_openedx_extensions/tests/test_scorm.py
Original file line number Diff line number Diff line change
@@ -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')

0 comments on commit 546425d

Please sign in to comment.