Configuration Setup
The Django-CFG sample project demonstrates best practices for configuration management using pydantic-settings for type-safe environment-based configuration. This guide covers the complete configuration architecture.
Configuration Architecture
The sample project uses a simple, modern configuration approach:
- Type-safe Configuration (
api/config.py) - Main configuration class - Environment Loader (
api/environment/loader.py) - Pydantic models with BaseSettings - Environment Variables - System ENV or
.envfile - Django Integration - Seamless settings generation
Priority: System ENV > .env file > Code defaults
Main Configuration (api/config.py)
The heart of the project is the type-safe configuration class:
from django_cfg import (
DjangoConfig,
DatabaseConfig, EmailConfig,
TelegramConfig, UnfoldConfig, DRFConfig
)
from typing import Dict
from .environment import env
class SampleProjectConfig(DjangoConfig):
"""Complete Django-CFG sample configuration."""
# Project metadata
project_name: str = env.app.name
debug: bool = env.debug
secret_key: str = env.secret_key
env_mode: str = env.env.env_mode
# URL Configuration
api_url: str = env.app.api_url
site_url: str = env.app.site_url
media_url: str = env.app.media_url # CDN or "__auto__" for api_url + /media/
# Multi-database configuration
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig.from_url(
url=env.database.url,
# Main database for users, sessions, admin
),
}
# Email configuration
email: EmailConfig | None = (
EmailConfig(
host=env.email.host,
port=env.email.port,
use_tls=env.email.use_tls,
username=env.email.username,
password=env.email.password,
default_from=env.email.default_from,
)
if env.email.host
else None
)
# Telegram notifications
telegram: TelegramConfig | None = (
TelegramConfig(
bot_token=env.telegram.bot_token,
chat_id=env.telegram.chat_id,
)
if env.telegram.bot_token and env.telegram.chat_id
else None
)
# Modern admin interface
unfold: UnfoldConfig = UnfoldConfig(
site_title="Django-CFG Sample Admin",
site_header="Django-CFG Sample",
navigation=[
{
"title": "Content Management",
"items": [
{"title": "Profiles", "link": "/admin/profiles/profile/"},
]
},
{
"title": "User Management",
"items": [
{"title": "Users", "link": "/admin/auth/user/"},
]
}
]
)
# API configuration
drf: DRFConfig = DRFConfig(
default_authentication_classes=[
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
default_permission_classes=[
"rest_framework.permissions.IsAuthenticated",
],
spectacular_settings={
"TITLE": "Django-CFG Sample API",
"DESCRIPTION": "Complete API for Django-CFG sample project",
"VERSION": "1.0.0",
"SERVE_INCLUDE_SCHEMA": False,
}
)
# Create global config instance
config = SampleProjectConfig()Configuration Components
Project Metadata
Basic project information loaded from environment:
project_name: str = env.app.name # From APP__NAME
debug: bool = env.debug # From DEBUG
secret_key: str = env.secret_key # From SECRET_KEY
env_mode: str = env.env.env_mode # "development", "production", or "test"URL Configuration
URL settings for API, frontend, and media files:
api_url: str = env.app.api_url # From APP__API_URL (default: http://localhost:8000)
site_url: str = env.app.site_url # From APP__SITE_URL (default: http://localhost:3000)
media_url: str = env.app.media_url # From APP__MEDIA_URL (default: /media/)Media URL options:
"/media/"- Relative URL (default, served by Django)"__auto__"- Auto-generate from api_url (e.g.,http://localhost:8000/media/)"https://cdn.example.com/media/"- Absolute URL for CDN/external storage
Set via environment:
APP__API_URL="https://api.example.com"
APP__SITE_URL="https://example.com"
APP__MEDIA_URL="https://cdn.example.com/media/" # CDN for productionDatabase Configuration
Database setup using environment URL:
databases: Dict[str, DatabaseConfig] = {
"default": DatabaseConfig.from_url(
url=env.database.url, # From DATABASE__URL
),
}Set via environment:
DATABASE__URL="postgresql://user:pass@localhost:5432/djangocfg"See Multi-Database Setup for detailed routing configuration.
Service Configurations
Email and notification services from environment:
# Email (console for dev, SMTP for prod)
email: EmailConfig | None = (
EmailConfig(
host=env.email.host, # From EMAIL__HOST
port=env.email.port, # From EMAIL__PORT
use_tls=env.email.use_tls, # From EMAIL__USE_TLS
username=env.email.username, # From EMAIL__USERNAME
password=env.email.password, # From EMAIL__PASSWORD
)
if env.email.host
else None
)
# Notifications (Telegram)
telegram: TelegramConfig | None = (
TelegramConfig(
bot_token=env.telegram.bot_token, # From TELEGRAM__BOT_TOKEN
chat_id=env.telegram.chat_id, # From TELEGRAM__CHAT_ID
)
if env.telegram.bot_token
else None
)Set via environment:
EMAIL__HOST="smtp.gmail.com"
EMAIL__PORT=587
TELEGRAM__BOT_TOKEN="123456:ABC-DEF"
TELEGRAM__CHAT_ID=-1001234567890See Service Integrations for service setup details.
Admin Interface Configuration
Unfold theme customization:
unfold: UnfoldConfig = UnfoldConfig(
site_title="Django-CFG Sample Admin",
navigation=[...],
)See Admin Interface for customization options.
API Configuration
Django REST Framework settings:
drf: DRFConfig = DRFConfig(
default_authentication_classes=[
"rest_framework_simplejwt.authentication.JWTAuthentication",
],
spectacular_settings={
"TITLE": "Django-CFG Sample API",
"VERSION": "1.0.0",
}
)See API Documentation for API configuration.
Environment Configuration
Development Configuration (.env)
Development-specific settings in .env file (gitignored):
# === Environment Mode ===
IS_DEV=true
# === Core Django Settings ===
SECRET_KEY="django-cfg-dev-key-change-in-production-min-50-chars"
DEBUG=true
# === Database ===
DATABASE__URL="postgresql://postgres:postgres@localhost:5432/djangocfg"
# === Cache ===
REDIS_URL="redis://localhost:6379/0"
# === Email Configuration ===
EMAIL__BACKEND="console"
EMAIL__DEFAULT_FROM="Django CFG Sample <[email protected]>"
# === Telegram (optional) ===
# TELEGRAM__BOT_TOKEN="your-bot-token"
# TELEGRAM__CHAT_ID=0
# === API Keys (optional) ===
# API_KEYS__OPENROUTER="sk-or-xxx"
# API_KEYS__OPENAI="sk-proj-xxx"
# === Application ===
APP__NAME="Django CFG Sample"
APP__API_URL="http://localhost:8000"
APP__SITE_URL="http://localhost:3000"
APP__MEDIA_URL="/media/" # Relative URL for local developmentBenefits for development:
- ✅ Console email backend (prints to terminal)
- ✅ PostgreSQL or SQLite (your choice)
- ✅ Local domains for CORS
- ✅ Gitignored - safe for local secrets
Production Configuration (ENV)
Production uses system environment variables (Docker, K8s, etc.):
# Set in Docker/K8s - NEVER in .env file!
# === Environment Mode ===
IS_PROD=true
# === Core Django Settings ===
SECRET_KEY="production-secret-key-from-secrets-manager-min-50-chars"
DEBUG=false
# === Database ===
DATABASE__URL="postgresql://prod_user:[email protected]:5432/prod_db"
# === Cache ===
REDIS_URL="redis://redis:6379/1"
# === Email Configuration ===
EMAIL__BACKEND="smtp"
EMAIL__HOST="smtp.sendgrid.net"
EMAIL__PORT=587
EMAIL__USERNAME="apikey"
EMAIL__PASSWORD="SG.xxxxxxxxxxxx"
EMAIL__USE_TLS=true
EMAIL__DEFAULT_FROM="Django CFG Sample <[email protected]>"
# === Application ===
APP__NAME="Django CFG Sample"
APP__DOMAIN="djangocfg.com"
APP__API_URL="https://api.djangocfg.com"
APP__SITE_URL="https://djangocfg.com"
APP__MEDIA_URL="https://cdn.djangocfg.com/media/" # CDN for media files
# === Security Domains ===
SECURITY_DOMAINS="djangocfg.com,api.djangocfg.com,admin.djangocfg.com"
# === Telegram ===
TELEGRAM__BOT_TOKEN="1234567890:ABCdefGHIjklMNOpqrsTUVwxyz"
TELEGRAM__CHAT_ID=-1001234567890
# === API Keys ===
API_KEYS__OPENROUTER="sk-or-xxx"
API_KEYS__OPENAI="sk-proj-xxx"Security practices:
- ✅ Use secrets managers (AWS Secrets Manager, Vault)
- ✅ Set in Docker environment or K8s Secrets
- ✅ Never commit to version control
- ✅ Rotate secrets regularly
Test Configuration
Testing uses defaults optimized for speed:
# === Environment Mode ===
IS_TEST=true
# === Core Settings ===
SECRET_KEY="test-key-for-testing-only-min-50-chars-long"
DEBUG=false
# === Database (in-memory for speed) ===
DATABASE__URL="sqlite:///:memory:"
# === Email (don't send real emails) ===
EMAIL__BACKEND="console"
# === Disable external services ===
TELEGRAM__BOT_TOKEN=""
API_KEYS__OPENROUTER=""Optimizations:
- ✅ In-memory SQLite for fast tests
- ✅ Console email backend
- ✅ Disabled external services
- ✅ Minimal logging
Configuration Loader
The environment loader uses pydantic-settings for automatic loading:
# api/environment/loader.py
from pathlib import Path
from typing import Optional
from pydantic import Field, computed_field, model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class DatabaseConfig(BaseSettings):
"""Database configuration."""
url: str = Field(default="sqlite:///db/default.sqlite3")
model_config = SettingsConfigDict(
env_prefix="DATABASE__",
env_nested_delimiter="__",
)
class EmailConfig(BaseSettings):
"""Email configuration."""
backend: str = Field(default="console")
host: str = Field(default="localhost")
port: int = Field(default=587)
username: Optional[str] = Field(default=None)
password: Optional[str] = Field(default=None)
use_tls: bool = Field(default=True)
default_from: str = Field(default="[email protected]")
model_config = SettingsConfigDict(
env_prefix="EMAIL__",
env_nested_delimiter="__",
)
class EnvironmentMode(BaseSettings):
"""Environment mode detection."""
is_test: bool = Field(default=False)
is_dev: bool = Field(default=False)
is_prod: bool = Field(default=False)
@model_validator(mode="after")
def set_default_env(self):
if not any([self.is_test, self.is_dev, self.is_prod]):
self.is_dev = True
return self
@computed_field
@property
def env_mode(self) -> str:
if self.is_test:
return "test"
elif self.is_prod:
return "production"
return "development"
model_config = SettingsConfigDict(
env_nested_delimiter="__",
)
class EnvironmentConfig(BaseSettings):
"""Complete environment configuration."""
secret_key: str = Field(
default="django-cfg-dev-key-change-in-production-min-50-chars"
)
debug: bool = Field(default=True)
database: DatabaseConfig = Field(default_factory=DatabaseConfig)
email: EmailConfig = Field(default_factory=EmailConfig)
env: EnvironmentMode = Field(default_factory=EnvironmentMode)
model_config = SettingsConfigDict(
env_file=str(Path(__file__).parent / ".env"),
env_file_encoding="utf-8",
env_nested_delimiter="__",
case_sensitive=False,
extra="ignore",
)
# Global instance - auto-loads from ENV > .env > defaults
env = EnvironmentConfig()How it works:
- Checks system environment variables (highest priority)
- Loads
.envfile if exists - Uses defaults from Field definitions
- Validates types automatically with Pydantic
Environment Variables
Setting Environment Variables
Development (local):
# In .env file (gitignored)
DATABASE__URL="postgresql://localhost:5432/dev_db"
DEBUG=trueProduction (Docker):
# docker-compose.yml
services:
django:
environment:
DATABASE__URL: "postgresql://prod-db:5432/db"
SECRET_KEY: "${SECRET_KEY}"
IS_PROD: "true"Production (Kubernetes):
# k8s-deployment.yaml
env:
- name: DATABASE__URL
valueFrom:
secretKeyRef:
name: django-secrets
key: database-url
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: django-secrets
key: secret-keyVariable Naming
Use double underscore (__) for nested configurations:
# Top-level
DEBUG=true
SECRET_KEY="my-secret"
# Nested: database.url
DATABASE__URL="postgresql://..."
# Nested: email.host
EMAIL__HOST="smtp.gmail.com"
# Nested: api_keys.openai
API_KEYS__OPENAI="sk-proj-xxx"Configuration Best Practices
1. Use Type-Safe Configuration
Always define configuration in type-safe classes:
# ✅ Good: Type-safe with validation
class MyConfig(DjangoConfig):
email: EmailConfig = EmailConfig(
host=env.email.host,
port=env.email.port,
)
# ❌ Bad: Raw dictionary
settings = {
"email_host": "smtp.example.com",
"email_port": 587
}2. Environment-Specific Settings
Use environment variables for all environments:
# ✅ Good: Environment variables
DATABASE__URL="postgresql://..." # Dev, prod, test
# ❌ Bad: Inline conditions
if DEBUG:
DATABASE = 'sqlite:///db.sqlite3'
else:
DATABASE = 'postgresql://...'3. Sensitive Data Management
Never commit sensitive data:
# ✅ Good: In .env file (gitignored)
SECRET_KEY="my-secret-key-min-50-chars"
EMAIL__PASSWORD="my-password"
# ✅ Good: In .gitignore
.env
.env.local
environment/.env
# ❌ Bad: Hardcoded in code
SECRET_KEY = "hardcoded-secret" # Never do this!4. Configuration Documentation
Document all configuration options:
class MyConfig(DjangoConfig):
"""Project configuration.
Environment Variables:
DEBUG: Enable debug mode (default: True)
SECRET_KEY: Django secret key (required in production)
DATABASE__URL: Database connection URL
EMAIL__HOST: SMTP server hostname
Example:
export DEBUG=false
export SECRET_KEY="prod-secret-key"
export DATABASE__URL="postgresql://..."
"""
debug: bool = env.debug
secret_key: str = env.secret_keyAccessing Configuration
Use the configuration instance throughout your project:
# Import configuration
from api.config import config
# Access settings
project_name = config.project_name
is_debug = config.debug
# Access service configs
email_host = config.email.host if config.email else None
telegram_enabled = config.telegram is not None
# Access database configs
databases = config.databasesConfiguration Commands
Check your configuration:
# Show all configuration
python manage.py show_config
# Validate configuration
python manage.py check
# Check settings
python manage.py check_settingsMigration from YAML
Migrating from YAML configs?
Old approach used config.dev.yaml, config.prod.yaml, etc.
New approach: Everything via ENV variables!
Before (YAML)
secret_key: "my-secret-key"
debug: false
database:
url: "postgresql://user:pass@localhost:5432/db"
email:
backend: "smtp"
host: "smtp.example.com"After (ENV)
SECRET_KEY="my-secret-key"
DEBUG=false
DATABASE__URL="postgresql://user:pass@localhost:5432/db"
EMAIL__BACKEND="smtp"
EMAIL__HOST="smtp.example.com"Benefits:
- ✅ Simpler - one method for all environments
- ✅ 12-factor app compliant
- ✅ Works everywhere (Docker, K8s, CI/CD)
- ✅ No file management
Related Topics
- Multi-Database Setup - Database routing configuration
- Service Integrations - Service configuration details
- Deployment Guide - Production configuration
- Environment Configuration - Detailed ENV guide
Proper configuration management is essential for maintainable Django-CFG applications!