Feature flags in Django

Sometimes you have unfinished work in your codebase and you have to release some other functionality. It might be a hotfix or something that has become urgent before you’ve been able to plan for it

Actually, there are times when you can’t plan ahead so much. There are multiple reasons for that, so one good strategy to use in a changing environment is to try to do Many More Much Smaller Steps and Continuous Integration.

But somethimes thing go south and you have to stop some work before you’ve finished it. And sometimes you decide that it is better to have that work integrated than to leave it in a soon to be stalled branch.

In this scenario you can use a feature flag.

—Allen Holub on twitter

Feature flags can be simple global variables

I once worked in a project where we had feature flags for conditionally enabling and trying things. I have bad memories about that project and so the term had been tabu for a long time. Until I rediscovered it:

—Allen Holub on twitter

Holub rang a bell. As he discusses, a feature flag is kinda waterfallish, but, if suficiently short-lived, it allows for continuous integration, avoiding long running branches (or branches at all) and let us work without code inventory, i.e. trunk based development. This is actually good in small teams that do mob programming, and don’t have to deal with cargo cult ceremonies such MR/PR and the like.

And what iluminated me is seeing it as a simple `if` statement instead of complicated mechanism I though it was.

The global variable antipattern

What rang the bell was that you can implement feature flags with a simple global variable and an `if` statement. Yes, sounds silly and an antipattern, but this is where you have to stop believing in slogans ant think for yourself: it is okay to break the rules. Specially if the rules are others’.

A global variable is an antipattern unless you want the damn global variable.

Now the example.

Say that you are developing the brand new potato concept in your ACME application but it will take you two more days and there’s a bug or something and you need some of your changes back in and can’t afford for branches and MRs.

Easy. Create the ACME_FLAGS_POTATO = False global variable and use the old if statement friend:

if flags.ACME_FLAGS_POTATO:
    # new potato code here
If you can, you should avoid the else part, having two more extra points for having a statement based code instead of an expression based one!😅

Now you’re ready to integrate the code and publish it on production.

In your development setup you can override the flag and keep the feature visible when you wan to work in it.

Implementing lightweight feature flags in django

Let’s go to django. As I’ve mentioned before, django is awesome. You’ve already got your homework done. The settings DSL is your injector.

From now on, I suppose you’ve converted your `settings` module into a package, with the following structure:

settings/
├── base.py
├── local.py
├── production.py
├── test.py
└── staging.py

Set your flag in _settings/base.py_ to `False` so that unfinished code is hidden by default:

ACME_FLAGS_POTATO = False

As all the other settings “inherit” from settings/base.py, right now the flag is deactivated in all of them. You can hide the code via the global setting on the affected parts:

from django.conf import settings

if settings.ACME_FLAGS_POTATO:
    # new potato code here

If you’re more exquisite, you can inject it to the affected functions:

from django.conf import settings

def afected_function(foo, bar, baz=None, flags=settings):
    if flags.ACME_FLAGS_POTATO:
        # new potato code here

Yes, this simple pattern will allow you to test the afected_function with both values of the flag right now.

Django context processors: django’s template system global variables

If you’ve made it this far, see how you can use the flags in your templates. As I said, **django is awesome** and already has an extension point for doing that.

A django context processor is a function tha returns a context (dictionary), given a request object. You just need a context processor that django will call and merge the returned dictionary back to the template system default context.

So, define the context processor function:

from django.conf import settings

def acme_flags_context(request):
    return {
        'acme_flags': { k, v for k, v in settings if k.startswith('ACME_FLAGS_') }
    }

and add it to the TEMPLATES setting, on settings/base.py.

TEMPLATES = [
    {
       # ...
        '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',
                'acme.context_processors.acme_flags_context',
            ],
        },
    },
]

Now acme_flags is available on the templates, and you can hide unwanted or partial interface parts easily:

{% if acme_flags.ACME_FLAGS_POTATO %}
{% comment %} new potato template {% endcomment %}
{% endif %}

Flags for environment

If you use a settings module layout as the one presented above, you can override the flag depending on the environment. For example, in your setting/local.py you can have:

import * from base

ACME_FLAGS_POTATO = True

and keep working in the _potato_ locally.

You can have this in a stagging environment an in prod just with different settings. You can also read it from the environment, if you like:

ACME_FLAGS_POTATO = getenv('POTATO_ENABLED', False)

A/B testing for free

Have I forgot to mention that django is awesome? The context processor has the request object, so you can change the flag dynamically also: by host, by user, by user permissions/groups, whatever. You just have to add a little bit of extra logic on the context processor function.

With this, you will be able to integrate changes fast and even check them with users to see if the thing is going in the right direction.

Final remarks

Feture flags don’t need to be great pieces of engineering. They can be simple and they get the job done.

Be aware, though, that if not short lived, they can be worse than branches. Use them with care. This means that you have to take care of decomissioning them once the functionality is already finished and published. Don’t keep your code with a flac permanently in True. Clean up the house, please.

On the practical side, I used a naming conventions like ACME_FLAGS in the example. This is not casual. It allow you to easily riggrep your repo and decomission the flag fast by finding all the places. If you’re doing small steps, they should be minimal! The name is sufficiently akward so that you want to remove them soon.