Skip to content

Commit 5dd5c73

Browse files
authored
chore: move ShowCorrectness from xmodule.graders into XBlock (#883)
1 parent c3f93b2 commit 5dd5c73

File tree

4 files changed

+142
-2
lines changed

4 files changed

+142
-2
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ Change history for XBlock
55
Unreleased
66
----------
77

8+
5.3.0 - 2025-12-19
9+
------------------
10+
11+
* Add exceptions NotFoundError and ProcessingError
12+
* Adds fields from xmodule into XBlock
13+
* Adds Progress from xmodule into XBlock
14+
* Adds ShowCorrectness from xmodule.graders into XBlock
15+
816
5.2.0 - 2025-04-08
917
------------------
1018

xblock/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
XBlock Courseware Components
33
"""
44

5-
__version__ = '5.2.0'
5+
__version__ = '5.3.0'

xblock/scorable.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
"""
22
Scorable.
33
"""
4-
from collections import namedtuple
4+
55
import logging
6+
from collections import namedtuple
7+
from datetime import datetime
68

9+
from pytz import UTC
710

811
log = logging.getLogger(__name__)
912

@@ -112,3 +115,38 @@ def _publish_grade(self, score, only_if_higher=None):
112115
'only_if_higher': only_if_higher,
113116
}
114117
self.runtime.publish(self, 'grade', grade_dict)
118+
119+
120+
class ShowCorrectness:
121+
"""
122+
Helper class for determining whether correctness is currently hidden for a block.
123+
124+
When correctness is hidden, this limits the user's access to the correct/incorrect flags, messages, problem scores,
125+
and aggregate subsection and course grades.
126+
"""
127+
128+
# Constants used to indicate when to show correctness
129+
ALWAYS = "always"
130+
PAST_DUE = "past_due"
131+
NEVER = "never"
132+
NEVER_BUT_INCLUDE_GRADE = "never_but_include_grade"
133+
134+
@classmethod
135+
def correctness_available(cls, show_correctness="", due_date=None, has_staff_access=False):
136+
"""
137+
Returns whether correctness is available now, for the given attributes.
138+
"""
139+
if show_correctness in (cls.NEVER, cls.NEVER_BUT_INCLUDE_GRADE):
140+
return False
141+
142+
if has_staff_access:
143+
# This is after the 'never' check because course staff can see correctness
144+
# unless the sequence/problem explicitly prevents it
145+
return True
146+
147+
if show_correctness == cls.PAST_DUE:
148+
# Is it now past the due date?
149+
return due_date is None or due_date < datetime.now(UTC)
150+
151+
# else: show_correctness == cls.ALWAYS
152+
return True

xblock/test/test_scorable.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
"""
44

55
# pylint: disable=protected-access
6+
from datetime import datetime, timedelta
67
from unittest import TestCase
78
from unittest.mock import Mock
89

910
import ddt
11+
from pytz import UTC
1012

1113
from xblock import scorable
1214

@@ -84,3 +86,95 @@ def test_scoring_error(self):
8486
block._scoring_error = True
8587
with self.assertRaises(RuntimeError):
8688
block.rescore(only_if_higher=False)
89+
90+
91+
@ddt.ddt
92+
class ShowCorrectnessTest(TestCase):
93+
"""
94+
Tests the correctness_available method
95+
"""
96+
97+
def setUp(self):
98+
super().setUp()
99+
100+
now = datetime.now(UTC)
101+
day_delta = timedelta(days=1)
102+
self.yesterday = now - day_delta
103+
self.today = now
104+
self.tomorrow = now + day_delta
105+
106+
def test_show_correctness_default(self):
107+
"""
108+
Test that correctness is visible by default.
109+
"""
110+
assert scorable.ShowCorrectness.correctness_available()
111+
112+
@ddt.data(
113+
(scorable.ShowCorrectness.ALWAYS, True),
114+
(scorable.ShowCorrectness.ALWAYS, False),
115+
# Any non-constant values behave like "always"
116+
("", True),
117+
("", False),
118+
("other-value", True),
119+
("other-value", False),
120+
)
121+
@ddt.unpack
122+
def test_show_correctness_always(self, show_correctness, has_staff_access):
123+
"""
124+
Test that correctness is visible when show_correctness is turned on.
125+
"""
126+
assert scorable.ShowCorrectness.correctness_available(
127+
show_correctness=show_correctness, has_staff_access=has_staff_access
128+
)
129+
130+
@ddt.data(True, False)
131+
def test_show_correctness_never(self, has_staff_access):
132+
"""
133+
Test that show_correctness="never" hides correctness from learners and course staff.
134+
"""
135+
assert not scorable.ShowCorrectness.correctness_available(
136+
show_correctness=scorable.ShowCorrectness.NEVER, has_staff_access=has_staff_access
137+
)
138+
139+
@ddt.data(
140+
# Correctness not visible to learners if due date in the future
141+
("tomorrow", False, False),
142+
# Correctness is visible to learners if due date in the past
143+
("yesterday", False, True),
144+
# Correctness is visible to learners if due date in the past (just)
145+
("today", False, True),
146+
# Correctness is visible to learners if there is no due date
147+
(None, False, True),
148+
# Correctness is visible to staff if due date in the future
149+
("tomorrow", True, True),
150+
# Correctness is visible to staff if due date in the past
151+
("yesterday", True, True),
152+
# Correctness is visible to staff if there is no due date
153+
(None, True, True),
154+
)
155+
@ddt.unpack
156+
def test_show_correctness_past_due(self, due_date_str, has_staff_access, expected_result):
157+
"""
158+
Test show_correctness="past_due" to ensure:
159+
* correctness is always visible to course staff
160+
* correctness is always visible to everyone if there is no due date
161+
* correctness is visible to learners after the due date, when there is a due date.
162+
"""
163+
if due_date_str is None:
164+
due_date = None
165+
else:
166+
due_date = getattr(self, due_date_str)
167+
assert (
168+
scorable.ShowCorrectness.correctness_available(
169+
scorable.ShowCorrectness.PAST_DUE, due_date, has_staff_access
170+
) == expected_result
171+
)
172+
173+
@ddt.data(True, False)
174+
def test_show_correctness_never_but_include_grade(self, has_staff_access):
175+
"""
176+
Test that show_correctness="never_but_include_grade" hides correctness from learners and course staff.
177+
"""
178+
assert not scorable.ShowCorrectness.correctness_available(
179+
show_correctness=scorable.ShowCorrectness.NEVER_BUT_INCLUDE_GRADE, has_staff_access=has_staff_access
180+
)

0 commit comments

Comments
 (0)