77from __future__ import annotations
88
99import logging
10+ from typing import Union
1011
1112from casbin_adapter .models import CasbinRule
1213from django .conf import settings
@@ -137,13 +138,54 @@ def handle_org_waffle_flag_change(sender, instance, **kwargs) -> None:
137138if 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".
142144WAFFLE_OVERRIDE_FORCE_ON = "on"
143145WAFFLE_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
172217def 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