Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions openedx_authz/rest_api/v1/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@
),
path("assignments/", views.AssignmentsAPIView.as_view(), name="assignment-list"),
path("scopes/", views.ScopesAPIView.as_view(), name="scope-list"),
path("waffle-flag-states/", views.WaffleFlagStatesAPIView.as_view(), name="waffle-flag-states"),
]
32 changes: 31 additions & 1 deletion openedx_authz/rest_api/v1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
UserValidationAPIViewResponseSerializer,
UserValidationAPIViewSerializer,
)
from openedx_authz.utils import get_user_by_username_or_email
from openedx_authz.utils import get_user_by_username_or_email, get_waffle_flag_states

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -1374,3 +1374,33 @@ def get(self, request: HttpRequest) -> Response:
paginator = self.pagination_class()
paginated_response_data = paginator.paginate_queryset(assignments, request)
return paginator.get_paginated_response(paginated_response_data)


@view_auth_classes()
class WaffleFlagStatesAPIView(APIView):
Comment thread
MaferMazu marked this conversation as resolved.
Resolved
"""
Simple API view that returns the waffle flag states from utils.get_waffle_flag_states.

**Endpoints**

- GET: Retrieve the enablement state of the course-authoring waffle flag across different scopes.

**Response Format**

* 'global' (bool): True if the global waffle flag is enabled.
* 'org' (bool): True if at least one organization-level override is enabled.
* 'course' (bool): True if at least one course-level override is enabled.

**Example Request**

GET /api/authz/v1/waffle-flag-states/
"""

def get(self, request: HttpRequest) -> Response:
"""Retrieve the enablement state of the course-authoring waffle flag across different scopes."""
try:
data = get_waffle_flag_states()
return Response(data, status=status.HTTP_200_OK)
except Exception as e: # pylint: disable=broad-exception-caught
logger.exception("Error getting waffle flag states: %s", e)
return Response({"message": "error"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
54 changes: 54 additions & 0 deletions openedx_authz/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
from django.db.models import Q
from edx_django_utils.cache import RequestCache

try:
from openedx.core.djangoapps.waffle_utils.models import (
WaffleFlagCourseOverrideModel,
WaffleFlagOrgOverrideModel,
)
from openedx.core.toggles import AUTHZ_COURSE_AUTHORING_FLAG
except ImportError:
WaffleFlagCourseOverrideModel = None
WaffleFlagOrgOverrideModel = None
AUTHZ_COURSE_AUTHORING_FLAG = None

from waffle.models import Flag

# Match handlers.py semantics: an override forces ON when override_choice == "on"
WAFFLE_OVERRIDE_FORCE_ON = "on"

User = get_user_model()

_STAFF_SUPERUSER_CACHE_NAMESPACE = "rbac_is_staff_or_superuser"
Expand Down Expand Up @@ -50,3 +66,41 @@ def get_user_by_username_or_email(username_or_email: str) -> User:
if hasattr(user, "userretirementrequest"):
raise User.DoesNotExist
return user


def get_waffle_flag_states() -> dict:
"""
Retrieve the enablement state of the course-authoring waffle flag across different scopes.

Returns:
dict: A dictionary mapping scopes to their activation status:
* 'global' (bool): True if the global waffle flag is enabled.
* 'org' (bool): True if at least one organization-level override is enabled.
* 'course' (bool): True if at least one course-level override is enabled.
"""
# Global flag (falls back False if toggle not available)
global_enabled = False
if AUTHZ_COURSE_AUTHORING_FLAG is not None:
gf = Flag.objects.filter(name=AUTHZ_COURSE_AUTHORING_FLAG.name).first()
global_enabled = bool(gf and gf.everyone)

org_active = False
course_active = False
Comment thread
MaferMazu marked this conversation as resolved.
Resolved

# Check if any org-level override currently forces the flag on (no org filter)
if WaffleFlagOrgOverrideModel is not None and AUTHZ_COURSE_AUTHORING_FLAG is not None:
org_active = WaffleFlagOrgOverrideModel.objects.current_set().filter(
waffle_flag=AUTHZ_COURSE_AUTHORING_FLAG.name,
enabled=True,
override_choice=WAFFLE_OVERRIDE_FORCE_ON,
).exists()

# Check if any course-level override currently forces the flag on (no course filter)
if WaffleFlagCourseOverrideModel is not None and AUTHZ_COURSE_AUTHORING_FLAG is not None:
course_active = WaffleFlagCourseOverrideModel.objects.current_set().filter(
waffle_flag=AUTHZ_COURSE_AUTHORING_FLAG.name,
enabled=True,
override_choice=WAFFLE_OVERRIDE_FORCE_ON,
).exists()
Comment thread
MaferMazu marked this conversation as resolved.
Resolved

return {"global": global_enabled, "org": org_active, "course": course_active}