AA-261: Overridding Show Answer functionality for PLS courses
We noticed that since users can choose to reset their due dates, they would have the ability to let due dates pass and then for any assessment that allows viewing the answer after the due date would be visible. The user could thus view all answers and then reset their due dates to receive a perfect score. This PR works to fix that issue by changing all show answer values to not take into account being past the due date when inside a PLS course.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
"""
|
||||
FieldOverride that forces Show Answer values that use Past Due logic to
|
||||
new Show Answer values that remove the Past Due check (keeping the rest intact)
|
||||
"""
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from common.lib.xmodule.xmodule.capa_base import SHOWANSWER
|
||||
from lms.djangoapps.courseware.field_overrides import FieldOverrideProvider
|
||||
from openedx.features.course_experience import RELATIVE_DATES_FLAG
|
||||
|
||||
|
||||
class ShowAnswerFieldOverride(FieldOverrideProvider):
|
||||
"""
|
||||
A concrete implementation of
|
||||
:class:`~courseware.field_overrides.FieldOverrideProvider` which forces
|
||||
Show Answer values that use Past Due logic to new Show Answer values
|
||||
that remove the Past Due check (keeping the rest intact)
|
||||
|
||||
Once Courseware is able to use BlockTransformers, this override should be
|
||||
converted to a BlockTransformer to set the showanswer field.
|
||||
"""
|
||||
def get(self, block, name, default):
|
||||
"""
|
||||
Overwrites the 'showanswer' field on blocks in self-paced courses to
|
||||
remove any checks about due dates being in the past.
|
||||
"""
|
||||
if name != 'showanswer':
|
||||
return default
|
||||
|
||||
has_showanswer = self.fallback_field_data.has(block, 'showanswer')
|
||||
# This is to explicitly check the case where the default value of
|
||||
# SHOWANSWER.FINISHED is left on a Course. In that case, we continue
|
||||
# to follow the same mapping of FINISHED -> AFTER_ALL_ATTEMPTS_OR_CORRECT.
|
||||
# This value will then be inherited throughout the rest of the Course.
|
||||
if not has_showanswer and block.category == 'course':
|
||||
return SHOWANSWER.AFTER_ALL_ATTEMPTS_OR_CORRECT
|
||||
elif not has_showanswer:
|
||||
return default
|
||||
|
||||
mapping = {
|
||||
SHOWANSWER.ATTEMPTED: SHOWANSWER.ATTEMPTED_NO_PAST_DUE,
|
||||
SHOWANSWER.CLOSED: SHOWANSWER.AFTER_ALL_ATTEMPTS,
|
||||
SHOWANSWER.CORRECT_OR_PAST_DUE: SHOWANSWER.ANSWERED,
|
||||
SHOWANSWER.FINISHED: SHOWANSWER.AFTER_ALL_ATTEMPTS_OR_CORRECT,
|
||||
SHOWANSWER.PAST_DUE: SHOWANSWER.NEVER,
|
||||
}
|
||||
current_show_answer_value = self.fallback_field_data.get(block, 'showanswer')
|
||||
|
||||
return mapping.get(current_show_answer_value, default)
|
||||
|
||||
@classmethod
|
||||
def enabled_for(cls, course):
|
||||
""" Enabled only for Self-Paced courses using Personalized User Schedules. """
|
||||
return course.self_paced and RELATIVE_DATES_FLAG.is_enabled(course.id)
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Tests for Show Answer overrides for self-paced courses."""
|
||||
|
||||
import ddt
|
||||
|
||||
from django.test import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
|
||||
from common.lib.xmodule.xmodule.capa_base import SHOWANSWER
|
||||
from lms.djangoapps.ccx.tests.test_overrides import inject_field_overrides
|
||||
from lms.djangoapps.courseware.model_data import FieldDataCache
|
||||
from lms.djangoapps.courseware.module_render import get_module
|
||||
from openedx.features.course_experience import RELATIVE_DATES_FLAG
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
|
||||
@override_settings(
|
||||
FIELD_OVERRIDE_PROVIDERS=[
|
||||
'openedx.features.personalized_learner_schedules.show_answer.show_answer_field_override.ShowAnswerFieldOverride'
|
||||
],
|
||||
)
|
||||
@ddt.ddt
|
||||
class ShowAnswerFieldOverrideTest(ModuleStoreTestCase):
|
||||
""" Tests for Show Answer overrides for self-paced courses. """
|
||||
|
||||
def setup_course(self, **course_kwargs):
|
||||
""" Set up a course with provided course attributes. """
|
||||
course = CourseFactory.create(**course_kwargs)
|
||||
inject_field_overrides((course,), course, self.user)
|
||||
return course
|
||||
|
||||
def get_course_module(self, course):
|
||||
request = RequestFactory().request()
|
||||
field_data_cache = FieldDataCache([], course.id, self.user)
|
||||
return get_module(self.user, request, course.location, field_data_cache, course=course)
|
||||
|
||||
@ddt.data(True, False)
|
||||
def test_override_enabled_for(self, active):
|
||||
with RELATIVE_DATES_FLAG.override(active=active):
|
||||
# Instructor paced course will just have the default value
|
||||
ip_course = self.setup_course()
|
||||
course_module = self.get_course_module(ip_course)
|
||||
self.assertEqual(course_module.showanswer, SHOWANSWER.FINISHED)
|
||||
|
||||
sp_course = self.setup_course(self_paced=True)
|
||||
course_module = self.get_course_module(sp_course)
|
||||
if active:
|
||||
self.assertEqual(course_module.showanswer, SHOWANSWER.AFTER_ALL_ATTEMPTS_OR_CORRECT)
|
||||
else:
|
||||
self.assertEqual(course_module.showanswer, SHOWANSWER.FINISHED)
|
||||
|
||||
@ddt.data(
|
||||
(SHOWANSWER.ATTEMPTED, SHOWANSWER.ATTEMPTED_NO_PAST_DUE),
|
||||
(SHOWANSWER.CLOSED, SHOWANSWER.AFTER_ALL_ATTEMPTS),
|
||||
(SHOWANSWER.CORRECT_OR_PAST_DUE, SHOWANSWER.ANSWERED),
|
||||
(SHOWANSWER.FINISHED, SHOWANSWER.AFTER_ALL_ATTEMPTS_OR_CORRECT),
|
||||
(SHOWANSWER.PAST_DUE, SHOWANSWER.NEVER),
|
||||
(SHOWANSWER.NEVER, SHOWANSWER.NEVER),
|
||||
(SHOWANSWER.AFTER_SOME_NUMBER_OF_ATTEMPTS, SHOWANSWER.AFTER_SOME_NUMBER_OF_ATTEMPTS),
|
||||
(SHOWANSWER.ALWAYS, SHOWANSWER.ALWAYS),
|
||||
)
|
||||
@ddt.unpack
|
||||
@RELATIVE_DATES_FLAG.override(active=True)
|
||||
def test_get(self, initial_value, expected_final_value):
|
||||
course = self.setup_course(self_paced=True, showanswer=initial_value)
|
||||
course_module = self.get_course_module(course)
|
||||
self.assertEqual(course_module.showanswer, expected_final_value)
|
||||
Reference in New Issue
Block a user