Adds a `features` app with a swappable waffle Flag (WAFFLE_FLAG_MODEL) that gains a m2m to Club, so a feature can be rolled out club by club. Two things worth calling out: - `everyone` keeps waffle's contract of overriding *all* other targeting, so club targeting is only consulted when `everyone is None`. This keeps `everyone = False` usable as a hard kill-switch. - m2m edits don't call save(), so waffle's per-flag cache would go stale when clubs are added or removed. A m2m_changed receiver flushes it from both directions, and get_flush_keys() drops the club-set key alongside waffle's own. Note for future flag tests: waffle's cache is not rolled back with the test transaction, so tests touching flags must clear it (see features/tests.py). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
27 lines
882 B
Python
27 lines
882 B
Python
"""Keep waffle's flag cache honest when club targeting changes.
|
|
|
|
waffle caches a flag's M2M ids and only flushes on ``save()``. Editing an M2M
|
|
does not call ``save()``, so adding or removing a club would otherwise leave a
|
|
stale cached set and the flag would keep answering with the old value.
|
|
"""
|
|
|
|
from django.db.models.signals import m2m_changed
|
|
from django.dispatch import receiver
|
|
|
|
from .models import Flag
|
|
|
|
FLUSH_ACTIONS = {"post_add", "post_remove", "post_clear"}
|
|
|
|
|
|
@receiver(m2m_changed, sender=Flag.clubs.through)
|
|
def flush_flag_club_cache(sender, instance, action, reverse, pk_set, **kwargs):
|
|
if action not in FLUSH_ACTIONS:
|
|
return
|
|
|
|
if isinstance(instance, Flag):
|
|
instance.flush()
|
|
else:
|
|
# Reverse edit (club.flags.add(flag)): flush each flag touched.
|
|
for flag in Flag.objects.filter(pk__in=pk_set or []):
|
|
flag.flush()
|