Mask grades on progress page according to "Show Correctness" setting.

This commit is contained in:
Jillian Vogel
2017-05-02 23:11:18 +09:30
committed by Tim Krones
parent e8a36957b1
commit 6ca8a702ae
11 changed files with 481 additions and 47 deletions

View File

@@ -25,8 +25,9 @@ from capa.responsetypes import StudentInputError, ResponseError, LoncapaProblemE
from capa.util import convert_files_to_filenames, get_inner_html_from_xpath
from xblock.fields import Boolean, Dict, Float, Integer, Scope, String, XMLString
from xblock.scorable import ScorableXBlockMixin, Score
from xmodule.capa_base_constants import RANDOMIZATION, SHOWANSWER, SHOW_CORRECTNESS
from xmodule.capa_base_constants import RANDOMIZATION, SHOWANSWER
from xmodule.exceptions import NotFoundError
from xmodule.graders import ShowCorrectness
from .fields import Date, Timedelta
from .progress import Progress
@@ -120,11 +121,11 @@ class CapaFields(object):
help=_("Defines when to show whether a learner's answer to the problem is correct. "
"Configured on the subsection."),
scope=Scope.settings,
default=SHOW_CORRECTNESS.ALWAYS,
default=ShowCorrectness.ALWAYS,
values=[
{"display_name": _("Always"), "value": SHOW_CORRECTNESS.ALWAYS},
{"display_name": _("Never"), "value": SHOW_CORRECTNESS.NEVER},
{"display_name": _("Past Due"), "value": SHOW_CORRECTNESS.PAST_DUE},
{"display_name": _("Always"), "value": ShowCorrectness.ALWAYS},
{"display_name": _("Never"), "value": ShowCorrectness.NEVER},
{"display_name": _("Past Due"), "value": ShowCorrectness.PAST_DUE},
],
)
showanswer = String(
@@ -921,17 +922,11 @@ class CapaMixin(ScorableXBlockMixin, CapaFields):
Limits access to the correct/incorrect flags, messages, and problem score.
"""
if self.show_correctness == SHOW_CORRECTNESS.NEVER:
return False
elif self.runtime.user_is_staff:
# This is after the 'never' check because admins can see correctness
# unless the problem explicitly prevents it
return True
elif self.show_correctness == SHOW_CORRECTNESS.PAST_DUE:
return self.is_past_due()
# else: self.show_correctness == SHOW_CORRECTNESS.ALWAYS
return True
return ShowCorrectness.correctness_available(
show_correctness=self.show_correctness,
due_date=self.close_date,
has_staff_access=self.runtime.user_is_staff,
)
def update_score(self, data):
"""

View File

@@ -4,15 +4,6 @@ Constants for capa_base problems
"""
class SHOW_CORRECTNESS(object): # pylint: disable=invalid-name
"""
Constants for when to show correctness
"""
ALWAYS = "always"
PAST_DUE = "past_due"
NEVER = "never"
class SHOWANSWER(object):
"""
Constants for when to show answer

View File

@@ -10,9 +10,10 @@ import logging
import random
import sys
from collections import OrderedDict
from datetime import datetime # Used by pycontracts. pylint: disable=unused-import
from datetime import datetime
from contracts import contract
from pytz import UTC
log = logging.getLogger("edx.courseware")
@@ -462,3 +463,38 @@ def _min_or_none(itr):
return min(itr)
except ValueError:
return None
class ShowCorrectness(object):
"""
Helper class for determining whether correctness is currently hidden for a block.
When correctness is hidden, this limits the user's access to the correct/incorrect flags, messages, problem scores,
and aggregate subsection and course grades.
"""
"""
Constants used to indicate when to show correctness
"""
ALWAYS = "always"
PAST_DUE = "past_due"
NEVER = "never"
@classmethod
def correctness_available(cls, show_correctness='', due_date=None, has_staff_access=False):
"""
Returns whether correctness is available now, for the given attributes.
"""
if show_correctness == cls.NEVER:
return False
elif has_staff_access:
# This is after the 'never' check because course staff can see correctness
# unless the sequence/problem explicitly prevents it
return True
elif show_correctness == cls.PAST_DUE:
# Is it now past the due date?
return (due_date is None or
due_date < datetime.now(UTC))
# else: show_correctness == cls.ALWAYS
return True

View File

@@ -2,13 +2,15 @@
Grading tests
"""
from datetime import datetime
import unittest
from datetime import datetime, timedelta
import ddt
import unittest
from pytz import UTC
from xmodule import graders
from xmodule.graders import ProblemScore, AggregatedScore, aggregate_scores
from xmodule.graders import (
AggregatedScore, ProblemScore, ShowCorrectness, aggregate_scores
)
class GradesheetTest(unittest.TestCase):
@@ -315,3 +317,86 @@ class GraderTest(unittest.TestCase):
with self.assertRaises(ValueError) as error:
graders.grader_from_conf([invalid_conf])
self.assertIn(expected_error_message, error.exception.message)
@ddt.ddt
class ShowCorrectnessTest(unittest.TestCase):
"""
Tests the correctness_available method
"""
def setUp(self):
super(ShowCorrectnessTest, self).setUp()
now = datetime.now(UTC)
day_delta = timedelta(days=1)
self.yesterday = now - day_delta
self.today = now
self.tomorrow = now + day_delta
def test_show_correctness_default(self):
"""
Test that correctness is visible by default.
"""
self.assertTrue(ShowCorrectness.correctness_available())
@ddt.data(
(ShowCorrectness.ALWAYS, True),
(ShowCorrectness.ALWAYS, False),
# Any non-constant values behave like "always"
('', True),
('', False),
('other-value', True),
('other-value', False),
)
@ddt.unpack
def test_show_correctness_always(self, show_correctness, has_staff_access):
"""
Test that correctness is visible when show_correctness is turned on.
"""
self.assertTrue(ShowCorrectness.correctness_available(
show_correctness=show_correctness,
has_staff_access=has_staff_access
))
@ddt.data(True, False)
def test_show_correctness_never(self, has_staff_access):
"""
Test that show_correctness="never" hides correctness from learners and course staff.
"""
self.assertFalse(ShowCorrectness.correctness_available(
show_correctness=ShowCorrectness.NEVER,
has_staff_access=has_staff_access
))
@ddt.data(
# Correctness not visible to learners if due date in the future
('tomorrow', False, False),
# Correctness is visible to learners if due date in the past
('yesterday', False, True),
# Correctness is visible to learners if due date in the past (just)
('today', False, True),
# Correctness is visible to learners if there is no due date
(None, False, True),
# Correctness is visible to staff if due date in the future
('tomorrow', True, True),
# Correctness is visible to staff if due date in the past
('yesterday', True, True),
# Correctness is visible to staff if there is no due date
(None, True, True),
)
@ddt.unpack
def test_show_correctness_past_due(self, due_date_str, has_staff_access, expected_result):
"""
Test show_correctness="past_due" to ensure:
* correctness is always visible to course staff
* correctness is always visible to everyone if there is no due date
* correctness is visible to learners after the due date, when there is a due date.
"""
if due_date_str is None:
due_date = None
else:
due_date = getattr(self, due_date_str)
self.assertEquals(
ShowCorrectness.correctness_available(ShowCorrectness.PAST_DUE, due_date, has_staff_access),
expected_result
)