Skip to Content
DocumentationGuides & ExamplesSample ProjectConfiguration

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:

  1. Type-safe Configuration (api/config.py) - Main configuration class
  2. Environment Loader (api/environment/loader.py) - Pydantic models with BaseSettings
  3. Environment Variables - System ENV or .env file
  4. 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 production

Database 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=-1001234567890

See 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):

api/environment/.env
# === 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 development

Benefits 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.):

Production Environment Variables
# 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:

Test Environment (pytest.ini or CI)
# === 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:

  1. Checks system environment variables (highest priority)
  2. Loads .env file if exists
  3. Uses defaults from Field definitions
  4. Validates types automatically with Pydantic

Environment Variables

Setting Environment Variables

Development (local):

# In .env file (gitignored) DATABASE__URL="postgresql://localhost:5432/dev_db" DEBUG=true

Production (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-key

Variable 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_key

Accessing 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.databases

Configuration Commands

Check your configuration:

# Show all configuration python manage.py show_config # Validate configuration python manage.py check # Check settings python manage.py check_settings

Migration from YAML

Migrating from YAML configs? Old approach used config.dev.yaml, config.prod.yaml, etc.

New approach: Everything via ENV variables!

Before (YAML)

config.prod.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)

.env or system 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

Proper configuration management is essential for maintainable Django-CFG applications!