Django Waffle
Django Waffle is a feature flipper for Django projects, allowing developers to dynamically toggle features on or off without redeploying code. It supports flags, switches, and samples, enabling use cases like A/B testing, phased rollouts, and granular control based on users, groups, or percentages. The library is actively maintained, with its current version being 5.0.0, and receives fairly steady updates.
Warnings
- breaking Version 5.0.0 dropped support for several End-of-Life Django versions (3.2, 4.0, 4.1) and Python 3.8. Users on these older versions must upgrade their Python/Django environment before updating to django-waffle 5.0.0. Similar breaking changes for Python and Django versions occurred in v4.0.0 and v3.0.0.
- breaking In v5.0.0, the behavior of `flag.everyone` was corrected. If you relied on the previous, potentially incorrect, behavior of this setting for flags, your application's feature rollout might change.
- gotcha Django Waffle aggressively caches flags, switches, and samples. After upgrading the library or if changing the underlying object structure (e.g., custom models), you may need to clear your cache or change the `WAFFLE_CACHE_PREFIX` setting to avoid stale data. Additionally, in high-traffic, multi-database environments, `WAFFLE_ALWAYS_READ_FROM_DB=True` might be necessary to prevent stale data due to cache misses falling back to potentially old read replicas.
- gotcha By default, if Waffle encounters a reference to a flag, switch, or sample that is not defined in the database, it considers it inactive (`False`). This can lead to unexpected behavior if you expect features to be active by default. You can change this behavior via settings like `WAFFLE_CREATE_MISSING_FLAGS` or `WAFFLE_FLAG_DEFAULT`.
- gotcha If you plan to use custom Flag, Switch, or Sample models, you must define the `WAFFLE_FLAG_MODEL`, `WAFFLE_SWITCH_MODEL`, or `WAFFLE_SAMPLE_MODEL` setting in `settings.py` from the very beginning of your project. Django's migration framework does not support changing swappable models after the initial migration, which can lead to complex migration issues later. Custom models must inherit from their respective `waffle.models.AbstractBase*` classes.
- gotcha When using `django-waffle`'s `waffle_status` JSON endpoint with Django Rest Framework (DRF), authentication might not be processed correctly by the Waffle middleware, leading to incorrect flag statuses being reported for authenticated users. This is because DRF's authentication typically runs at the start of the view, not in middleware.
Install
-
pip install django-waffle
Imports
- flag_is_active, switch_is_active, sample_is_active
from waffle import flag_is_active, switch_is_active, sample_is_active
- waffle_flag, waffle_switch, waffle_sample
from waffle.decorators import waffle_flag, waffle_switch, waffle_sample
- waffle_tags
{% load waffle_tags %} - Flag, Switch, Sample
from waffle.models import Flag, Switch, Sample
Quickstart
# settings.py
INSTALLED_APPS = [
# ...
'django.contrib.auth',
'django.contrib.messages',
'waffle',
# ...
]
MIDDLEWARE = [
# ...
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'waffle.middleware.WaffleMiddleware',
# ...
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# Terminal
# python manage.py migrate
# python manage.py createsuperuser # If you don't have one
# Then, log into Django Admin to create a Flag named 'my_new_feature'
# Set it to active for 'Superusers' or a percentage of users.
# views.py
from django.shortcuts import render
from waffle.decorators import waffle_flag
from waffle import flag_is_active
@waffle_flag('my_new_feature')
def my_feature_view(request):
# This view will return a 404 if 'my_new_feature' flag is not active for the request.
message = "Welcome to the new feature!"
if flag_is_active(request, 'another_flag'):
message += " (Another flag is also active.)"
return render(request, 'my_app/feature_page.html', {'message': message})
def my_other_view(request):
context = {}
if flag_is_active(request, 'some_conditional_element'):
context['show_element'] = True
return render(request, 'my_app/regular_page.html', context)
# my_app/feature_page.html
{% extends 'base.html' %}
{% load waffle_tags %}
{% block content %}
<h1>{{ message }}</h1>
<p>This page is protected by 'my_new_feature' flag.</p>
{% endblock %}
# my_app/regular_page.html
{% extends 'base.html' %}
{% load waffle_tags %}
{% block content %}
<h1>Regular Content</h1>
{% flag 'some_conditional_element' %}
<p>This element only shows if 'some_conditional_element' is active!</p>
{% else %}
<p>Conditional element is currently hidden.</p>
{% endflag %}
{% endblock %}