Add club-scoped feature flags on django-waffle

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>
This commit is contained in:
2026-07-13 16:42:22 +02:00
parent eace903f05
commit fed24bfee3
10 changed files with 268 additions and 0 deletions

View File

@@ -58,9 +58,16 @@ INSTALLED_APPS = [
"events.apps.EventsConfig",
"formbuilder.apps.FormbuilderConfig",
"shop.apps.ShopConfig",
"waffle",
"features.apps.FeaturesConfig",
"controlpanel.apps.ControlpanelConfig",
]
# Feature flags (django-waffle). The Flag model is swappable, like AUTH_USER_MODEL:
# ours adds a `clubs` M2M so a feature can be turned on per tenant. Because the
# tenant middleware sets request.club, `flag_is_active(request, "x")` just works.
WAFFLE_FLAG_MODEL = "features.Flag"
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",