Привет, Хабр!

Сегодня поговорим о том, как включать и выключать функциональность в Django, не разворачивая каждый раз новый деплой. В больших проектах эту задачу решают через feature flags, такие условные флажки , которые позволяют запускать скрытые возможности лишь для части пользователей или откатывать фичи, не выкатывая заново весь код. Если вы хотите поэтапно раскатать новую функцию, сделать A/B тест или просто спрятать недоделанный модуль за переключателем, вам сюда.

Начну с краткого экскурса: feature flag — это механизм, позволяющий менять поведение приложения на ходу, основываясь на некотором условии. Код новой фичи уже находится в продакшене, но он обёрнут в проверку, включён флаг или нет. Можно настроить флаг так, чтобы он включался для определённых пользователей, по дате или даже по параметру URL. Возможности широкие, и Django‑Flags их как раз дает это в удобной форме.

Django‑Flags — это стороннее приложенице, ставится через pip. Добавляем пакет и подключаем в settings.py:

pip install django-flags

После установки пропишем приложение и контекст‑процессор. В settings.py убедимся, что в INSTALLED_APPS добавлено 'flags'. Плюс нужно включить контекстный процессор запросов, если его ещё нет, django.template.context_processors.request (он нужен, чтобы флаги работали в шаблонах). Не забудьте выполнить миграции: python manage.py migrate. База данных нужна, потому что флаги можно хранить и редактировать через админку.

Теперь можно добавлять флаги. Есть два подхода: описывать флаги в настройках или полностью управлять ими через админку. По дефолту Django‑Flags поддерживает оба способа одновременно, флаги из настроек и из базы объединяются. Самый простой вариант объявить флаг в settings.py так:

# settings.py
FLAGS = {
    'NEW_COOL_FEATURE': []
}

NEW_COOL_FEATURE — это имя фича‑флага. Пустой список условий [] означает, что флаг пока всегда False, ведь условий нет, значит нет повода включаться. Зачем так делать? Затем, что сам факт наличия флага уже можно проверить в коде или шаблоне.

А включить его мы сможем позднее, задав условия. Условие можно добавить либо прямо в настройки (в тот же словарь), либо через админку Django‑Flags и там в Flag States. Кстати, флаг можно не объявлять заранее в коде вовсе, если создать его через админку, библиотека тоже подхватит. Главное, чтобы имя совпадало при использовании.

Сразу приведу пример использования в шаблоне. Допустим, идет работа над новым разделом сайта и нужно скрыть блок интерфейса, пока фича не готова. Оборачиваем его в специальный template‑тег:

{% load feature_flags %}
{% flag_enabled 'NEW_COOL_FEATURE' as new_feature_on %}

{% if new_feature_on %}
  <div class="new-feature-widget">
    Здесь что-то классное, доступное пока избранным.
  </div>
{% endif %}

Подключили тег feature_flags и с помощью конструкции {% flag_enabled 'NEW_COOL_FEATURE' as new_feature_on %} попросили: «определи, включён ли флаг NEW_COOL_FEATURE, и запиши результат в переменную new_feature_o». Дальше обычный {% if %} показывает блок только если флаг включён. Можно зайти в админку, поставить галочку, и у пользователей сразу появится новый блок. Это, конечно, при условии, что мы уже настроили условие для флага.

Поговорим об условиях. У флага может быть одна или несколько conditions, условий срабатывания. Например, включать флаг только для пользователя admin, или после определённой даты, или при наличии параметра в URL. Django‑Flags предлагает несколько типов условий: boolean (просто включён/выключен вручную), user (по имени пользователя), anonymous (для незалогиненных или залогиненных), parameter (по параметру запроса), path matches (по URL, regex), after date / before date (по времени).

Можно даже свои написать, если нужно что‑то экзотическое. Условия можно комбинировать списком. По дефолтует действует логика «ИЛИ», если хотя бы одно условие выполняется, флаг активен. Но есть нюанс, признак required позволяет пометить условие как обязательное. В этом случае флаг включится только если выполнено обязательное условие и какое‑то другое из списка. Вернее, если есть хоть одно required=True, то эти обязательные должны все быть истинны, иначе флаг гаснет, даже если другие бы включили.

Допустим, фичу нужно открыть только для пользователя vanya и только после 1 января 2026. Условий два: имя юзера и дата. Тут логика должна быть «И», и тот, и другой фактор. Один из них делаем required. Например:

FLAGS = {
    'MY_SPECIAL_FEATURE': [
        {'condition': 'user', 'value': 'vanya', 'required': True},
        {'condition': 'after date', 'value': '2026-01-01T00:00:00Z'}
    ]
}

Теперь флаг MY_SPECIAL_FEATURE включится лишь тогда, когда пользователь Alice зашёл после наступления нового года 2026. Если зайдёт другой пользователь после 1 января, не сработает (требуется vanya). Если vanya зайдёт раньше даты, тоже не сработает (дата не выполнена). А вот когда оба условия станут истинны, флаг загорится зелёным.

Другой пример — более простой: включение фичи через URL‑параметр. Многие делают скрытые режимы, активируемые параметром, вроде ?enable_feature=1. С Django‑Flags это решается элементарно: условие типа parameter. Добавим в настройки:

FLAGS = {
    'HIDDEN_MODE': [
        ('parameter', 'enable_feature')
    ]
}

Указали кортеж ('parameter', 'enable_feature'). Это значит, что если в запросе есть параметр enable_feature=True, флаг HIDDEN_MODE должен считаться включённым.

Фича в том, что django‑flags сам умеет читать True/False из строки запроса, достаточно наличия ?enable_feature=true (регистр и вариации типа on/1 тоже понимаются). Теперь в шаблоне или коде можем проверять HIDDEN_MODE и, например, показывать какие‑то диагностические панели только когда специально попросили через URL.

Конечно, флаги полезны не только в шаблонах. Часто нужно менять поведение на уровне самого Питона. Например, новый API‑эндпойнт, который пока хотим дать ограниченной аудитории. Django‑Flags имеет несколько способов: можно проверить состояние флага вручную через API или использовать удобные декораторы.

Прямой вызов: функция flag_enabled(name, request=request) возвращает True/False, включён ли флаг для данного запроса. Соответственно, в view можно сделать:

from flags.state import flag_enabled

if flag_enabled('NEW_COOL_FEATURE', request=request):
    # новая логика
    show_new_interface()
else:
    # старое поведение
    show_classic_interface()

Этот вариант хорош, когда нужно ветвление внутри функции. А если хотим целиком оградить представление? Например, сделать новую страницу, но пока отдавать 404 всем, кроме разрешённых пользователей. Для этого есть декораторы. Импортируем их: from flags.decorators import flag_required, flag_check.

  • @flag_required('FLAG_NAME') не пустит в функцию, если флаг выключен. По умолчанию вернёт 404 (Not Found), чтобы не светить сам факт наличия страницы. Можно указать fallback_view=... для перенаправления или кастомного ответа.

  • @flag_check('FLAG_NAME', state=True/False) более гибкий декоратор, позволяющий задать требуемое состояние. Например, @flag_check('BETA_MODE', True) пропустит только если флаг включён, а @flag_check('BETA_MODE', False) наоборот — если флаг выключен (то есть старый режим). Можно и fallback указать аналогично.

Пример использования декоратора:

from flags.decorators import flag_required

@flag_required('BETA_MODE')
def new_view(request):
    return HttpResponse("Эта страница видна, только когда флаг BETA_MODE включён!")

# А вот так с fallback:
def old_view(request):
    return HttpResponse("Старая версия страницы")

@flag_required('BETA_MODE', fallback_view=old_view)
def better_view(request):
    return HttpResponse("Новая версия страницы")

В первом случае new_view просто вернёт 404, если флаг выключен. Во втором better_view при выключенном флаге перенаправит исполнение в old_view, так что пользователи увидят старую версию. Подойдет для постепенного ввода нового функционала на существующем URL, сначала показываем всем старый, параллельно разрабатываем новый под тем же путем, включаем флаг для небольшой группы, они видят новую страницу, остальные ещё старую. Затем расширяем, наконец выключаем флаг и все получают новый контент. Всё это без переключения веток кода в продакшене и без дублей URL.

Дополнительно Django‑Flags умеет встраиваться в маршруты (URL patterns) и классы CBV. Есть, например, FlaggedUrl, можно условно подключать одни urlpatterns вместо других. В большинстве же случаев шаблонов и декораторов хватает с головой.

Ещё есть админка Django‑Flags. После установки пакета у вас появится раздел «Django Flags» с моделями Flag и Condition. Через админку можно щёлкать переключатели не заходя в код.

Для полноты картины скажу пару слов про тестирование. Поскольку флаги могут влиять на код, хорошо бы иметь возможность включать/выключать их в юнит‑тестах. Django‑Flags поддерживает это, можно вызывать enable_flag('FLAG_NAME') и disable_flag('FLAG_NAME') в тестах, они программно добавят условие типа boolean в базу на время выполнения (по умолчанию). Или использовать контекстный менеджер, чтобы временно менять состояние флага.


Попробуйте эти джанго флажки в своём проекте. Библиотека легковесная, понятная, и, что важно, не заставляет вас менять архитектуру. Флаг вам в руки, как говорится!

Если хочется внедрять фичи безопаснее и управляемее, одних флагов мало — важна вся инженерная «обвязка»: архитектура Django‑приложения, API, SSR, работа с данными и поддерживаемый код. На курсе «Django‑разработчик» это разбирают на практике, включая современный бэкенд и связку с Vue.js, чтобы довести навыки до уверенного Middle+. Готовы к серьезному обучению? Пройдите вступительный тест.

Чтобы узнать больше о формате обучения и познакомиться с преподавателями, приходите на бесплатные демо-уроки:

  • 14 января 20:00. «Шпаргалка по проектированию REST API». Записаться

  • 21 января 20:00. «Мониторинг: как понять, что твой сервис болен». Записаться

  • 29 января 20:00. «CI/CD: 90 минут от платформы до конвейера». Записаться

Комментарии (1)


  1. SergeyMi
    14.01.2026 10:12

    Теперь флаг MY_SPECIAL_FEATURE включится лишь тогда, когда пользователь Alice зашёл после наступления нового года 2026. Если зайдёт другой пользователь после 1 января, не сработает (требуется vanya). Если vanya зайдёт раньше даты, тоже не сработает (дата не выполнена). А вот когда оба условия станут истинны, флаг загорится зелёным.

    Странно, причем тут пользователь Alice ?

    это видимо описка...