diff --git a/lms/djangoapps/courseware/tests/test_course_tools.py b/lms/djangoapps/courseware/tests/test_course_tools.py index 65bcee45b4..8060e1e4e4 100644 --- a/lms/djangoapps/courseware/tests/test_course_tools.py +++ b/lms/djangoapps/courseware/tests/test_course_tools.py @@ -15,12 +15,12 @@ from course_modes.tests.factories import CourseModeFactory from courseware.course_tools import VerifiedUpgradeTool from courseware.models import DynamicUpgradeDeadlineConfiguration from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.schedules.config import CREATE_SCHEDULE_WAFFLE_FLAG from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from student.tests.factories import CourseEnrollmentFactory, UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory -from openedx.core.djangoapps.schedules.tests.factories import ScheduleConfigFactory @attr(shard=3) @@ -40,6 +40,7 @@ class VerifiedUpgradeToolTest(SharedModuleStoreTestCase): ) cls.course_overview = CourseOverview.get_from_id(cls.course.id) + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def setUp(self): super(VerifiedUpgradeToolTest, self).setUp() @@ -56,11 +57,6 @@ class VerifiedUpgradeToolTest(SharedModuleStoreTestCase): DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) - ScheduleConfigFactory.create( - site=mock_get_current_site.return_value, - create_schedules=True - ) - self.enrollment = CourseEnrollmentFactory( course_id=self.course.id, mode=CourseMode.AUDIT, diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index f768eec7d7..879c1628d7 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -32,12 +32,12 @@ from lms.djangoapps.commerce.models import CommerceConfiguration from lms.djangoapps.verify_student.models import VerificationDeadline from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory from openedx.core.djangoapps.content.course_overviews.models import CourseOverview +from openedx.core.djangoapps.schedules.signals import CREATE_SCHEDULE_WAFFLE_FLAG from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from openedx.core.djangoapps.user_api.preferences.api import set_user_preference from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages -from openedx.core.djangoapps.schedules.tests.factories import ScheduleConfigFactory from student.tests.factories import TEST_PASSWORD, CourseEnrollmentFactory, UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -578,8 +578,8 @@ class TestScheduleOverrides(SharedModuleStoreTestCase): self.addCleanup(patcher.stop) mock_get_current_site.return_value = SiteFactory.create() - self.site = mock_get_current_site.return_value + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def test_date_with_self_paced_with_enrollment_before_course_start(self): """ Enrolling before a course begins should result in the upgrade deadline being set relative to the course start date. """ @@ -587,7 +587,6 @@ class TestScheduleOverrides(SharedModuleStoreTestCase): course = create_self_paced_course_run(days_till_start=3) overview = CourseOverview.get_from_id(course.id) expected = overview.start + timedelta(days=global_config.deadline_days) - ScheduleConfigFactory.create(site=self.site, enabled=True, create_schedules=True) enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT) block = VerifiedUpgradeDeadlineDate(course, enrollment.user) self.assertEqual(block.date, expected) @@ -602,6 +601,7 @@ class TestScheduleOverrides(SharedModuleStoreTestCase): ) self.assertEqual(upgrade_date_summary.relative_datestring, 'by {date}') + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def test_date_with_self_paced_with_enrollment_after_course_start(self): """ Enrolling after a course begins should result in the upgrade deadline being set relative to the enrollment date. @@ -611,7 +611,6 @@ class TestScheduleOverrides(SharedModuleStoreTestCase): """ global_config = DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) course = create_self_paced_course_run(days_till_start=-1, org_id='TestOrg') - ScheduleConfigFactory.create(site=self.site, enabled=True, create_schedules=True) enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT) block = VerifiedUpgradeDeadlineDate(course, enrollment.user) expected = enrollment.created + timedelta(days=global_config.deadline_days) @@ -635,6 +634,7 @@ class TestScheduleOverrides(SharedModuleStoreTestCase): expected = enrollment.created + timedelta(days=course_config.deadline_days) self.assertEqual(block.date, expected) + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def test_date_with_self_paced_without_dynamic_upgrade_deadline(self): """ Disabling the dynamic upgrade deadline functionality should result in the verified mode's expiration date being returned. """ @@ -645,13 +645,13 @@ class TestScheduleOverrides(SharedModuleStoreTestCase): block = VerifiedUpgradeDeadlineDate(course, enrollment.user) self.assertEqual(block.date, expected) + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def test_date_with_existing_schedule(self): """ If a schedule is created while deadlines are disabled, they shouldn't magically appear once the feature is turned on. """ course = create_self_paced_course_run(days_till_start=-1) DynamicUpgradeDeadlineConfiguration.objects.create(enabled=False) course_config = CourseDynamicUpgradeDeadlineConfiguration.objects.create(enabled=False, course_id=course.id) - ScheduleConfigFactory.create(site=self.site, enabled=True, create_schedules=True) enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT) # The enrollment has a schedule, but the upgrade deadline should be None @@ -705,6 +705,7 @@ class TestScheduleOverrides(SharedModuleStoreTestCase): (True, True, True, True, True, False), ) @ddt.unpack + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def test_date_with_org_and_course_config_overrides(self, enroll_first, org_config_enabled, org_config_opt_out, course_config_enabled, course_config_opt_out, expected_dynamic_deadline): @@ -712,7 +713,6 @@ class TestScheduleOverrides(SharedModuleStoreTestCase): and opt-out states to verify that course-level overrides the org-level config. """ course = create_self_paced_course_run(days_till_start=-1, org_id='TestOrg') DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True) - ScheduleConfigFactory.create(site=self.site, enabled=True, create_schedules=True) if enroll_first: enrollment = CourseEnrollmentFactory(course_id=course.id, mode=CourseMode.AUDIT, course__self_paced=True) OrgDynamicUpgradeDeadlineConfiguration.objects.create( diff --git a/openedx/core/djangoapps/schedules/config.py b/openedx/core/djangoapps/schedules/config.py index 1b147ac7f9..0ae579e396 100644 --- a/openedx/core/djangoapps/schedules/config.py +++ b/openedx/core/djangoapps/schedules/config.py @@ -3,6 +3,12 @@ from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, CourseWaff WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name=u'schedules') +CREATE_SCHEDULE_WAFFLE_FLAG = CourseWaffleFlag( + waffle_namespace=WAFFLE_FLAG_NAMESPACE, + flag_name=u'create_schedules_for_course', + flag_undefined_default=False +) + COURSE_UPDATE_WAFFLE_FLAG = CourseWaffleFlag( waffle_namespace=WAFFLE_FLAG_NAMESPACE, flag_name=u'send_updates_for_course', diff --git a/openedx/core/djangoapps/schedules/docs/README.rst b/openedx/core/djangoapps/schedules/docs/README.rst index c7c5a5798a..5bf4c4196b 100644 --- a/openedx/core/djangoapps/schedules/docs/README.rst +++ b/openedx/core/djangoapps/schedules/docs/README.rst @@ -250,6 +250,14 @@ and link it to the Site. Make sure to enable all of the settings: - ``hold_back_ratio``: ratio of all new Course Enrollments that should NOT have a Schedule created. +Roll-out Waffle Flag +^^^^^^^^^^^^^^^^^^^^ + +There is one roll-out related course waffle flag that we plan to delete +called ``schedules.create_schedules_for_course``, which, if the +``ScheduleConfig.create_schedules`` is disabled, will enable schedule +creation on a per-course basis. + Self-paced Configuration ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/openedx/core/djangoapps/schedules/signals.py b/openedx/core/djangoapps/schedules/signals.py index 1f364f6efc..8a8e28249c 100644 --- a/openedx/core/djangoapps/schedules/signals.py +++ b/openedx/core/djangoapps/schedules/signals.py @@ -18,6 +18,7 @@ from openedx.core.djangoapps.schedules.content_highlights import course_has_high from openedx.core.djangoapps.signals.signals import COURSE_START_DATE_CHANGED from openedx.core.djangoapps.theming.helpers import get_current_site from student.models import CourseEnrollment +from .config import CREATE_SCHEDULE_WAFFLE_FLAG from .models import Schedule, ScheduleConfig from .tasks import update_course_schedules @@ -38,7 +39,10 @@ def create_schedule(sender, **kwargs): enrollment = kwargs['instance'] schedule_config = ScheduleConfig.current(current_site) - if not schedule_config.create_schedules: + if ( + not schedule_config.create_schedules + and not CREATE_SCHEDULE_WAFFLE_FLAG.is_enabled(enrollment.course_id) + ): log.debug('Schedules: Creation not enabled for this course or for this site') return diff --git a/openedx/core/djangoapps/schedules/tests/test_signals.py b/openedx/core/djangoapps/schedules/tests/test_signals.py index c0820c9107..167744e4c5 100644 --- a/openedx/core/djangoapps/schedules/tests/test_signals.py +++ b/openedx/core/djangoapps/schedules/tests/test_signals.py @@ -7,6 +7,7 @@ from course_modes.models import CourseMode from course_modes.tests.factories import CourseModeFactory from courseware.models import DynamicUpgradeDeadlineConfiguration from openedx.core.djangoapps.schedules.models import ScheduleExperience +from openedx.core.djangoapps.schedules.signals import CREATE_SCHEDULE_WAFFLE_FLAG from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.core.djangolib.testing.utils import skip_unless_lms @@ -43,28 +44,40 @@ class CreateScheduleTests(SharedModuleStoreTestCase): with self.assertRaises(Schedule.DoesNotExist): enrollment.schedule + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def test_create_schedule(self, mock_get_current_site): site = SiteFactory.create() mock_get_current_site.return_value = site ScheduleConfigFactory.create(site=site) self.assert_schedule_created() + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def test_no_current_site(self, mock_get_current_site): mock_get_current_site.return_value = None self.assert_schedule_not_created() - def test_schedule_config_enabled(self, mock_get_current_site): + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) + def test_schedule_config_disabled_waffle_enabled(self, mock_get_current_site): + site = SiteFactory.create() + mock_get_current_site.return_value = site + ScheduleConfigFactory.create(site=site, create_schedules=False) + self.assert_schedule_created() + + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, False) + def test_schedule_config_enabled_waffle_disabled(self, mock_get_current_site): site = SiteFactory.create() mock_get_current_site.return_value = site ScheduleConfigFactory.create(site=site, create_schedules=True) self.assert_schedule_created() - def test_schedule_config_disabled(self, mock_get_current_site): + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, False) + def test_schedule_config_disabled_waffle_disabled(self, mock_get_current_site): site = SiteFactory.create() mock_get_current_site.return_value = site ScheduleConfigFactory.create(site=site, create_schedules=False) self.assert_schedule_not_created() + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) def test_schedule_config_creation_enabled_instructor_paced(self, mock_get_current_site): site = SiteFactory.create() mock_get_current_site.return_value = site @@ -74,14 +87,15 @@ class CreateScheduleTests(SharedModuleStoreTestCase): with self.assertRaises(Schedule.DoesNotExist): enrollment.schedule + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) @patch('openedx.core.djangoapps.schedules.signals.course_has_highlights') def test_create_schedule_course_updates_experience(self, mock_course_has_highlights, mock_get_current_site): site = SiteFactory.create() - ScheduleConfigFactory.create(site=site, enabled=True, create_schedules=True) mock_course_has_highlights.return_value = True mock_get_current_site.return_value = site self.assert_schedule_created(experience_type=ScheduleExperience.EXPERIENCES.course_updates) + @override_waffle_flag(CREATE_SCHEDULE_WAFFLE_FLAG, True) @patch('analytics.track') @patch('random.random') @ddt.data(