How to Use Conditional Availability¶
Implementations can be conditionally available based on permissions, feature flags, settings, or arbitrary callables.
ConditionalInterface¶
Subclass ConditionalInterface instead of Interface and set the condition class attribute:
from django_stratagem import ConditionalInterface, PermissionCondition
class AdminNotification(ConditionalInterface):
registry = NotificationRegistry
slug = "admin_only"
description = "Admin-only notification channel"
condition = PermissionCondition("myapp.admin_notifications")
def send(self, message, recipient):
...
The is_available(context) classmethod checks whether the implementation’s conditions are satisfied:
context = {"user": request.user, "request": request}
AdminNotification.is_available(context) # True/False
Condition Base Class¶
All conditions extend Condition and implement is_met(context) -> bool:
from django_stratagem import Condition
class MyCondition(Condition):
def is_met(self, context: dict) -> bool:
return context.get("some_key") == "some_value"
def explain(self) -> str:
return "MyCondition(some_key=some_value)"
Additional methods:
explain() -> str- Human-readable descriptioncheck_with_details(context) -> tuple[bool, str]- Returns result with explanation (useful for debugging)
Built-in Conditions¶
PermissionCondition¶
Checks user.has_perm() against a Django permission string.
PermissionCondition("myapp.can_send_sms")
The context dict must include a "user" key with an authenticated user.
FeatureFlagCondition¶
Checks a feature flag. Supports settings.FEATURE_FLAGS dict or django-waffle.
FeatureFlagCondition("enable_push_notifications")
With waffle, the context dict must include a "request" key.
SettingCondition¶
Checks if a Django setting matches an expected value.
SettingCondition("DEBUG", True)
SettingCondition("NOTIFICATION_BACKEND", "production")
CallableCondition¶
Wraps any (context) -> bool callable.
CallableCondition(lambda ctx: ctx.get("user") and ctx["user"].is_staff)
AuthenticatedCondition¶
Checks that the user in context is authenticated. Works safely with AnonymousUser, mock users, or missing user keys.
AuthenticatedCondition()
The context dict must include a "user" key.
StaffCondition¶
Checks that the user is a staff member (is_staff).
StaffCondition()
SuperuserCondition¶
Checks that the user is a superuser (is_superuser).
SuperuserCondition()
GroupCondition¶
Checks that the user belongs to a specific Django auth group.
GroupCondition("editors")
Calls user.groups.filter(name=...).exists() internally.
TimeWindowCondition¶
Checks if the current local time falls within a window. Handles overnight windows (e.g. 22:00-06:00) and optional day-of-week filtering using Python weekday convention (0=Monday, 6=Sunday).
from datetime import time
# Business hours, weekdays only
TimeWindowCondition(time(9, 0), time(17, 0), days=[0, 1, 2, 3, 4])
# Overnight maintenance window, every day
TimeWindowCondition(time(2, 0), time(5, 0))
DateRangeCondition¶
Checks if the current local date is within a range (inclusive on both ends). Either bound can be None for an open-ended range.
from datetime import date
# Available only during Q1 2026
DateRangeCondition(date(2026, 1, 1), date(2026, 3, 31))
# Available from launch date onward
DateRangeCondition(start_date=date(2026, 6, 1))
# Available until sunset date
DateRangeCondition(end_date=date(2026, 12, 31))
EnvironmentCondition¶
Checks an environment variable. If expected_value is not provided, checks that the variable exists and is non-empty. If provided, checks for an exact string match.
# Just check that it's set
EnvironmentCondition("FEATURE_X_ENABLED")
# Check exact value
EnvironmentCondition("DEPLOY_ENV", "production")
Composing Conditions¶
Conditions support & (AND), | (OR), and ~ (NOT) operators:
from django_stratagem import PermissionCondition, FeatureFlagCondition
# Must have permission AND feature flag enabled
condition = PermissionCondition("myapp.send") & FeatureFlagCondition("notifications_v2")
# Either permission OR staff status
condition = PermissionCondition("myapp.send") | CallableCondition(lambda ctx: ctx.get("user", None) and ctx["user"].is_staff)
# NOT a condition
condition = ~SettingCondition("MAINTENANCE_MODE", True)
Compound conditions can also be created directly:
from django_stratagem import AllConditions, AnyCondition, NotCondition
AllConditions([cond1, cond2, cond3]) # All must pass
AnyCondition([cond1, cond2, cond3]) # At least one must pass
NotCondition(cond1) # Must fail
Context-Aware Registry Methods¶
Registries containing ConditionalInterface subclasses have extra methods that filter by a context dict:
context = {"user": request.user, "request": request}
# Get only available implementations
available = NotificationRegistry.get_available_implementations(context)
# {"email": <class EmailNotification>, ...}
# Get choices filtered by context
choices = NotificationRegistry.get_choices_for_context(context)
# [("email", "Email Notification"), ...]
# Get implementation with context check and fallback
impl = NotificationRegistry.get_for_context(
context,
slug="admin_only",
fallback="email",
)
Writing Custom Conditions¶
Database-Backed Conditions¶
Check a database value, like whether a tenant’s plan includes a feature:
from django_stratagem import Condition
class TenantPlanCondition(Condition):
def __init__(self, required_plan):
self.required_plan = required_plan
def is_met(self, context):
tenant = context.get("tenant")
if not tenant:
return False
# Assumes tenant has a .plan attribute
return tenant.plan in self.required_plan
def explain(self):
return f"Tenant plan must be one of: {self.required_plan}"
Usage:
class PremiumExport(ConditionalInterface):
registry = ExportRegistry
slug = "premium_export"
condition = TenantPlanCondition(["business", "enterprise"])
Combining Custom and Built-in Conditions¶
Conditions compose with &, |, and ~:
from django_stratagem import PermissionCondition, FeatureFlagCondition
class EnterpriseExport(ConditionalInterface):
registry = ExportRegistry
slug = "enterprise_export"
condition = (
TenantPlanCondition(["enterprise"])
& PermissionCondition("exports.use_enterprise")
& FeatureFlagCondition("enterprise_exports_enabled")
)