Skip to content
This repository has been archived by the owner on Nov 27, 2023. It is now read-only.

feat: [UNESCO-41] Add mobile sync plugin #43

Merged
merged 3 commits into from
Jul 9, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added scorm_app/__init__.py
Empty file.
21 changes: 21 additions & 0 deletions scorm_app/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.apps import AppConfig

from openedx.core.djangoapps.plugins.constants import ProjectType, PluginURLs


class MobileScormSyncConfig(AppConfig):
name = 'scorm_app'

plugin_app = {
PluginURLs.CONFIG: {
ProjectType.LMS: {
PluginURLs.NAMESPACE: 'scorm_app',
PluginURLs.APP_NAME: 'scorm_app',
PluginURLs.REGEX: '^mobile_xblock_sync/',
PluginURLs.RELATIVE_PATH: 'urls',
}
}
}
10 changes: 10 additions & 0 deletions scorm_app/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from django.conf.urls import url

from .views import SyncXBlockData


urlpatterns = [
url(
r'^set_values$', SyncXBlockData.as_view(), name='set_values'
),
]
58 changes: 58 additions & 0 deletions scorm_app/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import json

from django.test import RequestFactory
from django.urls import reverse
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from rest_framework import status, views
from rest_framework.response import Response
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError

from lms.djangoapps.courseware.module_render import _invoke_xblock_handler
from lms.djangoapps.mobile_api.decorators import mobile_course_access, mobile_view


@mobile_view()
class SyncXBlockData(views.APIView):

def post(self, request, format=None):
# TODO: need to take a handler from the request
user = request.user
handler = 'scorm_set_values'
response_context = {}

for course_data in request.data.get('courses_data'):
course_id = course_data.get('course_id')
response_context[course_id] = {}
try:
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
return Response({'error': '{} is not a valid course key'.format(course_id)},
status=status.HTTP_404_NOT_FOUND)

with modulestore().bulk_operations(course_key):
try:
course = modulestore().get_course(course_key)
except ItemNotFoundError:
return Response({'error': '{} does not exist in the modulestore'.format(course_id)},
status=status.HTTP_404_NOT_FOUND)

for scorm in course_data.get('xblocks_data'):
factory = RequestFactory()
data = json.dumps(scorm)

scorm_request = factory.post(reverse('mobile_xblock_sync:set_values'), data,
content_type='application/json')
scorm_request.user = user
scorm_request.session = request.session
scorm_request.user.known = True

usage_id = scorm.get('usage_id')
scorm_response = _invoke_xblock_handler(scorm_request, course_id, usage_id, handler, None,
course=course)
response_context[course_id][usage_id] = json.loads(scorm_response.content)
return Response(response_context)
33 changes: 27 additions & 6 deletions scormxblock/scormxblock.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import time
import json
import hashlib
import re
Expand Down Expand Up @@ -207,33 +208,53 @@ def scorm_get_value(self, data, suffix=''):

@XBlock.json_handler
def scorm_set_value(self, data, suffix=''):
return self.set_value(data, suffix)

def set_value(self, data, suffix='', set_last_updated_time=True):
context = {'result': 'success'}
name = data.get('name')

if name in ['cmi.core.lesson_status', 'cmi.completion_status']:
self.lesson_status = data.get('value')
if self.has_score and data.get('value') in ['completed', 'failed', 'passed']:
self.publish_grade()
self.publish_grade(set_last_updated_time)
context.update({"lesson_score": self.format_lesson_score})

elif name == 'cmi.success_status':
self.success_status = data.get('value')
if self.has_score:
if self.success_status == 'unknown':
self.lesson_score = 0
self.publish_grade()
self.publish_grade(set_last_updated_time)
context.update({"lesson_score": self.format_lesson_score})
elif name in ['cmi.core.score.raw', 'cmi.score.raw'] and self.has_score:
self.lesson_score = float(data.get('value', 0))/100.0
self.publish_grade()
self.publish_grade(set_last_updated_time)
context.update({"lesson_score": self.format_lesson_score})
else:
self.data_scorm[name] = data.get('value', '')

self.data_scorm[name] = data.get('value', '')
context.update({"completion_status": self.get_completion_status()})
return context

def publish_grade(self):
@XBlock.json_handler
def scorm_get_values(self, data, suffix=''):
return self.data_scorm

@XBlock.json_handler
def scorm_set_values(self, data, suffix=''):
is_updated = False
if self.data_scorm.get('last_updated_time', 0) < data.get('last_updated_time'):
for datum in data.get('data'):
self.set_value(datum, suffix, set_last_updated_time=False)
self.data_scorm['last_updated_time'] = int(data.get('last_updated_time'))
is_updated = True
context = self.data_scorm
context.update({"is_updated": is_updated})
return context

def publish_grade(self, set_last_updated_time=True):
if set_last_updated_time:
self.data_scorm['last_updated_time'] = int(time.time())
if self.lesson_status == 'failed' or (self.version_scorm == 'SCORM_2004'
and self.success_status in ['failed', 'unknown']):
self.runtime.publish(
Expand Down
9 changes: 8 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,21 @@ def package_data(pkg, roots):
description='scormxblock XBlock', # TODO: write a better description.
packages=[
'scormxblock',
'scorm_app'
],
include_package_data=True,
zip_safe=False,
install_requires=[
'XBlock',
],
entry_points={
'xblock.v1': [
'scormxblock = scormxblock:ScormXBlock',
]
],
'lms.djangoapp': [
'scorm_app = scorm_app.apps:MobileScormSyncConfig',
],
'cms.djangoapp': [],
},
package_data=package_data("scormxblock", ["static", "public", "translations"]),
license="Apache",
Expand Down