Skip to content

Commit 5452603

Browse files
committed
feat: implement migration type determination logic based on current and previous record states
1 parent 9b2d3b7 commit 5452603

2 files changed

Lines changed: 176 additions & 185 deletions

File tree

openedx_authz/handlers.py

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import annotations
88

99
import logging
10+
from typing import Union
1011

1112
from casbin_adapter.models import CasbinRule
1213
from django.conf import settings
@@ -137,13 +138,54 @@ def handle_org_waffle_flag_change(sender, instance, **kwargs) -> None:
137138
if WaffleFlagOrgOverrideModel is not None:
138139
post_save.connect(handle_org_waffle_flag_change, sender=WaffleFlagOrgOverrideModel)
139140

141+
140142
# Match ``WaffleFlagCourseOverrideModel.OVERRIDE_CHOICES`` / ``override_value`` in edx-platform:
141143
# the flag is effectively forced on only when the row is enabled and ``override_choice`` is "on".
142144
WAFFLE_OVERRIDE_FORCE_ON = "on"
143145
WAFFLE_OVERRIDE_FORCE_OFF = "off"
144146

145147

146-
def get_excluded_course_ids_for_org_migration(org_id: str, reverse_choice: str) -> frozenset[str]:
148+
# Type Alias for better readability
149+
WaffleOverrideRecord = Union[WaffleFlagCourseOverrideModel, WaffleFlagOrgOverrideModel]
150+
151+
152+
def get_migration_type(
153+
current_record: WaffleOverrideRecord, previous_record: WaffleOverrideRecord | None
154+
) -> MigrationType | None:
155+
"""
156+
Determine the migration type by comparing the current and previous record states.
157+
158+
A FORWARD migration occurs when a flag transitions from disabled/not-forced
159+
to enabled and forced-on. A ROLLBACK occurs when it transitions back.
160+
161+
Args:
162+
current_record (WaffleOverrideRecord): The state of the record in the current transaction.
163+
previous_record (WaffleOverrideRecord | None): The state of the record prior to the current transaction.
164+
165+
Returns:
166+
MigrationType.FORWARD: If the flag is newly forced on.
167+
MigrationType.ROLLBACK: If the forced-on state is removed.
168+
None: If there is no effective change in the flag's behavior.
169+
"""
170+
# 1. Handle New Records (Creation)
171+
if not previous_record:
172+
is_active = current_record.enabled and current_record.override_choice == WAFFLE_OVERRIDE_FORCE_ON
173+
return MigrationType.FORWARD if is_active else None
174+
175+
# 2. Extract Effective States
176+
# A flag is "effectively active" only if enabled AND set to FORCE_ON
177+
was_effectively_active = previous_record.enabled and previous_record.override_choice == WAFFLE_OVERRIDE_FORCE_ON
178+
is_effectively_active = current_record.enabled and current_record.override_choice == WAFFLE_OVERRIDE_FORCE_ON
179+
180+
# 3. Return None if state hasn't changed
181+
if is_effectively_active == was_effectively_active:
182+
return None
183+
184+
# 4. Determine Transition Type
185+
return MigrationType.FORWARD if is_effectively_active else MigrationType.ROLLBACK
186+
187+
188+
def get_excluded_course_ids_for_org_migration(org_id: str, override_choice: str) -> frozenset[str]:
147189
"""
148190
Collect course-level authoring flag overrides for an org that oppose the new org-level state.
149191
@@ -152,13 +194,16 @@ def get_excluded_course_ids_for_org_migration(org_id: str, reverse_choice: str)
152194
153195
Args:
154196
org_id (str): Organization short name.
155-
reverse_choice (str): The reverse choice of the org waffle flag.
197+
override_choice (str): The override choice of the org waffle flag.
156198
157199
Returns:
158200
frozenset[str]: course ids excluded from org migration
159201
"""
160202
# We only need to check the current set (active flags). Opposing overrides are rows that
161203
# force the opposite of the org transition (Force On vs Force Off), not merely inactive rows.
204+
reverse_choice = (
205+
WAFFLE_OVERRIDE_FORCE_ON if override_choice == WAFFLE_OVERRIDE_FORCE_OFF else WAFFLE_OVERRIDE_FORCE_OFF
206+
)
162207
filter_kwargs = {
163208
"waffle_flag": AUTHZ_COURSE_AUTHORING_FLAG.name,
164209
"course_id__startswith": f"course-v1:{org_id}+",
@@ -170,8 +215,8 @@ def get_excluded_course_ids_for_org_migration(org_id: str, reverse_choice: str)
170215

171216

172217
def trigger_course_authoring_migration(
173-
sender: type[WaffleFlagCourseOverrideModel | WaffleFlagOrgOverrideModel],
174-
instance: WaffleFlagCourseOverrideModel | WaffleFlagOrgOverrideModel,
218+
sender: type[WaffleOverrideRecord],
219+
instance: WaffleOverrideRecord,
175220
scope_key: str,
176221
) -> None:
177222
"""
@@ -182,17 +227,13 @@ def trigger_course_authoring_migration(
182227
which handles tracking and concurrent-run protection.
183228
184229
Args:
185-
sender: The model class (WaffleFlagCourseOverrideModel or WaffleFlagOrgOverrideModel).
230+
sender: The model class (WaffleOverrideRecord).
186231
instance: The waffle flag instance that triggered the migration.
187232
scope_key (str): Course ID or organization name.
188233
"""
189234
if instance.waffle_flag != AUTHZ_COURSE_AUTHORING_FLAG.name:
190235
return
191236

192-
if not instance.enabled:
193-
logger.info("Waffle flag is disabled, skipping migration")
194-
return
195-
196237
if not settings.ENABLE_AUTOMATIC_AUTHZ_COURSE_AUTHORING_MIGRATION:
197238
logger.info("ENABLE_AUTOMATIC_AUTHZ_COURSE_AUTHORING_MIGRATION is set to False, skipping migration")
198239
return
@@ -213,22 +254,15 @@ def trigger_course_authoring_migration(
213254

214255
prev_record = sender.objects.filter(**filter_kwargs).exclude(id=instance.id).order_by("-change_date").first()
215256

216-
if prev_record and prev_record.enabled and instance.override_choice == prev_record.override_choice:
257+
migration_type = get_migration_type(instance, prev_record)
258+
if migration_type is None:
217259
logger.info("No change in waffle flag, skipping migration")
218260
return
219261

220-
if instance.override_choice == WAFFLE_OVERRIDE_FORCE_ON:
221-
migration_type = MigrationType.FORWARD
222-
reverse_choice = WAFFLE_OVERRIDE_FORCE_OFF
223-
else:
224-
migration_type = MigrationType.ROLLBACK
225-
reverse_choice = WAFFLE_OVERRIDE_FORCE_ON
226-
227262
excluded_course_ids = frozenset()
228263
if isinstance(instance, WaffleFlagOrgOverrideModel):
229264
excluded_course_ids = get_excluded_course_ids_for_org_migration(
230-
org_id=scope_key,
231-
reverse_choice=reverse_choice,
265+
org_id=scope_key, override_choice=instance.override_choice
232266
)
233267

234268
logger.info("Triggering %s migration for %s:%s due to waffle flag change", migration_type, scope_type, scope_key)

0 commit comments

Comments
 (0)