-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add API endpoint to manage course waffle flags (#35622)
Co-authored-by: Sagirov Eugeniy <[email protected]>
- Loading branch information
Showing
6 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
146 changes: 146 additions & 0 deletions
146
cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
""" | ||
API Serializers for course waffle flags | ||
""" | ||
|
||
from rest_framework import serializers | ||
|
||
from cms.djangoapps.contentstore import toggles | ||
|
||
|
||
class CourseWaffleFlagsSerializer(serializers.Serializer): | ||
""" | ||
Serializer for course waffle flags | ||
""" | ||
use_new_home_page = serializers.SerializerMethodField() | ||
use_new_custom_pages = serializers.SerializerMethodField() | ||
use_new_schedule_details_page = serializers.SerializerMethodField() | ||
use_new_advanced_settings_page = serializers.SerializerMethodField() | ||
use_new_grading_page = serializers.SerializerMethodField() | ||
use_new_updates_page = serializers.SerializerMethodField() | ||
use_new_import_page = serializers.SerializerMethodField() | ||
use_new_export_page = serializers.SerializerMethodField() | ||
use_new_files_uploads_page = serializers.SerializerMethodField() | ||
use_new_video_uploads_page = serializers.SerializerMethodField() | ||
use_new_course_outline_page = serializers.SerializerMethodField() | ||
use_new_unit_page = serializers.SerializerMethodField() | ||
use_new_course_team_page = serializers.SerializerMethodField() | ||
use_new_certificates_page = serializers.SerializerMethodField() | ||
use_new_textbooks_page = serializers.SerializerMethodField() | ||
use_new_group_configurations_page = serializers.SerializerMethodField() | ||
|
||
def get_course_key(self): | ||
""" | ||
Retrieve the course_key from the context | ||
""" | ||
return self.context.get("course_key") | ||
|
||
def get_use_new_home_page(self, obj): | ||
""" | ||
Method to get the use_new_home_page switch | ||
""" | ||
return toggles.use_new_home_page() | ||
|
||
def get_use_new_custom_pages(self, obj): | ||
""" | ||
Method to get the use_new_custom_pages switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_custom_pages(course_key) | ||
|
||
def get_use_new_schedule_details_page(self, obj): | ||
""" | ||
Method to get the use_new_schedule_details_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_schedule_details_page(course_key) | ||
|
||
def get_use_new_advanced_settings_page(self, obj): | ||
""" | ||
Method to get the use_new_advanced_settings_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_advanced_settings_page(course_key) | ||
|
||
def get_use_new_grading_page(self, obj): | ||
""" | ||
Method to get the use_new_grading_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_grading_page(course_key) | ||
|
||
def get_use_new_updates_page(self, obj): | ||
""" | ||
Method to get the use_new_updates_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_updates_page(course_key) | ||
|
||
def get_use_new_import_page(self, obj): | ||
""" | ||
Method to get the use_new_import_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_import_page(course_key) | ||
|
||
def get_use_new_export_page(self, obj): | ||
""" | ||
Method to get the use_new_export_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_export_page(course_key) | ||
|
||
def get_use_new_files_uploads_page(self, obj): | ||
""" | ||
Method to get the use_new_files_uploads_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_files_uploads_page(course_key) | ||
|
||
def get_use_new_video_uploads_page(self, obj): | ||
""" | ||
Method to get the use_new_video_uploads_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_video_uploads_page(course_key) | ||
|
||
def get_use_new_course_outline_page(self, obj): | ||
""" | ||
Method to get the use_new_course_outline_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_course_outline_page(course_key) | ||
|
||
def get_use_new_unit_page(self, obj): | ||
""" | ||
Method to get the use_new_unit_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_unit_page(course_key) | ||
|
||
def get_use_new_course_team_page(self, obj): | ||
""" | ||
Method to get the use_new_course_team_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_course_team_page(course_key) | ||
|
||
def get_use_new_certificates_page(self, obj): | ||
""" | ||
Method to get the use_new_certificates_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_certificates_page(course_key) | ||
|
||
def get_use_new_textbooks_page(self, obj): | ||
""" | ||
Method to get the use_new_textbooks_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_textbooks_page(course_key) | ||
|
||
def get_use_new_group_configurations_page(self, obj): | ||
""" | ||
Method to get the use_new_group_configurations_page switch | ||
""" | ||
course_key = self.get_course_key() | ||
return toggles.use_new_group_configurations_page(course_key) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
cms/djangoapps/contentstore/rest_api/v1/views/course_waffle_flags.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
""" API Views for course waffle flags """ | ||
|
||
from opaque_keys.edx.keys import CourseKey | ||
from rest_framework.decorators import APIView | ||
from rest_framework.response import Response | ||
|
||
from openedx.core.lib.api.view_utils import view_auth_classes | ||
|
||
from ..serializers import CourseWaffleFlagsSerializer | ||
|
||
|
||
@view_auth_classes(is_authenticated=True) | ||
class CourseWaffleFlagsView(APIView): | ||
""" | ||
API view to retrieve course waffle flag settings for a specific course. | ||
This view provides a GET endpoint that returns the status of various waffle | ||
flags for a given course. It requires the user to be authenticated. | ||
""" | ||
|
||
def get(self, request, course_id=None): | ||
""" | ||
Retrieve the waffle flag settings for the specified course. | ||
Args: | ||
request (HttpRequest): The HTTP request object. | ||
course_id (str, optional): The ID of the course for which to retrieve | ||
the waffle flag settings. If not provided, | ||
defaults to None. | ||
Returns: | ||
Response: A JSON response containing the status of various waffle flags | ||
for the specified course. | ||
**Example Request** | ||
GET /api/contentstore/v1/course_waffle_flags | ||
GET /api/contentstore/v1/course_waffle_flags/course-v1:test+test+test | ||
**Response Values** | ||
A JSON response containing the status of various waffle flags | ||
for the specified course. | ||
**Example Response** | ||
```json | ||
{ | ||
"use_new_home_page": true, | ||
"use_new_custom_pages": true, | ||
"use_new_schedule_details_page": true, | ||
"use_new_advanced_settings_page": true, | ||
"use_new_grading_page": true, | ||
"use_new_updates_page": true, | ||
"use_new_import_page": true, | ||
"use_new_export_page": true, | ||
"use_new_files_uploads_page": true, | ||
"use_new_video_uploads_page": false, | ||
"use_new_course_outline_page": true, | ||
"use_new_unit_page": false, | ||
"use_new_course_team_page": true, | ||
"use_new_certificates_page": true, | ||
"use_new_textbooks_page": true, | ||
"use_new_group_configurations_page": true | ||
} | ||
``` | ||
""" | ||
course_key = CourseKey.from_string(course_id) if course_id else None | ||
serializer = CourseWaffleFlagsSerializer( | ||
context={"course_key": course_key}, data={} | ||
) | ||
serializer.is_valid(raise_exception=True) | ||
return Response(serializer.data) |
112 changes: 112 additions & 0 deletions
112
cms/djangoapps/contentstore/rest_api/v1/views/tests/test_course_waffle_flags.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
""" | ||
Unit tests for the course waffle flags view | ||
""" | ||
|
||
from django.contrib.auth import get_user_model | ||
from django.urls import reverse | ||
from rest_framework import status | ||
|
||
from cms.djangoapps.contentstore.tests.utils import CourseTestCase | ||
from openedx.core.djangoapps.waffle_utils.models import WaffleFlagCourseOverrideModel | ||
|
||
User = get_user_model() | ||
|
||
|
||
class CourseWaffleFlagsViewTest(CourseTestCase): | ||
""" | ||
Tests for the CourseWaffleFlagsView endpoint, which returns waffle flag states | ||
for a specific course or globally if no course ID is provided. | ||
""" | ||
|
||
course_waffle_flags = [ | ||
"use_new_custom_pages", | ||
"use_new_schedule_details_page", | ||
"use_new_advanced_settings_page", | ||
"use_new_grading_page", | ||
"use_new_updates_page", | ||
"use_new_import_page", | ||
"use_new_export_page", | ||
"use_new_files_uploads_page", | ||
"use_new_video_uploads_page", | ||
"use_new_course_outline_page", | ||
"use_new_unit_page", | ||
"use_new_course_team_page", | ||
"use_new_certificates_page", | ||
"use_new_textbooks_page", | ||
"use_new_group_configurations_page", | ||
] | ||
|
||
def setUp(self): | ||
""" | ||
Set up test data and state before each test method. | ||
This method initializes the endpoint URL and creates a set of waffle flags | ||
for the test course, setting each flag's value to `True`. | ||
""" | ||
super().setUp() | ||
self.url = reverse("cms.djangoapps.contentstore:v1:course_waffle_flags") | ||
self.create_waffle_flags(self.course_waffle_flags) | ||
|
||
def create_waffle_flags(self, flags, enabled=True): | ||
""" | ||
Helper method to create waffle flag entries in the database for the test course. | ||
Args: | ||
flags (list): A list of flag names to set up. | ||
enabled (bool): The value to set for each flag's enabled state. | ||
""" | ||
for flag in flags: | ||
WaffleFlagCourseOverrideModel.objects.create( | ||
waffle_flag=f"contentstore.new_studio_mfe.{flag}", | ||
course_id=self.course.id, | ||
enabled=enabled, | ||
) | ||
|
||
def expected_response(self, enabled=False): | ||
""" | ||
Generate an expected response dictionary based on the enabled flag. | ||
Args: | ||
enabled (bool): State to assign to each waffle flag in the response. | ||
Returns: | ||
dict: A dictionary with each flag set to the value of `enabled`. | ||
""" | ||
return {flag: enabled for flag in self.course_waffle_flags} | ||
|
||
def test_get_course_waffle_flags_with_course_id(self): | ||
""" | ||
Test that waffle flags for a specific course are correctly returned when | ||
a valid course ID is provided. | ||
Expected Behavior: | ||
- The response should return HTTP 200 status. | ||
- Each flag returned should be `True` as set up in the `setUp` method. | ||
""" | ||
course_url = reverse( | ||
"cms.djangoapps.contentstore:v1:course_waffle_flags", | ||
kwargs={"course_id": self.course.id}, | ||
) | ||
|
||
expected_response = self.expected_response(enabled=True) | ||
expected_response["use_new_home_page"] = False | ||
|
||
response = self.client.get(course_url) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
self.assertDictEqual(expected_response, response.data) | ||
|
||
def test_get_course_waffle_flags_without_course_id(self): | ||
""" | ||
Test that the default waffle flag states are returned when no course ID is provided. | ||
Expected Behavior: | ||
- The response should return HTTP 200 status. | ||
- Each flag returned should default to `False`, representing the global | ||
default state for each flag. | ||
""" | ||
expected_response = self.expected_response(enabled=False) | ||
expected_response["use_new_home_page"] = False | ||
|
||
response = self.client.get(self.url) | ||
self.assertEqual(response.status_code, status.HTTP_200_OK) | ||
self.assertDictEqual(expected_response, response.data) |