Builder Pattern in Django-CFG
The Builder Pattern is a core architectural pattern in Django-CFG that separates the construction of complex configuration objects from their representation.
Overview
Django-CFG uses dedicated builder classes to construct different parts of Django settings from configuration models. This approach provides:
- Single Responsibility: Each builder focuses on one aspect of settings
- Testability: Builders can be tested independently
- Extensibility: Easy to add custom builders
- Maintainability: Changes isolated to specific builders
Core Builders
InstalledAppsBuilder
Builds the INSTALLED_APPS list from your DjangoConfig.
Location: django_cfg.core.builders.apps_builder
Responsibilities:
- Combine default Django apps with third-party apps
- Add django-cfg apps based on feature flags
- Handle special ordering requirements
- Auto-enable dependencies
- Remove duplicates while preserving order
Example:
from django_cfg.core.builders import InstalledAppsBuilder
# Inside ConfigService
builder = InstalledAppsBuilder(config)
apps = builder.build()Key Features:
-
Accounts Before Admin: Automatically inserts
django_cfg.apps.accountsbeforedjango.contrib.adminfor proper migration order -
Auto-Enable Tasks: Automatically adds
django_rearqwhen knowbase or agents are enabled
MiddlewareBuilder
Builds the MIDDLEWARE list from your DjangoConfig.
Location: django_cfg.core.builders.middleware_builder
Responsibilities:
- Start with Django’s default middleware stack
- Insert CORS middleware at correct position
- Add feature-specific middleware
- Add custom user middleware
- Maintain proper ordering
Example:
from django_cfg.core.builders import MiddlewareBuilder
builder = MiddlewareBuilder(config)
middleware = builder.build()Key Features:
-
Smart CORS Insertion: Places CORS middleware after WhiteNoise but before Session
[ "whitenoise.middleware.WhiteNoiseMiddleware", "corsheaders.middleware.CorsMiddleware", # ← Inserted here "django.contrib.sessions.middleware.SessionMiddleware", ... ] -
Feature Middleware: Automatically adds middleware for enabled features
UserActivityMiddlewarewhen accounts enabled- Payment middleware when payments configured
SecurityBuilder
Builds security-related settings like ALLOWED_HOSTS.
Location: django_cfg.core.builders.security_builder
Responsibilities:
- Generate
ALLOWED_HOSTSfromsecurity_domains - Add localhost in development
- Handle wildcard domains
- Auto-add www subdomains
Example:
from django_cfg.core.builders import SecurityBuilder
builder = SecurityBuilder(config)
allowed_hosts = builder.build_allowed_hosts()Key Features:
-
Environment-Aware Defaults:
# Development security_domains = [] → ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]"] # Production with domains security_domains = ["example.com"] → ALLOWED_HOSTS = ["example.com", "www.example.com"] -
Production Warning: Issues warning if no domains configured in production
Usage Pattern
Direct Usage
from django_cfg import DjangoConfig
from django_cfg.core.builders import InstalledAppsBuilder
config = DjangoConfig(project_name="MyProject", ...)
builder = InstalledAppsBuilder(config)
apps = builder.build()Through ConfigService
The recommended approach is through ConfigService:
from django_cfg import DjangoConfig
config = DjangoConfig(project_name="MyProject", ...)
# ConfigService automatically uses builders
apps = config.get_installed_apps() # Uses InstalledAppsBuilder internally
middleware = config.get_middleware() # Uses MiddlewareBuilder internally
hosts = config.get_allowed_hosts() # Uses SecurityBuilder internallyCreating Custom Builders
You can create custom builders for your own configuration needs:
from typing import List, TYPE_CHECKING
if TYPE_CHECKING:
from django_cfg.core.base.config_model import DjangoConfig
class CustomSettingsBuilder:
"""Build custom settings from config."""
def __init__(self, config: "DjangoConfig"):
self.config = config
def build(self) -> dict:
"""Build custom settings."""
settings = {}
# Your custom logic here
if self.config.is_production:
settings["CUSTOM_FEATURE"] = True
return settingsIntegration with Generators
Builders feed into generators:
DjangoConfig
↓
Builders (construct components)
↓
Generators (create settings)
↓
Complete Django SettingsExample:
# Builder creates INSTALLED_APPS list
apps_builder = InstalledAppsBuilder(config)
installed_apps = apps_builder.build()
# Generator uses the list in settings
settings = {
"INSTALLED_APPS": installed_apps,
...
}Testing Builders
Builders are designed to be easily testable:
import pytest
from django_cfg import DjangoConfig
from django_cfg.core.builders import InstalledAppsBuilder
def test_installed_apps_includes_accounts_before_admin():
config = DjangoConfig(
project_name="Test",
secret_key="x" * 50,
enable_accounts=True,
databases={"default": {...}},
)
builder = InstalledAppsBuilder(config)
apps = builder.build()
accounts_index = apps.index("django_cfg.apps.accounts")
admin_index = apps.index("django.contrib.admin")
assert accounts_index < admin_index, "Accounts must come before admin"
def test_tasks_auto_enabled_with_knowbase():
config = DjangoConfig(
project_name="Test",
secret_key="x" * 50,
enable_knowbase=True,
databases={"default": {...}},
)
builder = InstalledAppsBuilder(config)
apps = builder.build()
assert "django_rearq" in apps
assert "django_cfg.apps.tasks" in appsBest Practices
1. Single Responsibility
Each builder should focus on one aspect of configuration:
- ✅
InstalledAppsBuilder- only builds apps list - ✅
MiddlewareBuilder- only builds middleware list - ❌ Don’t mix concerns
2. Immutability
Builders should not modify the config:
# ✅ Good - read from config
if self.config.enable_accounts:
apps.append("django_cfg.apps.accounts")
# ❌ Bad - don't modify config
self.config.enable_accounts = True3. Deterministic Output
Same config should always produce same output:
# ✅ Good - deterministic
apps = ["app1", "app2", "app3"]
# ❌ Bad - random or time-dependent
import random
apps = random.sample(all_apps, 5)4. Error Handling
Provide clear error messages:
if not self.config.databases:
raise ConfigurationError(
"At least one database must be configured",
suggestions=["Add a 'default' database"],
)Comparison with Other Patterns
Builder vs Factory
Builder Pattern (Django-CFG):
- Constructs complex objects step-by-step
- Same builder can create different representations
- Focuses on HOW to build
Factory Pattern:
- Creates objects in one step
- Different factories for different types
- Focuses on WHAT to build
Builder vs Template Method
Builder (Django-CFG):
- Multiple classes for different aspects
- Composition over inheritance
- More flexible
Template Method:
- Single class with hooks
- Inheritance-based
- More rigid structure
See Also
- Generator Pattern - How builders feed into generators
- Architecture - Overall system architecture
- Configuration - Configuration model reference