Skip to content

Commit

Permalink
Custom storage for SCORM xblock
Browse files Browse the repository at this point in the history
  • Loading branch information
nauman4386 committed May 18, 2023
1 parent a47ef1c commit 9fddeff
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 12 deletions.
65 changes: 54 additions & 11 deletions openedxscorm/scormxblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import re
import xml.etree.ElementTree as ET
import zipfile
import requests
import mimetypes

from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
Expand Down Expand Up @@ -61,7 +63,6 @@ class ScormXBlock(XBlock, CompletableXBlockMixin):
Note that neither the folder the folder nor the package file are deleted when the
xblock is removed.
By default, static assets are stored in the default Django storage backend. To
override this behaviour, you should define a custom storage function. This
function must take the xblock instance as its first and only argument. For instance,
Expand Down Expand Up @@ -202,6 +203,35 @@ def student_view(self, context=None):
)
return frag

@XBlock.handler
def scorm_view(self, request, _suffix):
"""
View for serving SCORM content. It receives a request with the path to the SCORM content to serve, generates a pre-signed
URL to access the content in the AWS S3 bucket, retrieves the file content and returns it with the appropriate content
type.
Parameters:
----------
request : django.http.request.HttpRequest
HTTP request object containing the path to the SCORM content to serve.
_suffix : str
Unused parameter.
Returns:
-------
Response object containing the content of the requested file with the appropriate content type.
"""
path = request.url.split('scorm_view/')[-1]
path = re.sub(r"(\?.*|:[\d:]*$)", "", path)
file_name = os.path.basename(path)
signed_url = self.storage.url(os.path.join(self.extract_folder_path, path))
file_content = requests.get(signed_url).content
file_type, _ = mimetypes.guess_type(file_name)

return Response(
file_content, content_type=file_type
)

def studio_view(self, context=None):
# Note that we cannot use xblockutils's StudioEditableXBlockMixin because we
# need to support package file uploads.
Expand Down Expand Up @@ -326,20 +356,33 @@ def extract_package(self, package_file):
dest_path,
ContentFile(scorm_zipfile.read(zipinfo.filename)),
)

@property
def index_page_url(self):
if not self.package_meta or not self.index_page_path:
return ""
folder = self.extract_folder_path
if self.storage.exists(
os.path.join(self.extract_folder_base_path, self.index_page_path)
):
# For backward-compatibility, we must handle the case when the xblock data
# is stored in the base folder.
folder = self.extract_folder_base_path
logger.warning("Serving SCORM content from old-style path: %s", folder)
return self.storage.url(os.path.join(folder, self.index_page_path))

querystring_auth_setting = self.xblock_settings.get('SCORM_S3_QUERY_AUTH')
if querystring_auth_setting is False:
folder = self.extract_folder_path
if self.storage.exists(
os.path.join(self.extract_folder_base_path, self.index_page_path)
):
# For backward-compatibility, we must handle the case when the xblock data
# is stored in the base folder.
folder = self.extract_folder_base_path
logger.warning("Serving SCORM content from old-style path: %s", folder)
return self.storage.url(os.path.join(folder, self.index_page_path))
else:
url = self.runtime.handler_url(self, 'scorm_view')
if url.endswith('?'):
url = url.split('?')[0] + self.index_page_path
elif not url.endswith('/'):
url = url + '/' + self.index_page_path
else:
url = self.runtime.handler_url(self, 'scorm_view') + self.index_page_path

return url

@property
def extract_folder_path(self):
Expand Down
37 changes: 37 additions & 0 deletions openedxscorm/storage.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""
Storage backend for scorm metadata export.
"""
from django.core.files.storage import get_storage_class
from storages.backends.s3boto3 import S3Boto3Storage


class S3ScormStorage(S3Boto3Storage):
"""
S3 backend for scorm metadata export
"""
def __init__(self, bucket, querystring_auth, querystring_expire):
super().__init__(bucket=bucket, querystring_auth=querystring_auth,
querystring_expire=querystring_expire)


def scorm_storage(xblock):
"""
Creates and returns an instance of the S3ScormStorage class.
This function takes an xblock instance as its argument and returns an instance
of the S3ScormStorage class. The S3ScormStorage class is defined in the
'openedxscorm.storage' module and provides storage functionality specific to
SCORM XBlock.
Args:
xblock (XBlock): An instance of the SCORM XBlock.
Returns:
S3ScormStorage: An instance of the S3ScormStorage class.
"""
bucket = xblock.xblock_settings.get('SCORM_S3_BUCKET_NAME', None)
querystring_auth = xblock.xblock_settings.get('SCORM_S3_QUERY_AUTH', None)
querystring_expire = xblock.xblock_settings.get('SCORM_S3_EXPIRES_IN', 604800)
storage_class = get_storage_class('openedxscorm.storage.S3ScormStorage')
return storage_class(bucket=bucket, querystring_auth=querystring_auth,
querystring_expire=querystring_expire)
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def package_data(pkg, roots):

setup(
name="openedx-scorm-xblock",
version="15.0.0",
version="15.1.0",
description="Scorm XBlock for Open edX",
long_description=readme,
long_description_content_type="text/x-rst",
Expand Down

0 comments on commit 9fddeff

Please sign in to comment.