Commit bb2f8da4 by David Ormsbee

Add LmsCompatibilityMixin to OpenAssessmentBlock.

edx-platform expects certain things during the grade calculation
process. This adds a few fields and tests for that purpose.

[TIM-232]
parent 74af9d4a
from xblock.core import XBlock
from xblock.fields import Float, Scope
class LmsCompatibilityMixin(object):
"""Extra methods we tack on because the LMS expects them for grading."""
weight = Float(
display_name="Problem Weight",
help=("Defines the number of points each problem is worth. "
"If the value is not set, the problem is worth the sum of the "
"option point values."),
values={"min": 0, "step": .1},
scope=Scope.settings
)
def has_dynamic_children(self):
"""Do we dynamically determine our children? No, we don't have any.
The LMS wants to know this to see if it has to instantiate our module
and query it to find the children, or whether it can just trust what's
in the static (cheaper) children listing.
"""
return False
@property
def has_score(self):
"""Are we a scored type (read: a problem). Yes.
For LMS Progress page/grades download purposes, we're always going to
have a score, even if it's just 0 at the start.
"""
return True
def max_score(self):
"""The maximum raw score of our problem.
Called whenever the LMS knows that something is scorable, but finds no
recorded raw score for it (i.e. the student hasn't done it). In that
case, the LMS knows that the earned score is 0, but it doesn't know what
to put in the denominator. So we supply it with the total number of
points that it is possible for us to earn -- the sum of the highest
pointed options from each criterion.
Note that if we have already recorded a score in submissions, this
method will never be called. So it's perfectly possible for us to have
10/10 on the progress page and a 12 returning from this method if our
10/10 score was earned in the past and the problem has changed since
then.
"""
return sum(
max(option["points"] for option in criterion["options"])
for criterion in self.rubric_criteria
)
......@@ -16,6 +16,7 @@ from xblock.fragment import Fragment
from openassessment.xblock.grade_mixin import GradeMixin
from openassessment.xblock.peer_assessment_mixin import PeerAssessmentMixin
from openassessment.xblock.lms_mixin import LmsCompatibilityMixin
from openassessment.xblock.self_assessment_mixin import SelfAssessmentMixin
from openassessment.xblock.submission_mixin import SubmissionMixin
from openassessment.xblock.studio_mixin import StudioMixin
......@@ -165,7 +166,8 @@ class OpenAssessmentBlock(
SelfAssessmentMixin,
StudioMixin,
GradeMixin,
WorkflowMixin):
WorkflowMixin,
LmsCompatibilityMixin):
"""Displays a question and gives an area where students can compose a response."""
start = String(
......@@ -503,4 +505,4 @@ class OpenAssessmentBlock(
"""
for assessment in self.rubric_assessments:
if assessment["name"] == mixin_name:
return assessment
\ No newline at end of file
return assessment
"""
Tests for the LMS compatibility mixin for the OpenAssessment block.
"""
from ddt import ddt
from .base import scenario, XBlockHandlerTestCase
@ddt
class LmsMixinTest(XBlockHandlerTestCase):
"""Test the simple LMS-specific attributes used during grading."""
@scenario('data/basic_scenario.xml')
def test_simple_methods(self, xblock):
self.assertTrue(xblock.has_score)
self.assertFalse(xblock.has_dynamic_children())
self.assertTrue(hasattr(xblock, 'weight'))
@scenario('data/basic_scenario.xml')
def test_max_score(self, xblock):
self.assertEqual(xblock.max_score(), 20)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment