Django settings configuration is crucial for managing different environments, security, and application behavior. This comprehensive guide covers settings organization, environment management, and best practices for maintainable Django projects.
Default Structure:
# settings.py
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-your-secret-key-here'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'myproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'myproject.wsgi.application'
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Internationalization
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
STATIC_URL = 'static/'
# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
Settings Package Structure:
myproject/
├── settings/
│ ├── __init__.py
│ ├── base.py # Common settings
│ ├── development.py # Development environment
│ ├── staging.py # Staging environment
│ ├── production.py # Production environment
│ └── testing.py # Testing environment
├── urls.py
├── wsgi.py
└── asgi.py
Base Settings (settings/base.py):
import os
from pathlib import Path
from decouple import config, Csv
# Build paths
BASE_DIR = Path(__file__).resolve().parent.parent.parent
# Security
SECRET_KEY = config('SECRET_KEY')
# Application definition
DJANGO_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
THIRD_PARTY_APPS = [
'rest_framework',
'corsheaders',
'django_extensions',
]
LOCAL_APPS = [
'accounts',
'blog',
'api',
]
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # Static files
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'myproject.urls'
# Templates
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
# WSGI/ASGI
WSGI_APPLICATION = 'myproject.wsgi.application'
ASGI_APPLICATION = 'myproject.asgi.application'
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
LANGUAGE_CODE = config('LANGUAGE_CODE', default='en-us')
TIME_ZONE = config('TIME_ZONE', default='UTC')
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# Media files
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# Default primary key field type
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Custom user model (if applicable)
# AUTH_USER_MODEL = 'accounts.User'
# Email configuration (base settings)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = config('EMAIL_HOST', default='localhost')
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
DEFAULT_FROM_EMAIL = config('DEFAULT_FROM_EMAIL', default='noreply@example.com')
# Logging configuration
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
'style': '{',
},
'simple': {
'format': '{levelname} {message}',
'style': '{',
},
},
'handlers': {
'file': {
'level': 'INFO',
'class': 'logging.FileHandler',
'filename': BASE_DIR / 'logs' / 'django.log',
'formatter': 'verbose',
},
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
},
'root': {
'handlers': ['console'],
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'INFO',
'propagate': False,
},
'myproject': {
'handlers': ['file', 'console'],
'level': 'DEBUG',
'propagate': False,
},
},
}
Development Settings (settings/development.py):
from .base import *
# Debug mode
DEBUG = True
# Allowed hosts
ALLOWED_HOSTS = ['localhost', '127.0.0.1', '0.0.0.0']
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Alternative: PostgreSQL for development
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql',
# 'NAME': config('DB_NAME', default='myproject_dev'),
# 'USER': config('DB_USER', default='postgres'),
# 'PASSWORD': config('DB_PASSWORD', default=''),
# 'HOST': config('DB_HOST', default='localhost'),
# 'PORT': config('DB_PORT', default='5432'),
# }
# }
# Development-specific apps
INSTALLED_APPS += [
'debug_toolbar',
'django_extensions',
]
# Development middleware
MIDDLEWARE += [
'debug_toolbar.middleware.DebugToolbarMiddleware',
]
# Debug toolbar configuration
DEBUG_TOOLBAR_CONFIG = {
'SHOW_TOOLBAR_CALLBACK': lambda request: True,
'SHOW_COLLAPSED': True,
}
# Email backend for development
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Cache (development)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
# Disable HTTPS redirects in development
SECURE_SSL_REDIRECT = False
# CORS settings for development
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True
# Static files (development)
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
# Logging (development)
LOGGING['loggers']['django']['level'] = 'DEBUG'
LOGGING['loggers']['myproject']['level'] = 'DEBUG'
Production Settings (settings/production.py):
from .base import *
import dj_database_url
# Security
DEBUG = False
SECRET_KEY = config('SECRET_KEY')
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=Csv())
# Database
DATABASES = {
'default': dj_database_url.config(
default=config('DATABASE_URL')
)
}
# Security settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_HSTS_SECONDS = 31536000
SECURE_REDIRECT_EXEMPT = []
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = 'DENY'
# Static files (production)
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
# Cache (production)
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': config('REDIS_URL', default='redis://127.0.0.1:6379/1'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# Session engine
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
# Email (production)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# Logging (production)
LOGGING['handlers']['file']['filename'] = '/var/log/django/myproject.log'
LOGGING['loggers']['django']['level'] = 'WARNING'
LOGGING['loggers']['myproject']['level'] = 'INFO'
# Error reporting
ADMINS = [
('Admin', config('ADMIN_EMAIL', default='admin@example.com')),
]
MANAGERS = ADMINS
# Performance optimizations
CONN_MAX_AGE = 60 # Database connection pooling
Testing Settings (settings/testing.py):
from .base import *
# Test database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
}
}
# Disable migrations for faster tests
class DisableMigrations:
def __contains__(self, item):
return True
def __getitem__(self, item):
return None
MIGRATION_MODULES = DisableMigrations()
# Password hashers (faster for tests)
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.MD5PasswordHasher',
]
# Email backend for testing
EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
# Cache (testing)
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
}
}
# Disable logging during tests
LOGGING_CONFIG = None
# Media files (testing)
MEDIA_ROOT = '/tmp/test_media'
# Celery (testing)
CELERY_TASK_ALWAYS_EAGER = True
CELERY_TASK_EAGER_PROPAGATES = True
Installation:
pip install python-decouple
.env File:
# .env
SECRET_KEY=your-super-secret-key-here
DEBUG=True
DATABASE_URL=postgresql://user:password@localhost:5432/myproject_dev
ALLOWED_HOSTS=localhost,127.0.0.1
EMAIL_HOST=smtp.gmail.com
EMAIL_HOST_USER=your-email@gmail.com
EMAIL_HOST_PASSWORD=your-app-password
REDIS_URL=redis://localhost:6379/0
Usage in Settings:
from decouple import config, Csv
# String values
SECRET_KEY = config('SECRET_KEY')
EMAIL_HOST = config('EMAIL_HOST', default='localhost')
# Boolean values
DEBUG = config('DEBUG', default=False, cast=bool)
EMAIL_USE_TLS = config('EMAIL_USE_TLS', default=True, cast=bool)
# Integer values
EMAIL_PORT = config('EMAIL_PORT', default=587, cast=int)
# List values
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())
# Complex casting
def cast_database_url(value):
import dj_database_url
return dj_database_url.parse(value)
DATABASE_CONFIG = config('DATABASE_URL', cast=cast_database_url)
# .env.development
DEBUG=True
DATABASE_URL=sqlite:///db.sqlite3
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
# .env.staging
DEBUG=False
DATABASE_URL=postgresql://user:pass@staging-db:5432/myproject_staging
ALLOWED_HOSTS=staging.example.com
# .env.production
DEBUG=False
DATABASE_URL=postgresql://user:pass@prod-db:5432/myproject_prod
ALLOWED_HOSTS=example.com,www.example.com
Loading Environment-Specific Files:
# settings/base.py
import os
from decouple import Config, RepositoryEnv
# Determine environment
ENVIRONMENT = os.getenv('DJANGO_ENVIRONMENT', 'development')
# Load environment-specific .env file
env_file = f'.env.{ENVIRONMENT}'
if os.path.exists(env_file):
config = Config(RepositoryEnv(env_file))
else:
from decouple import config
# settings/base.py
import sys
from django.core.exceptions import ImproperlyConfigured
def get_env_variable(var_name, default=None):
"""Get environment variable or raise exception."""
try:
return os.environ[var_name]
except KeyError:
if default is not None:
return default
error_msg = f"Set the {var_name} environment variable"
raise ImproperlyConfigured(error_msg)
# Validate required settings
REQUIRED_SETTINGS = ['SECRET_KEY', 'DATABASE_URL']
for setting in REQUIRED_SETTINGS:
if not config(setting, default=None):
raise ImproperlyConfigured(f"Missing required setting: {setting}")
# Validate DEBUG setting in production
if not DEBUG and 'runserver' not in sys.argv:
if SECRET_KEY == 'django-insecure-default-key':
raise ImproperlyConfigured("SECRET_KEY must be set in production")
# settings/base.py
import socket
# Get hostname for environment detection
HOSTNAME = socket.gethostname()
# Dynamic database configuration
if HOSTNAME.startswith('prod-'):
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'production_db',
# ... production database config
}
}
elif HOSTNAME.startswith('staging-'):
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'staging_db',
# ... staging database config
}
}
else:
# Development database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Feature flags
FEATURE_FLAGS = {
'NEW_DASHBOARD': config('FEATURE_NEW_DASHBOARD', default=False, cast=bool),
'BETA_FEATURES': config('FEATURE_BETA', default=False, cast=bool),
'MAINTENANCE_MODE': config('MAINTENANCE_MODE', default=False, cast=bool),
}
# settings/mixins.py
class DatabaseMixin:
"""Database configuration mixin."""
@property
def DATABASES(self):
return {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': config('DB_NAME'),
'USER': config('DB_USER'),
'PASSWORD': config('DB_PASSWORD'),
'HOST': config('DB_HOST', default='localhost'),
'PORT': config('DB_PORT', default='5432'),
'OPTIONS': {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
},
}
}
class CacheMixin:
"""Cache configuration mixin."""
@property
def CACHES(self):
return {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': config('REDIS_URL'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# settings/production.py
from .base import *
from .mixins import DatabaseMixin, CacheMixin
class ProductionSettings(DatabaseMixin, CacheMixin):
DEBUG = False
# ... other production settings
# Apply settings
globals().update(ProductionSettings().__dict__)
# settings/production.py
# HTTPS and security headers
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
# Cookies
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 3600 # 1 hour
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = True
# File uploads
FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5MB
DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000
# Password validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 12,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Admin security
ADMIN_URL = config('ADMIN_URL', default='admin/')
# settings/production.py
# CSP settings (using django-csp)
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net")
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'", "https://fonts.googleapis.com")
CSP_FONT_SRC = ("'self'", "https://fonts.gstatic.com")
CSP_IMG_SRC = ("'self'", "data:", "https:")
CSP_CONNECT_SRC = ("'self'",)
CSP_FRAME_ANCESTORS = ("'none'",)
CSP_BASE_URI = ("'self'",)
CSP_FORM_ACTION = ("'self'",)
# settings/production.py
# Database connection pooling
DATABASES['default']['CONN_MAX_AGE'] = 60
DATABASES['default']['OPTIONS'] = {
'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
'charset': 'utf8mb4',
'autocommit': True,
}
# Database connection health checks
DATABASES['default']['CONN_HEALTH_CHECKS'] = True
# settings/production.py
# Redis cache
CACHES = {
'default': {
'BACKEND': 'django_redis.cache.RedisCache',
'LOCATION': config('REDIS_URL'),
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
'CONNECTION_POOL_KWARGS': {
'max_connections': 50,
'retry_on_timeout': True,
},
'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor',
'SERIALIZER': 'django_redis.serializers.json.JSONSerializer',
},
'KEY_PREFIX': 'myproject',
'TIMEOUT': 300,
}
}
# Session cache
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'
# settings/production.py
# Static files
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
STATICFILES_FINDERS = [
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]
# Compression
STATICFILES_DIRS = [
BASE_DIR / 'static',
]
# WhiteNoise settings
WHITENOISE_USE_FINDERS = True
WHITENOISE_AUTOREFRESH = True
WHITENOISE_MAX_AGE = 31536000 # 1 year
pip install django-environ
# settings/base.py
import environ
env = environ.Env(
DEBUG=(bool, False),
EMAIL_USE_TLS=(bool, True),
DATABASE_URL=(str, 'sqlite:///db.sqlite3'),
)
# Read .env file
environ.Env.read_env(BASE_DIR / '.env')
# Use environment variables
DEBUG = env('DEBUG')
SECRET_KEY = env('SECRET_KEY')
DATABASES = {
'default': env.db()
}
# scripts/validate_settings.py
import os
import sys
import django
from django.conf import settings
from django.core.management import execute_from_command_line
def validate_settings():
"""Validate Django settings configuration."""
# Required settings
required_settings = [
'SECRET_KEY',
'DATABASES',
'INSTALLED_APPS',
'MIDDLEWARE',
]
missing_settings = []
for setting in required_settings:
if not hasattr(settings, setting):
missing_settings.append(setting)
if missing_settings:
print(f"Missing required settings: {', '.join(missing_settings)}")
return False
# Security checks
if settings.DEBUG and 'production' in os.environ.get('DJANGO_SETTINGS_MODULE', ''):
print("WARNING: DEBUG=True in production settings")
if settings.SECRET_KEY == 'django-insecure-default-key':
print("WARNING: Using default SECRET_KEY")
# Database connectivity
try:
from django.db import connection
connection.ensure_connection()
print("✓ Database connection successful")
except Exception as e:
print(f"✗ Database connection failed: {e}")
return False
print("✓ Settings validation passed")
return True
if __name__ == '__main__':
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.development')
django.setup()
validate_settings()
Django settings management is fundamental to building maintainable, secure, and scalable applications. Proper organization and environment-specific configuration ensure your application works correctly across different deployment scenarios while maintaining security and performance standards.
Django Admin and Management Commands
Django's admin interface and management commands are powerful tools for development, testing, and maintenance. This comprehensive guide covers both the built-in admin system and the extensive management command ecosystem.
Managing Django Environments: Local, Staging, and Production
Proper environment management is crucial for Django applications. This comprehensive guide covers setting up and managing different environments, ensuring smooth transitions from development to production while maintaining security and performance.