Revert "feat: Reimagine certificate_availability_date and certificates_display_behavior"
This commit is contained in:
@@ -14,8 +14,6 @@ from math import exp
|
||||
import dateutil.parser
|
||||
from pytz import utc
|
||||
|
||||
from xmodule.data import CertificatesDisplayBehaviors
|
||||
|
||||
DEFAULT_START_DATE = datetime(2030, 1, 1, tzinfo=utc)
|
||||
|
||||
"""
|
||||
@@ -158,18 +156,15 @@ def may_certify_for_course(
|
||||
self_paced (bool): Whether the course is self-paced.
|
||||
"""
|
||||
show_early = (
|
||||
certificates_display_behavior == CertificatesDisplayBehaviors.EARLY_NO_INFO
|
||||
certificates_display_behavior in ('early_with_info', 'early_no_info')
|
||||
or certificates_show_before_end
|
||||
)
|
||||
past_available_date = (
|
||||
certificates_display_behavior == CertificatesDisplayBehaviors.END_WITH_DATE
|
||||
and certificate_available_date
|
||||
certificate_available_date
|
||||
and certificate_available_date < datetime.now(utc)
|
||||
)
|
||||
ended_without_available_date = (
|
||||
certificates_display_behavior == CertificatesDisplayBehaviors.END
|
||||
and has_ended
|
||||
)
|
||||
ended_without_available_date = (certificate_available_date is None) and has_ended
|
||||
|
||||
return any((self_paced, show_early, past_available_date, ended_without_available_date))
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ Django module container for classes and operations related to the "Course Module
|
||||
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from io import BytesIO
|
||||
|
||||
import dateutil.parser
|
||||
@@ -22,7 +22,6 @@ from openedx.core.lib.license import LicenseMixin
|
||||
from openedx.core.lib.teams_config import TeamsConfig # lint-amnesty, pylint: disable=unused-import
|
||||
from xmodule import course_metadata_utils
|
||||
from xmodule.course_metadata_utils import DEFAULT_GRADING_POLICY, DEFAULT_START_DATE
|
||||
from xmodule.data import CertificatesDisplayBehaviors
|
||||
from xmodule.graders import grader_from_conf
|
||||
from xmodule.seq_module import SequenceBlock
|
||||
from xmodule.tabs import CourseTabList, InvalidTabsException
|
||||
@@ -556,15 +555,15 @@ class CourseFields: # lint-amnesty, pylint: disable=missing-class-docstring
|
||||
certificates_display_behavior = String(
|
||||
display_name=_("Certificates Display Behavior"),
|
||||
help=_(
|
||||
"Enter end, end_with_date, or early_no_info. After certificate generation, students who passed see a "
|
||||
"Enter end, early_with_info, or early_no_info. After certificate generation, students who passed see a "
|
||||
"link to their certificates on the dashboard and students who did not pass see information about the "
|
||||
"grading configuration. The default is end, which displays this certificate information to all students "
|
||||
"after the course end date. To display the certificate information to all students at a date after the "
|
||||
"course end date, use end_with_date and add a certificate_available_date. To display only the links to "
|
||||
"passing students as soon as certificates are generated, enter early_no_info."
|
||||
"after the course end date. To display this certificate information to all students as soon as "
|
||||
"certificates are generated, enter early_with_info. To display only the links to passing students as "
|
||||
"soon as certificates are generated, enter early_no_info."
|
||||
),
|
||||
scope=Scope.settings,
|
||||
default=CertificatesDisplayBehaviors.END,
|
||||
default="end"
|
||||
)
|
||||
course_image = String(
|
||||
display_name=_("Course About Page Image"),
|
||||
@@ -1062,6 +1061,8 @@ class CourseBlock(
|
||||
except InvalidTabsException as err:
|
||||
raise type(err)(f'{str(err)} For course: {str(self.id)}') # lint-amnesty, pylint: disable=line-too-long
|
||||
|
||||
self.set_default_certificate_available_date()
|
||||
|
||||
def set_grading_policy(self, course_policy):
|
||||
"""
|
||||
The JSON object can have the keys GRADER and GRADE_CUTOFFS. If either is
|
||||
@@ -1087,6 +1088,10 @@ class CourseBlock(
|
||||
self.raw_grader = grading_policy['GRADER'] # used for cms access
|
||||
self.grade_cutoffs = grading_policy['GRADE_CUTOFFS']
|
||||
|
||||
def set_default_certificate_available_date(self):
|
||||
if (not self.certificate_available_date) and self.end:
|
||||
self.certificate_available_date = self.end + timedelta(days=2)
|
||||
|
||||
@classmethod
|
||||
def read_grading_policy(cls, paths, system):
|
||||
"""Load a grading policy from the specified paths, in order, if it exists."""
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
"""
|
||||
Public data structures for this app.
|
||||
|
||||
See OEP-49 for details
|
||||
"""
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class CertificatesDisplayBehaviors(str, Enum):
|
||||
"""
|
||||
Options for the certificates_display_behavior field of a course
|
||||
|
||||
end: Certificates are available at the end of the course
|
||||
end_with_date: Certificates are available after the certificate_available_date (post course end)
|
||||
early_no_info: Certificates are available immediately after earning them.
|
||||
|
||||
Only in affect for instructor based courses.
|
||||
"""
|
||||
END = "end"
|
||||
END_WITH_DATE = "end_with_date"
|
||||
EARLY_NO_INFO = "early_no_info"
|
||||
|
||||
@classmethod
|
||||
def includes_value(cls, value):
|
||||
return value in set(item.value for item in cls)
|
||||
@@ -23,7 +23,6 @@ from xmodule.course_metadata_utils import (
|
||||
may_certify_for_course,
|
||||
number_for_course_location
|
||||
)
|
||||
from xmodule.data import CertificatesDisplayBehaviors
|
||||
from xmodule.modulestore.tests.utils import (
|
||||
MixedModulestoreBuilder,
|
||||
MongoModulestoreBuilder,
|
||||
@@ -164,28 +163,16 @@ class CourseMetadataUtilsTestCase(TestCase):
|
||||
TestScenario((DEFAULT_START_DATE, None), True),
|
||||
]),
|
||||
FunctionTest(may_certify_for_course, [
|
||||
# Test certificates_show_before_end
|
||||
TestScenario((CertificatesDisplayBehaviors.EARLY_NO_INFO, True, False, test_datetime, False), True),
|
||||
TestScenario((CertificatesDisplayBehaviors.END, True, False, test_datetime, False), True),
|
||||
TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, True, False, _NEXT_WEEK, False), True),
|
||||
|
||||
# Test that EARLY_NO_INFO
|
||||
TestScenario((CertificatesDisplayBehaviors.EARLY_NO_INFO, True, True, test_datetime, False), True),
|
||||
TestScenario((CertificatesDisplayBehaviors.EARLY_NO_INFO, False, False, test_datetime, False), True),
|
||||
|
||||
# Test END_WITH_DATE
|
||||
TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, False, False, test_datetime, False), True),
|
||||
TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, False, False, _LAST_WEEK, False), True),
|
||||
TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, False, False, _NEXT_WEEK, False), False),
|
||||
TestScenario((CertificatesDisplayBehaviors.END_WITH_DATE, False, False, None, False), False),
|
||||
|
||||
# Test END
|
||||
TestScenario((CertificatesDisplayBehaviors.END, False, False, test_datetime, False), False),
|
||||
TestScenario((CertificatesDisplayBehaviors.END, False, True, test_datetime, False), True),
|
||||
|
||||
# Test self_paced
|
||||
TestScenario((CertificatesDisplayBehaviors.END, False, False, test_datetime, False), False),
|
||||
TestScenario((CertificatesDisplayBehaviors.END, False, False, test_datetime, True), True),
|
||||
TestScenario(('early_with_info', True, True, test_datetime, False), True),
|
||||
TestScenario(('early_no_info', False, False, test_datetime, False), True),
|
||||
TestScenario(('end', True, False, test_datetime, False), True),
|
||||
TestScenario(('end', False, True, test_datetime, False), True),
|
||||
TestScenario(('end', False, False, _NEXT_WEEK, False), False),
|
||||
TestScenario(('end', False, False, _LAST_WEEK, False), True),
|
||||
TestScenario(('end', False, False, None, False), False),
|
||||
TestScenario(('early_with_info', False, False, None, False), True),
|
||||
TestScenario(('end', False, False, _NEXT_WEEK, False), False),
|
||||
TestScenario(('end', False, False, _NEXT_WEEK, True), True),
|
||||
]),
|
||||
]
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ from xblock.runtime import DictKeyValueStore, KvsFieldData
|
||||
|
||||
from openedx.core.lib.teams_config import TeamsConfig, DEFAULT_COURSE_RUN_MAX_TEAM_SIZE
|
||||
import xmodule.course_module
|
||||
from xmodule.data import CertificatesDisplayBehaviors
|
||||
from xmodule.modulestore.xml import ImportSystem, XMLModuleStore
|
||||
from xmodule.modulestore.exceptions import InvalidProctoringProvider
|
||||
|
||||
@@ -106,41 +105,36 @@ class HasEndedMayCertifyTestCase(unittest.TestCase):
|
||||
super().setUp()
|
||||
|
||||
system = DummySystem(load_error_modules=True) # lint-amnesty, pylint: disable=unused-variable
|
||||
|
||||
#sample_xml = """
|
||||
# <course org="{org}" course="{course}" display_organization="{org}_display" display_coursenumber="{course}_display" # lint-amnesty, pylint: disable=line-too-long
|
||||
# graceperiod="1 day" url_name="test"
|
||||
# start="2012-01-01T12:00"
|
||||
# {end}
|
||||
# certificates_show_before_end={cert}>
|
||||
# <chapter url="hi" url_name="ch" display_name="CH">
|
||||
# <html url_name="h" display_name="H">Two houses, ...</html>
|
||||
# </chapter>
|
||||
# </course>
|
||||
#""".format(org=ORG, course=COURSE)
|
||||
past_end = (datetime.now() - timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
|
||||
future_end = (datetime.now() + timedelta(days=12)).strftime("%Y-%m-%dT%H:%M:00")
|
||||
self.past_show_certs = get_dummy_course(
|
||||
"2012-01-01T12:00",
|
||||
end=past_end,
|
||||
certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
|
||||
)
|
||||
self.past_show_certs_no_info = get_dummy_course(
|
||||
"2012-01-01T12:00",
|
||||
end=past_end,
|
||||
certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
|
||||
)
|
||||
self.past_noshow_certs = get_dummy_course(
|
||||
"2012-01-01T12:00",
|
||||
end=past_end,
|
||||
certs=CertificatesDisplayBehaviors.END
|
||||
)
|
||||
|
||||
self.future_show_certs_no_info = get_dummy_course(
|
||||
"2012-01-01T12:00",
|
||||
end=future_end,
|
||||
certs=CertificatesDisplayBehaviors.EARLY_NO_INFO
|
||||
)
|
||||
self.future_noshow_certs = get_dummy_course(
|
||||
"2012-01-01T12:00",
|
||||
end=future_end,
|
||||
certs=CertificatesDisplayBehaviors.END
|
||||
)
|
||||
self.past_show_certs = get_dummy_course("2012-01-01T12:00", end=past_end, certs='early_with_info')
|
||||
self.past_show_certs_no_info = get_dummy_course("2012-01-01T12:00", end=past_end, certs='early_no_info')
|
||||
self.past_noshow_certs = get_dummy_course("2012-01-01T12:00", end=past_end, certs='end')
|
||||
self.future_show_certs = get_dummy_course("2012-01-01T12:00", end=future_end, certs='early_with_info')
|
||||
self.future_show_certs_no_info = get_dummy_course("2012-01-01T12:00", end=future_end, certs='early_no_info')
|
||||
self.future_noshow_certs = get_dummy_course("2012-01-01T12:00", end=future_end, certs='end')
|
||||
#self.past_show_certs = system.process_xml(sample_xml.format(end=past_end, cert=True))
|
||||
#self.past_noshow_certs = system.process_xml(sample_xml.format(end=past_end, cert=False))
|
||||
#self.future_show_certs = system.process_xml(sample_xml.format(end=future_end, cert=True))
|
||||
#self.future_noshow_certs = system.process_xml(sample_xml.format(end=future_end, cert=False))
|
||||
|
||||
def test_has_ended(self):
|
||||
"""Check that has_ended correctly tells us when a course is over."""
|
||||
assert self.past_show_certs.has_ended()
|
||||
assert self.past_show_certs_no_info.has_ended()
|
||||
assert self.past_noshow_certs.has_ended()
|
||||
assert not self.future_show_certs.has_ended()
|
||||
assert not self.future_show_certs_no_info.has_ended()
|
||||
assert not self.future_noshow_certs.has_ended()
|
||||
|
||||
@@ -149,6 +143,7 @@ class HasEndedMayCertifyTestCase(unittest.TestCase):
|
||||
assert self.past_show_certs.may_certify()
|
||||
assert self.past_noshow_certs.may_certify()
|
||||
assert self.past_show_certs_no_info.may_certify()
|
||||
assert self.future_show_certs.may_certify()
|
||||
assert self.future_show_certs_no_info.may_certify()
|
||||
assert not self.future_noshow_certs.may_certify()
|
||||
|
||||
@@ -416,6 +411,14 @@ class CourseBlockTestCase(unittest.TestCase):
|
||||
"""
|
||||
assert self.course.number == COURSE
|
||||
|
||||
def test_set_default_certificate_available_date(self):
|
||||
"""
|
||||
The certificate_available_date field should default to two days
|
||||
after the course end date.
|
||||
"""
|
||||
expected_certificate_available_date = self.course.end + timedelta(days=2)
|
||||
assert expected_certificate_available_date == self.course.certificate_available_date
|
||||
|
||||
|
||||
class ProctoringProviderTestCase(unittest.TestCase):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user