Estructura de configuración#

Organización de configuraciones de Django para diferentes entornos.

Descripción general#

Construbot utiliza configuraciones basadas en el entorno con herencia para evitar la duplicación y admitir múltiples entornos.

Ubicación: construbot/config/settings/

Archivos:

  • base.py - Configuración compartida para todos los entornos

  • local.py - Configuración de desarrollo

  • test.py - Configuración de prueba

  • producción.py - Configuración de producción

Jerarquía de configuración#

base.py (Common settings)
├── local.py (Development)
├── test.py (Testing)
└── production.py (Production)

Todos los archivos de entorno se importan desde base.py:

# local.py, test.py, production.py
from .base import *  # Import all base settings
from .base import env  # Import environ instance

# Override specific settings
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']

Configuración básica (base.py)#

Configuraciones comunes compartidas en todos los entornos:

Núcleo de Django:

# Django version
DJANGO_ADMIN_URL = env('DJANGO_ADMIN_URL', default='admin/')

# Internationalization
LANGUAGE_CODE = 'es-mx'
TIME_ZONE = 'America/Mexico_City'
USE_I18N = True
USE_L10N = True
USE_TZ = True

Aplicaciones instaladas:

INSTALLED_APPS = [
    # AutocompleteLight MUST be before admin
    'dal',
    'dal_select2',
    # Django contrib
    'django.contrib.admin',
    'django.contrib.auth',
    ...
    # Third-party
    'rest_framework',
    'rest_framework.authtoken',
    'allauth',
    'allauth.account',
    ...
    # Local apps
    'construbot.users',
    'construbot.proyectos',
    'construbot.api',
    'construbot.core',
]

Advertencia

dal y dal_select2 DEBEN aparecer antes de django.contrib.admin o los widgets de autocompletar no funcionarán.

Middleware:

MIDDLEWARE = [
    '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',
]

Autenticación:

AUTH_USER_MODEL = 'users.User'  # Custom user model
AUTHENTICATION_BACKENDS = [
    'construbot.core.backends.ModelBackend',  # Custom backend
    'allauth.account.auth_backends.AuthenticationBackend',
]

Base de datos (usa DATABASE_URL):

DATABASES = {
    'default': env.db('DATABASE_URL'),
}
DATABASES['default']['ATOMIC_REQUESTS'] = True

Archivos estáticos/multimedia:

STATIC_ROOT = str(ROOT_DIR / 'staticfiles')
STATIC_URL = '/static/'
STATICFILES_DIRS = [str(APPS_DIR / 'static')]

MEDIA_ROOT = str(APPS_DIR / 'media')
MEDIA_URL = '/media/'

Hashhers de contraseña:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',  # Primary
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

Apio:

if USE_TZ:
    CELERY_TIMEZONE = TIME_ZONE
CELERY_BROKER_URL = env('CELERY_BROKER_URL', default=env('REDIS_URL', default='redis://localhost:6379/0'))
CELERY_RESULT_BACKEND = CELERY_BROKER_URL
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TASK_TIME_LIMIT = 5 * 60  # 5 minutes
CELERY_TASK_SOFT_TIME_LIMIT = 60  # 1 minute

Configuración local (local.py)#

Configuración del entorno de desarrollo:

Modo de depuración:

DEBUG = True

Anfitriones permitidos:

ALLOWED_HOSTS = ['localhost', '0.0.0.0', '127.0.0.1']

Base de datos:

Puede usar variables de entorno o valores predeterminados en PostgreSQL local:

DATABASES = {
    'default': env.db('DATABASE_URL', default='postgresql://debug:debug@postgres:5432/construbot')
}

Almacenamiento en caché (desarrollo - caché ficticio):

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': '',
    }
}

Correo electrónico (backend de la consola):

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
EMAIL_HOST = 'localhost'
EMAIL_PORT = 1025  # MailHog

Herramientas de depuración:

INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
INTERNAL_IPS = ['127.0.0.1', '10.0.2.2']

extensiones-django:

INSTALLED_APPS += ['django_extensions']

Configuración de prueba (test.py)#

Configuración del entorno de prueba:

Depuración desactivada:

DEBUG = False

Base de datos (SQLite en memoria):

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': ':memory:',
    }
}

Hash rápido de contraseñas:

PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.MD5PasswordHasher',
]

Plantillas (sin almacenamiento en caché):

TEMPLATES[0]['OPTIONS']['loaders'] = [
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
]

Correo electrónico (backend de memoria):

EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'

Apio (ejecución sincrónica):

CELERY_TASK_ALWAYS_EAGER = True
CELERY_TASK_EAGER_PROPAGATES = True

Configuración de producción (producción.py)#

Configuración del entorno de producción:

Depuración desactivada:

DEBUG = env.bool('DJANGO_DEBUG', False)

Anfitriones permitidos:

ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=['example.com'])

Seguridad:

SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = env.bool('DJANGO_SECURE_SSL_REDIRECT', default=True)
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_CONTENT_TYPE_NOSNIFF = True

Base de datos:

DATABASES['default'] = env.db('DATABASE_URL')
DATABASES['default']['ATOMIC_REQUESTS'] = True
DATABASES['default']['CONN_MAX_AGE'] = env.int('CONN_MAX_AGE', default=60)

Almacenamiento en caché (Redis):

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': env('REDIS_URL'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'IGNORE_EXCEPTIONS': True,
        }
    }
}

Archivos estáticos (WhiteNoise):

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

Archivos multimedia (S3):

if env.bool('USE_S3', default=False):
    AWS_ACCESS_KEY_ID = env('AWS_ACCESS_KEY_ID')
    AWS_SECRET_ACCESS_KEY = env('AWS_SECRET_ACCESS_KEY')
    AWS_STORAGE_BUCKET_NAME = env('AWS_STORAGE_BUCKET_NAME')
    AWS_S3_REGION_NAME = env('AWS_S3_REGION_NAME', default='us-east-1')
    DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

Correo electrónico (servicio de producción):

EMAIL_BACKEND = env('DJANGO_EMAIL_BACKEND', default='anymail.backends.mailgun.EmailBackend')

Explotación florestal:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'verbose'
        },
    },
    'root': {
        'level': 'INFO',
        'handlers': ['console'],
    },
}

Centinela:

if env('SENTRY_DSN', default=None):
    import sentry_sdk
    from sentry_sdk.integrations.django import DjangoIntegration

    sentry_sdk.init(
        dsn=env('SENTRY_DSN'),
        integrations=[DjangoIntegration()],
        environment=env('SENTRY_ENVIRONMENT', default='production'),
    )

Selección del entorno#

A través de variable de entorno:

export DJANGO_SETTINGS_MODULE=construbot.config.settings.production
python manage.py runserver

A través de Makefile:

make dev        # Uses local settings
make buildprod  # Uses production settings
make test       # Uses test settings

En administrar.py:

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'construbot.config.settings.local')

entorno-django#

Cargando archivo .env:

# In base.py
import environ

ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
APPS_DIR = ROOT_DIR / 'construbot'

env = environ.Env()

# Read .env file
READ_DOT_ENV_FILE = env.bool('DJANGO_READ_DOT_ENV_FILE', default=False)
if READ_DOT_ENV_FILE:
    env.read_env(str(ROOT_DIR / '.env'))

Acceso a variables:

DEBUG = env.bool('DJANGO_DEBUG', default=False)
SECRET_KEY = env('DJANGO_SECRET_KEY')
DATABASE_URL = env.db('DATABASE_URL')
ALLOWED_HOSTS = env.list('DJANGO_ALLOWED_HOSTS', default=[])

Modo biblioteca#

Configuración CONSTRUBOT_AS_LIBRARY:

Cuando es True, desactiva las funciones independientes:

# In base.py
CONSTRUBOT_AS_LIBRARY = env.bool('CONSTRUBOT_AS_LIBRARY', default=False)

if not CONSTRUBOT_AS_LIBRARY:
    INSTALLED_APPS += [
        'construbot.account_config',  # Only in standalone mode
    ]

Afecta:

  • Disponibilidad de la interfaz de administración

  • URL de administración de cuentas

  • Sistema de autenticación

Consulte Modo biblioteca para obtener más detalles.

Mejores prácticas#

1. Nunca cometas secretos:

# Use environment variables
SECRET_KEY = env('DJANGO_SECRET_KEY')

# Never hardcode:
# SECRET_KEY = 'abc123'  # BAD!

2. Utilice valores predeterminados sensatos:

DEBUG = env.bool('DJANGO_DEBUG', default=False)  # Safe default

3. Variables requeridas del documento:

Cree .env.example con todas las variables requeridas.

4. Validar configuración de producción:

python manage.py check --deploy

5. Mantenga base.py SECO:

Coloque configuraciones comunes en base.py, anule solo lo que difiera.

Solución de problemas#

Módulo de configuración incorrecto cargado:

# Check current settings
python manage.py diffsettings

# Verify environment variable
echo $DJANGO_SETTINGS_MODULE

La configuración no se carga desde .env:

# Ensure this is set
export DJANGO_READ_DOT_ENV_FILE=True

# Or in .env file itself (chicken-egg problem)
# Better: export it before running Django

Errores de importación:

# Always import from base
from .base import *
from .base import env  # Don't forget this!

Ver también#