Skip to content

Commit

Permalink
Merge pull request #7 from Abstract-Tech/fix/grading
Browse files Browse the repository at this point in the history
Fix/grading
  • Loading branch information
snglth authored Feb 21, 2023
2 parents 856e321 + c14c017 commit 618892c
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 130 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ jobs:
derex reset-rabbitmq
# We can't use `derex create-bucket` because github CI doesn't allocate a TTY
docker run --rm --network derex --entrypoint /bin/sh minio/mc -c 'mc config host add local http://minio:80 minio_derex "ICDTE0ZnlbIR7r6/qE81nkF7Kshc2gXYv6fJR4I/HKPeTbxEeB3nxC85Ne6C844hEaaC2+KHBRIOzGou9leulZ7t" --api s3v4; set -ex; mc mb --ignore-existing local/scorm; mc policy set download local/scorm/profile-images'
derex build final
derex settings base
derex build project
- name: Run tests
run: |
make coverage
141 changes: 112 additions & 29 deletions abstract_scorm_xblock/abstract_scorm_xblock/scormxblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from xblock.core import XBlock
from xblock.fields import Scope, String, Float, Boolean, Dict, Integer
from xblock.fragment import Fragment
from xblock.completable import CompletableXBlockMixin

from .utils import gettext as _
from .utils import resource_string, render_template
Expand All @@ -26,7 +27,7 @@
logger = logging.getLogger(__name__)


class AbstractScormXBlock(XBlock):
class AbstractScormXBlock(XBlock, CompletableXBlockMixin):
display_name = String(
display_name=_("Display Name"),
help=_("Display name for this module"),
Expand Down Expand Up @@ -108,8 +109,30 @@ class AbstractScormXBlock(XBlock):
# save completion_status for SCORM_2004
_lesson_status = String(scope=Scope.user_state, default="not attempted")
_success_status = String(scope=Scope.user_state, default="unknown")

"""
Fields description from
https://scorm.com/scorm-explained/technical-scorm/run-time/run-time-reference/ :
* cmi.learner_id (long_identifier_type (SPM: 4000), RO) Identifies the learner on behalf of whom the SCO was launched
* cmi.location (characterstring (SPM: 1000), RW) The learner's current location in the SCO
* cmi.suspend_data (characterstring (SPM: 4000), RW) Provides space to store and retrieve data between learner sessions
* cmi.completion_status (“completed”, “incomplete”, “not attempted”, “unknown”, RW) Indicates whether the learner has completed the SCO
* cmi.completion_threshold (real(10,7) range (0..1), RO) Used to determine whether the SCO should be considered complete
* cmi.success_status (“passed”, “failed”, “unknown”, RW) Indicates whether the learner has mastered the SCO
* cmi.score.scaled (real (10,7) range (-1..1), RW) Number that reflects the performance of the learner
* cmi.score.raw (real (10,7), RW) Number that reflects the performance of the learner relative to the range bounded by the values of min and max
* cmi.score.min (real (10,7), RW) Minimum value in the range for the raw score
* cmi.score.max (real (10,7), RW) Maximum value in the range for the raw score
"""
_scorm_data = Dict(scope=Scope.user_state, default={})

@property
def lesson_score_display(self):
if self.has_score and self.lesson_score == self.weight:
return int(self.weight)
return round(self.lesson_score, 2)

def student_view(self, context={}):
# TODO: We should be able to display an error message
# instead of trying to render an inexistent or problematic
Expand All @@ -123,15 +146,16 @@ def student_view(self, context={}):

template = render_template(
"static/html/scormxblock.html",
{"completion_status": self._get_completion_status(), "scorm_xblock": self},
{"completion_status": self.get_lesson_status(), "scorm_xblock": self},
)
fragment = Fragment(template)
fragment.add_css(resource_string("static/css/scormxblock.css"))
fragment.add_javascript(resource_string("static/js/src/scormxblock.js"))
js_settings = {
"scorm_version": self._scorm_version,
"scorm_url": self._scorm_url,
"completion_status": self._get_completion_status(),
"scorm_data": self._scorm_data,
"completion_status": self.get_lesson_status(),
"scorm_xblock": {
"display_name": self.display_name,
"width": self.width,
Expand Down Expand Up @@ -217,48 +241,107 @@ def studio_submit(self, request, suffix=""):
status=200,
)

def get_current_user_attributes(self, attribute):
user = self.runtime.service(self, "user").get_current_user()
return user.opt_attrs.get(attribute)

@XBlock.json_handler
def scorm_get_value(self, data, suffix=""):
name = data.get("name")
if name in ["cmi.core.lesson_status", "cmi.completion_status"]:
return {"value": self._lesson_status}
elif name == "cmi.success_status":
return {"value": self._success_status}
elif name in ["cmi.core.score.raw", "cmi.score.raw"]:
elif name in ["cmi.core.score.raw", "cmi.score.raw", "cmi.score.scaled"]:
return {"value": self.lesson_score * 100}
elif name in ["cmi.core.student_id", "cmi.learner_id"]:
return {"value": self.get_current_user_attr("edx-platform.user_id")}
elif name in ["cmi.core.student_name", "cmi.learner_name"]:
return {"value": self.get_current_user_attr("edx-platform.username")}
else:
return {"value": self._scorm_data.get(name, "")}

def get_payload(
self,
lesson_score,
lesson_status,
success_status,
completion_status,
):
payload = {"result": "success"}
if lesson_score:
self.lesson_score = lesson_score
if success_status in ["failed", "unknown"]:
lesson_score = 0
else:
lesson_score = lesson_score * self.weight
payload.update({"lesson_score": lesson_score})

if lesson_status:
self._lesson_status = lesson_status
payload.update({"completion_status": lesson_status})

if completion_status:
self.completion_status = completion_status
payload.update({"completion_status": completion_status})

return payload

@XBlock.json_handler
def scorm_set_value(self, data, suffix=""):
payload = {"result": "success"}
name = data.get("name")
value = data.get("value")

lesson_score = None
lesson_status = None
success_status = None
completion_status = None
completion_percent = None

self._scorm_data[name] = value

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()
payload.update({"lesson_score": self.lesson_score})
lesson_status = value
if lesson_status in ["passed", "failed"]:
success_status = lesson_status
elif lesson_status in ["completed", "incomplete"]:
completion_status = lesson_status
elif name == "cmi.success_status":
self._success_status = data.get("value")
success_status = value
elif name == "cmi.completion_status":
completion_status = value
elif (
name in ["cmi.core.score.raw", "cmi.score.raw", "cmi.score.scaled"]
and self.has_score
):
lesson_score = float(value) / 100
elif name == "cmi.progress_measure":
completion_percent = float(value)

if completion_status == "completed":
self.emit_completion(1)
if completion_percent:
self.emit_completion(completion_percent)

if success_status or completion_status == "completed":
if self.has_score:
if self._success_status == "unknown":
self.lesson_score = 0
self._publish_grade()
payload.update({"lesson_score": self.lesson_score})
elif name in ["cmi.core.score.raw", "cmi.score.raw"] and self.has_score:
self.lesson_score = int(data.get("value", 0)) / 100.0
self._publish_grade()
payload.update({"lesson_score": self.lesson_score})
else:
self._scorm_data[name] = data.get("value", "")

payload.update({"completion_status": self._get_completion_status()})
return payload
return self.get_payload(
lesson_score,
lesson_status,
success_status,
completion_status,
)

def set_score(self, score):
"""
Utility method used to rescore a problem.
"""
if self.has_score:
self.lesson_score = score.raw_earned
self._publish_grade()
self.emit_completion(1)

def _publish_grade(self):
if self._lesson_status == "failed" or (
Expand All @@ -272,15 +355,15 @@ def _publish_grade(self):
self, "grade", {"value": self.lesson_score, "max_value": self.weight}
)

def _get_completion_status(self):
completion_status = self._lesson_status
def get_lesson_status(self):
lesson_status = self._lesson_status
if (
self.scorm_file
and ScormVersions(self._scorm_version) > ScormVersions["SCORM_12"]
and self._success_status != "unknown"
):
completion_status = self._success_status
return completion_status
lesson_status = self._success_status
return lesson_status

def _read_scorm_manifest(self, scorm_path):
manifest_path = os.path.join(scorm_path, "imsmanifest.xml")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<div class="scormxblock_block" id="scormxblock_{{ scorm_xblock.url_name }}">
{% if scorm_xblock.has_score %}
<p class="scormxblock_score">
(<span class="lesson_score">{{ scorm_xblock.lesson_score }}</span>/{{ scorm_xblock.weight }} {% trans "points" %})
(<span class="lesson_score">{{ scorm_xblock.lesson_score_display }}</span>/{{ scorm_xblock.weight }} {% trans "points" %})
<span class="scormxblock_completion completion_status">{% trans completion_status %}</span>
</p>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,21 @@ function ScormXBlock(runtime, element, settings) {
}

var GetValue = function (cmi_element) {
return $.ajax({
var response = $.ajax({
type: "POST",
url: runtime.handlerUrl(element, "scorm_get_value"),
data: JSON.stringify({ name: cmi_element }),
async: false,
}).responseText;
});

response = JSON.parse(response.responseText);
if (!response.value && cmi_element in settings.scorm_data) {
if ([undefined, null].includes(settings.scorm_data[cmi_element])) {
return "";
}
return settings.scorm_data[cmi_element];
}
return response.value;
};

var SetValue = function (cmi_element, value) {
Expand All @@ -74,11 +83,12 @@ function ScormXBlock(runtime, element, settings) {
async: true,
success: function (response) {
if (typeof response.lesson_score != "undefined") {
$(".lesson_score", element).html(response.lesson_score);
$(".lesson_score", element).html(response.lesson_score.toFixed(2));
}
$(".completion_status", element).html(response.completion_status);
},
});
return "true";
};

var GetAPI = function () {
Expand Down
Loading

0 comments on commit 618892c

Please sign in to comment.