EDUCATOR-165 instructor task and UI for overriding learner grades.

This commit is contained in:
Sanford Student
2017-06-07 12:56:28 -04:00
parent 4622b24ab4
commit 932776159c
21 changed files with 599 additions and 51 deletions

View File

@@ -28,7 +28,7 @@ from xblock.scorable import ScorableXBlockMixin, Score
from xmodule.capa_base_constants import RANDOMIZATION, SHOWANSWER
from xmodule.exceptions import NotFoundError
from xmodule.graders import ShowCorrectness
from .fields import Date, Timedelta
from .fields import Date, Timedelta, ScoreField
from .progress import Progress
from openedx.core.djangolib.markup import HTML, Text
@@ -104,7 +104,8 @@ class CapaFields(object):
attempts = Integer(
help=_("Number of attempts taken by the student on this problem"),
default=0,
scope=Scope.user_state)
scope=Scope.user_state
)
max_attempts = Integer(
display_name=_("Maximum Attempts"),
help=_("Defines the number of times a student can try to answer this problem. "
@@ -183,6 +184,9 @@ class CapaFields(object):
scope=Scope.user_state, default={})
input_state = Dict(help=_("Dictionary for maintaining the state of inputtypes"), scope=Scope.user_state)
student_answers = Dict(help=_("Dictionary with the current student responses"), scope=Scope.user_state)
# enforce_type is set to False here because this field is saved as a dict in the database.
score = ScoreField(help=_("Dictionary with the current student score"), scope=Scope.user_state, enforce_type=False)
has_saved_answers = Boolean(help=_("Whether or not the answers have been saved since last submit"),
scope=Scope.user_state)
done = Boolean(help=_("Whether the student has answered the problem"), scope=Scope.user_state)
@@ -292,7 +296,8 @@ class CapaMixin(ScorableXBlockMixin, CapaFields):
self.set_state_from_lcp()
self.set_score(self.score_from_lcp())
if self.score is None:
self.set_score(self.score_from_lcp())
assert self.seed is not None
@@ -380,9 +385,8 @@ class CapaMixin(ScorableXBlockMixin, CapaFields):
"""
For now, just return weighted earned / weighted possible
"""
score = self.get_score()
raw_earned = score.raw_earned
raw_possible = score.raw_possible
raw_earned = self.score.raw_earned
raw_possible = self.score.raw_possible
if raw_possible > 0:
if self.weight is not None:

View File

@@ -331,6 +331,7 @@ class CapaDescriptor(CapaFields, RawDescriptor):
rescore = module_attr('rescore')
reset_problem = module_attr('reset_problem')
save_problem = module_attr('save_problem')
set_score = module_attr('set_score')
set_state_from_lcp = module_attr('set_state_from_lcp')
should_show_submit_button = module_attr('should_show_submit_button')
should_show_reset_button = module_attr('should_show_reset_button')

View File

@@ -6,6 +6,7 @@ import time
import dateutil.parser
from pytz import UTC
from xblock.fields import JSONField
from xblock.scorable import Score
log = logging.getLogger(__name__)
@@ -252,3 +253,48 @@ class RelativeTime(JSONField):
return value
return self.from_json(value)
class ScoreField(JSONField):
"""
Field for blocks that need to store a Score. XBlocks that implement
the ScorableXBlockMixin may need to store their score separately
from their problem state, specifically for use in staff override
of problem scores.
"""
MUTABLE = False
def from_json(self, value):
if value is None:
return value
if isinstance(value, Score):
return value
if set(value) != {'raw_earned', 'raw_possible'}:
raise TypeError('Scores must contain only a raw earned and raw possible value. Got {}'.format(
set(value)
))
raw_earned = value['raw_earned']
raw_possible = value['raw_possible']
if raw_possible < 0:
raise ValueError(
'Error deserializing field of type {0}: Expected a positive number for raw_possible, got {1}.'.format(
self.display_name,
raw_possible,
)
)
if not (0 <= raw_earned <= raw_possible):
raise ValueError(
'Error deserializing field of type {0}: Expected raw_earned between 0 and {1}, got {2}.'.format(
self.display_name,
raw_possible,
raw_earned
)
)
return Score(raw_earned, raw_possible)
enforce_type = from_json

View File

@@ -241,8 +241,9 @@
totalScore
);
}
} else if (attemptsUsed === 0 || totalScore === 0) {
} else if ((attemptsUsed === 0 || totalScore === 0) && curScore === 0) {
// Render 'x point(s) possible' if student has not yet attempted question
// But if staff has overridden score to a non-zero number, show it
if (graded) {
progressTemplate = ngettext(
// Translators: %(num_points)s is the number of points possible (examples: 1, 3, 10).;