Django Integration
toggly-django connects a shared definition client to Django's authenticated request, view decorators, and templates.
See the Python Django SDK Sample for a runnable workshop covering native gates, session identity, Order context, and the eleven-filter matrix.
Installation
pip install toggly-django
Configuration
Add the application and middleware to your existing settings. TogglyConfig.ready() reads the TOGGLY dictionary and initializes the client at application startup. Keep authentication middleware before Toggly so the request has its user before evaluation.
# settings.py: add these entries to your existing Django settings.
import os
INSTALLED_APPS += ["toggly_django"]
MIDDLEWARE += ["toggly_django.middleware.TogglyMiddleware"]
TOGGLY = {
"APP_KEY": os.environ.get("TOGGLY_APP_KEY"),
"ENVIRONMENT": "Production",
"FEATURE_DEFAULTS": {"new-dashboard": False},
"REFRESH_INTERVAL": 180.0,
}
Set TOGGLY_APP_KEY in your process environment to your application's key. With no key, the local defaults let you start the app; they do not fetch dashboard definitions. Create new-dashboard in that application/environment, enable it, and revisit the route after the next refresh. Keep the existing authentication and permission checks on your view.
Middleware and views
TogglyMiddleware attaches request.toggly. Its context is created on first use from request.user: the authenticated user's primary key becomes identity, and Django group names become groups. Email, staff status and request metadata are traits.
from django.http import JsonResponse
def dashboard(request):
enabled = request.toggly.is_enabled("new-dashboard", default=False)
return JsonResponse({"version": "v2" if enabled else "v1"})
Map this view in your application's URL configuration:
from django.urls import path
from .views import dashboard
urlpatterns = [path("dashboard/", dashboard, name="dashboard")]
View decorators
Use feature_flag_required to return HTTP 403 when the flag is disabled or the client is unavailable. A redirect_url or fallback_view provides an alternative response. Use raise_exception=True to raise Django's PermissionDenied.
from django.http import JsonResponse
from toggly import FeatureRequirement
from toggly_django.decorators import feature_flag_required, feature_gate_required
def old_dashboard(request):
return JsonResponse({"version": "v1"})
@feature_flag_required("new-dashboard", fallback_view=old_dashboard)
def dashboard(request):
return JsonResponse({"version": "v2"})
@feature_gate_required(["premium", "vip"], requirement=FeatureRequirement.ANY)
def premium_content(request):
return JsonResponse({"access": "premium"})
Class-based views
Apply the same decorator to dispatch:
from django.utils.decorators import method_decorator
from django.views.generic import TemplateView
from toggly_django.decorators import feature_flag_required
@method_decorator(feature_flag_required("new-dashboard"), name="dispatch")
class DashboardView(TemplateView):
template_name = "dashboard.html"
Template tags
Enable APP_DIRS on your Django template backend and include django.template.context_processors.request in its context processors. Render using render(request, ...) so tags can evaluate with the current user and entity.
{% load toggly_tags %}
{% iffeature "new-navigation" %}
<nav>New navigation</nav>
{% endiffeature %}
{% iffeature "new-navigation" negate=True %}
<nav>Legacy navigation</nav>
{% endiffeature %}
The optional negate argument requires toggly-django 0.4.0+. Use the same feature key in both blocks: the positive block renders enabled content, and negate=True renders disabled content. Without a configured client, only the negated block renders. Both retain request context and Django autoescaping.
negate can also be a context expression, such as negate=show_disabled or negate=options.reverse|default:False, resolved on each render. Use boolean literals True and False; a quoted nonempty string is truthy. Feature keys retain their existing literal interpretation, including unquoted keys.
Existing {% else %} branches remain supported for legacy compatibility and render when the final result, after any negation, is false. Prefer paired positive and negated blocks in new templates.
feature_enabled, feature_disabled and feature_gate are simple tags that assign booleans with as:
{% load toggly_tags %}
{% feature_enabled "new-header" as show_header %}
{% if show_header %}<header>New header</header>{% endif %}
{% feature_disabled "maintenance-mode" as operational %}
{% if operational %}<p>System operational</p>{% endif %}
{% feature_gate "premium" "trial" requirement="any" as has_access %}
{% if has_access %}<p>Premium content</p>{% endif %}
Context processor
Add toggly_django.context_processors.toggly_context to your template backend's OPTIONS.context_processors to expose toggly. Use a literal feature key that is valid in Django template variable syntax. For ExpressCheckout, set request.toggly_entity to the current Order before rendering, as shown in Entity context:
{% if toggly.is_enabled.ExpressCheckout %}
<p>Express checkout</p>
{% else %}
<p>Standard checkout</p>
{% endif %}
For hyphenated keys, use a native tag with the quoted literal key:
{% load toggly_tags %}
{% feature_enabled "new-feature" as show_feature %}
{% if show_feature %}
<p>New feature!</p>
{% else %}
<p>Standard feature</p>
{% endif %}
Django tries dictionary lookup before Python attribute lookup. On this helper, new_feature therefore checks the literal new_feature key; it does not select new-feature.
Use is_enabled or the tags for user-specific decisions. toggly.flags exposes the client's flag snapshot, not a fresh evaluation for every key with the current request context.
Entity context
An Order is separate from user identity. Set request.toggly_entity before the first helper evaluation or template render. If a decorator gates on an entity, populate it in middleware before the decorator runs. Here the route uses a fixed demonstration Order; replace it with your authorized domain object's values.
from django.http import JsonResponse
from toggly import TogglyEntityContext
from toggly_django import get_client
def checkout(request):
request.toggly_entity = TogglyEntityContext(
kind="Order", key="order-123", attributes={"Vip": True, "Total": 120.0}
)
helper = request.toggly
enabled = helper.is_enabled("ExpressCheckout")
# Evaluate another Order without changing the cached request context.
other = TogglyEntityContext(
kind="Order", key="order-456", attributes={"Vip": False, "Total": 20.0}
)
client = get_client()
other_enabled = client.is_enabled(
"ExpressCheckout", context=helper.context.with_entity(other)
) if client is not None else False
return JsonResponse({"express": enabled, "other_express": other_enabled})
Configure ExpressCheckout with an Order Context Property condition Vip == true to see opposite results. The per-call context preserves the same user's identity and groups. Changing request.toggly_entity after the helper first reads its context does not replace that cached context. See Python entity mapping for registering domain mappers and optional startup schema registration.
Claims and request filters
Native extraction supplies identity, groups, traits and the optional entity. Claims and structured browser/language/country fields need an explicit core context. The helper's is_enabled accepts a key and default; call the core client when supplying a custom context.
from toggly import HttpRequestMapper
from toggly_django import get_client, get_context_from_request
def browser_offer(request):
context = HttpRequestMapper.merge_into(
request.headers, get_context_from_request(request)
)
client = get_client()
return client.is_enabled("browser-offer", context=context) if client else False
Only trust country headers set by your trusted proxy. Add authenticated claims with context.with_claims(...); arbitrary traits do not populate UserClaims or request segment filters. Build each user's context per request and keep the shared client's identity unchanged. Remote variants use a separate client-wide startup context.
Testing and lifecycle
For offline application tests, configure FEATURE_DEFAULTS, omit APP_KEY, set DISABLE_BACKGROUND_REFRESH=True and ENABLE_USAGE_TRACKING=False in TOGGLY before Django starts. Test both enabled and disabled routes with Django's test client and render templates with a request. For targeting tests, use real definitions in a snapshot provider and alternate authenticated users and Orders.
The integration uses synchronous client methods and synchronous view decorators. In async views, run synchronous Django operations using sync_to_async; use the core async client directly for an independently owned async lifecycle. Close get_client() once at worker shutdown or fixture teardown, not after each request.
Learning path
Start with the dashboard view, then the decorator, template, and two-Order example above. Browse the Samples catalog for runnable projects and their setup instructions. To inspect the Python integration, read middleware, utilities, decorators and template tags.