From 3121638ad4356e73482698f8b42c97c78cb22f3b Mon Sep 17 00:00:00 2001 From: AfaqShuaib09 Date: Fri, 8 Sep 2023 16:02:37 +0500 Subject: [PATCH 1/2] feat: autogeneration of bootcamp url slug --- .../apps/course_metadata/constants.py | 1 - .../apps/course_metadata/models.py | 8 ++- .../apps/course_metadata/tests/test_models.py | 56 ++++++++++++++++++- .../apps/course_metadata/toggles.py | 11 ++++ 4 files changed, 71 insertions(+), 5 deletions(-) diff --git a/course_discovery/apps/course_metadata/constants.py b/course_discovery/apps/course_metadata/constants.py index 4e9db7d16c..9694722092 100644 --- a/course_discovery/apps/course_metadata/constants.py +++ b/course_discovery/apps/course_metadata/constants.py @@ -12,7 +12,6 @@ r'boot-camps\/[a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+$' ) SLUG_FORMAT_REGEX = r'[a-zA-Z0-9-_]+$' - DEFAULT_SLUG_FORMAT_ERROR_MSG = 'Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.' MASTERS_PROGRAM_TYPE_SLUG = 'masters' diff --git a/course_discovery/apps/course_metadata/models.py b/course_discovery/apps/course_metadata/models.py index 873f9778bf..a7647f744b 100644 --- a/course_discovery/apps/course_metadata/models.py +++ b/course_discovery/apps/course_metadata/models.py @@ -55,7 +55,8 @@ ) from course_discovery.apps.course_metadata.query import CourseQuerySet, CourseRunQuerySet, ProgramQuerySet from course_discovery.apps.course_metadata.toggles import ( - IS_SUBDIRECTORY_SLUG_FORMAT_ENABLED, IS_SUBDIRECTORY_SLUG_FORMAT_FOR_EXEC_ED_ENABLED + IS_SUBDIRECTORY_SLUG_FORMAT_ENABLED, IS_SUBDIRECTORY_SLUG_FORMAT_FOR_BOOTCAMP_ENABLED, + IS_SUBDIRECTORY_SLUG_FORMAT_FOR_EXEC_ED_ENABLED ) from course_discovery.apps.course_metadata.utils import ( UploadToFieldNamePath, clean_query, custom_render_variations, get_slug_for_course, is_ocm_course, @@ -1862,10 +1863,13 @@ def set_subdirectory_slug(self): """ is_slug_in_subdirectory_format = bool(re.match(SUBDIRECTORY_SLUG_FORMAT_REGEX, self.active_url_slug)) is_exec_ed_course = self.type.slug == CourseType.EXECUTIVE_EDUCATION_2U + is_bootcamp_course = self.type.slug == CourseType.BOOTCAMP_2U if is_exec_ed_course and not IS_SUBDIRECTORY_SLUG_FORMAT_FOR_EXEC_ED_ENABLED.is_enabled(): return + if is_bootcamp_course and not IS_SUBDIRECTORY_SLUG_FORMAT_FOR_BOOTCAMP_ENABLED.is_enabled(): + return is_open_course = is_ocm_course(self) - if not is_slug_in_subdirectory_format and (is_exec_ed_course or is_open_course): + if not is_slug_in_subdirectory_format and (is_exec_ed_course or is_open_course or is_bootcamp_course): slug, error = get_slug_for_course(self) if slug: self.set_active_url_slug(slug) diff --git a/course_discovery/apps/course_metadata/tests/test_models.py b/course_discovery/apps/course_metadata/tests/test_models.py index 2cf1b67ad7..4f9697a708 100644 --- a/course_discovery/apps/course_metadata/tests/test_models.py +++ b/course_discovery/apps/course_metadata/tests/test_models.py @@ -18,6 +18,7 @@ from django.core.management import call_command from django.db import IntegrityError, transaction from django.test import TestCase, override_settings +from edx_toggles.toggles.testutils import override_waffle_switch from freezegun import freeze_time from slugify import slugify from taggit.models import Tag @@ -44,10 +45,13 @@ ) from course_discovery.apps.course_metadata.tests import factories from course_discovery.apps.course_metadata.tests.factories import ( - AdditionalMetadataFactory, CourseFactory, CourseRunFactory, CourseUrlSlugFactory, ImageFactory, PartnerFactory, - ProgramFactory, SeatFactory, SeatTypeFactory, SourceFactory + AdditionalMetadataFactory, CourseFactory, CourseRunFactory, CourseTypeFactory, CourseUrlSlugFactory, ImageFactory, + OrganizationFactory, PartnerFactory, ProgramFactory, SeatFactory, SeatTypeFactory, SourceFactory, SubjectFactory ) from course_discovery.apps.course_metadata.tests.mixins import MarketingSitePublisherTestMixin +from course_discovery.apps.course_metadata.toggles import ( + IS_SUBDIRECTORY_SLUG_FORMAT_ENABLED, IS_SUBDIRECTORY_SLUG_FORMAT_FOR_BOOTCAMP_ENABLED +) from course_discovery.apps.course_metadata.utils import ensure_draft_world from course_discovery.apps.course_metadata.utils import logger as utils_logger from course_discovery.apps.ietf_language_tags.models import LanguageTag @@ -105,6 +109,54 @@ def test_watchers(self): course.refresh_from_db() assert course.watchers == ['test@test.com'] + @ddt.data( + (True, True), + (True, False), + (False, False), + ) + @ddt.unpack + def test_automate_url_restructuring_for_bootcamps_with_feature_flag_state( + self, is_subdirectory_slug_format_enabled, is_subdirectory_slug_format_for_bootcamp_enabled + ): + """ + Tests automate url slug restructuring for bootcamps must work under its relevant feature flag + """ + bootcamp_type = CourseTypeFactory(slug=CourseType.BOOTCAMP_2U) + bootcamp_course_draft = CourseFactory(draft=True, type=bootcamp_type) + draft_course_run = CourseRunFactory(draft=True, course=bootcamp_course_draft) + subject = SubjectFactory(name='Subject1') + org = OrganizationFactory(name='organization1') + bootcamp_course_draft.subjects.add(subject) + bootcamp_course_draft.authoring_organizations.add(org) + bootcamp_course_draft.save() + + draft_course_run.status = CourseRunStatus.Unpublished + draft_course_run.save() + active_url_slug = draft_course_run.course.active_url_slug + + with override_waffle_switch(IS_SUBDIRECTORY_SLUG_FORMAT_ENABLED, active=is_subdirectory_slug_format_enabled): + with override_waffle_switch(IS_SUBDIRECTORY_SLUG_FORMAT_FOR_BOOTCAMP_ENABLED, + active=is_subdirectory_slug_format_for_bootcamp_enabled): + draft_course_run.status = CourseRunStatus.LegalReview + draft_course_run.save() + course = draft_course_run.course + official_version = draft_course_run.update_or_create_official_version() + course.refresh_from_db() + + if is_subdirectory_slug_format_for_bootcamp_enabled: + assert course.active_url_slug.startswith('boot-camps/') + assert course.active_url_slug == f'boot-camps/{subject.slug}/{org.name}-{slugify(course.title)}' + assert official_version.course.active_url_slug.startswith('boot-camps/') + assert ( + official_version.course.active_url_slug == + f'boot-camps/{subject.slug}/{org.name}-{slugify(course.title)}' + ) + else: + assert not course.active_url_slug.startswith('boot-camps/') + assert course.active_url_slug == f'{active_url_slug}' + assert not official_version.course.active_url_slug.startswith('boot-camps/') + assert official_version.course.active_url_slug == f'{active_url_slug}' + @ddt.data( ('https://www.example.com', 'test-slug', 'https://www.example.com/course/test-slug'), # pylint: disable=line-too-long diff --git a/course_discovery/apps/course_metadata/toggles.py b/course_discovery/apps/course_metadata/toggles.py index 0f402261f2..6876e8dd28 100644 --- a/course_discovery/apps/course_metadata/toggles.py +++ b/course_discovery/apps/course_metadata/toggles.py @@ -43,3 +43,14 @@ IS_SUBDIRECTORY_SLUG_FORMAT_FOR_EXEC_ED_ENABLED = WaffleSwitch( 'course_metadata.is_subdirectory_slug_format_for_exec_ed_enabled', __name__ ) +# .. toggle_name: course_metadata.is_subdirectory_slug_format_for_bootcamp_enabled +# .. toggle_implementation: WaffleSwitch +# .. toggle_default: False +# .. toggle_description: Enable to use subdirectory slug format for bootcamp courses. +# .. toggle_use_cases: open_edx +# .. toggle_creation_date: 2023-09-05 +# .. toggle_target_removal_date: 2023-09-20 +# .. toggle_tickets: PROD-3611 +IS_SUBDIRECTORY_SLUG_FORMAT_FOR_BOOTCAMP_ENABLED = WaffleSwitch( + 'course_metadata.is_subdirectory_slug_format_for_bootcamp_enabled', __name__ +) From 5b562533ef3a6562d8306381961bd22d514da881 Mon Sep 17 00:00:00 2001 From: AfaqShuaib09 Date: Fri, 8 Sep 2023 16:07:53 +0500 Subject: [PATCH 2/2] chore: remove unwanted changes --- course_discovery/apps/course_metadata/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/course_discovery/apps/course_metadata/constants.py b/course_discovery/apps/course_metadata/constants.py index 9694722092..4e9db7d16c 100644 --- a/course_discovery/apps/course_metadata/constants.py +++ b/course_discovery/apps/course_metadata/constants.py @@ -12,6 +12,7 @@ r'boot-camps\/[a-zA-Z0-9-_]+\/[a-zA-Z0-9-_]+$' ) SLUG_FORMAT_REGEX = r'[a-zA-Z0-9-_]+$' + DEFAULT_SLUG_FORMAT_ERROR_MSG = 'Enter a valid “slug” consisting of letters, numbers, underscores or hyphens.' MASTERS_PROGRAM_TYPE_SLUG = 'masters'