2. Django配置工程化

Django 默认生成的 settings.py 是一个单文件配置,适合入门,却难以支撑企业项目在开发、测试、生产三套环境之间切换。工程化的核心目标只有一句话:同一份代码,在不同环境中加载不同的配置,且敏感信息不进入版本库。本节围绕这个目标,讲清配置分层结构、环境变量读取方式、敏感信息管理手段,以及一套可直接落地的目录组织方案。

一、为什么单文件配置会失控

当项目只有 settings.py 时,开发者常遇到三类问题。第一,DEBUG、ALLOWED_HOSTS、数据库连接在开发与生产之间不同,靠手工注释切换,极易把 DEBUG=True 带上线。第二,数据库密码、SECRET_KEY、第三方 API Key 直接写在文件里,一旦仓库泄露或新人克隆代码,密钥即外泄。第三,测试环境需要独立的数据库和缓存,若与开发共用配置,测试数据会污染开发库。

解决思路是配置分层 + 环境变量注入:把配置拆成“公共部分”和“环境差异部分”,差异部分通过环境变量在运行时注入,敏感值只存在于部署环境,不写入代码仓库。

二、推荐的配置目录结构

把原来的 settings.py 改造成一个包,是 Django 官方文档认可的做法。典型结构如下:

myproject/
├── manage.py
├── myproject/
│   ├── __init__.py
│   ├── settings/
│   │   ├── __init__.py
│   │   ├── base.py        # 公共配置
│   │   ├── dev.py         # 开发环境
│   │   ├── test.py        # 测试环境
│   │   └── prod.py        # 生产环境
│   ├── urls.py
│   └── wsgi.py
├── .env.example           # 变量清单模板(入库)
├── .env                   # 真实值(不入库)
└── .gitignore

base.py 存放所有环境共用的内容,例如 INSTALLED_APPS、MIDDLEWARE、TEMPLATES、LANGUAGE_CODE、TIME_ZONE。各环境文件用 from .base import * 继承,再覆盖差异项。启动时通过 DJANGO_SETTINGS_MODULE 指定加载哪个模块:

# 开发
export DJANGO_SETTINGS_MODULE=myproject.settings.dev
python manage.py runserver

# 生产(由 WSGI/ASGI 或容器环境变量指定)
export DJANGO_SETTINGS_MODULE=myproject.settings.prod

三、用环境变量读取敏感信息

环境变量是敏感信息与代码之间的隔离层。推荐使用 django-environ 或 python-decouple 统一读取,避免到处写 os.environ.get 并手工做类型转换。以 django-environ 为例,在 base.py 顶部初始化:

import environ
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent.parent

env = environ.Env(
    DEBUG=(bool, False),
    ALLOWED_HOSTS=(list, []),
)
environ.Env.read_env(BASE_DIR / ".env")

SECRET_KEY = env("SECRET_KEY")
DEBUG = env("DEBUG")
ALLOWED_HOSTS = env("ALLOWED_HOSTS")
DATABASES = {"default": env.db("DATABASE_URL")}

这里 DATABASE_URL 采用 postgres://user:pass@host:5432/dbname 这类单串格式,env.db() 会自动解析成 Django 的 DATABASES 字典,既简洁又便于在不同环境替换。对应的 .env.example 只写变量名和示例值,供团队参照:

SECRET_KEY=change-me-in-production
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
DATABASE_URL=postgres://app:app@127.0.0.1:5432/app_dev
REDIS_URL=redis://127.0.0.1:6379/0

而 .env 存放真实值,必须写入 .gitignore:

# .gitignore
.env
*.env
!.env.example
注意:.env.example 中的 SECRET_KEY 只是占位符,绝不能把生产密钥提交到仓库。生产环境的密钥应由部署平台(如容器编排的 Secret、云厂商的密钥管理服务)注入,而不是放在服务器上的明文文件里。

四、三套环境的差异配置

各环境文件只写“与 base 不同”的部分,差异集中在数据库、缓存、调试开关、日志和安全项上。下面给出三个环境的关键差异示例。

# dev.py
from .base import *

DEBUG = True
ALLOWED_HOSTS = ["*"]
DATABASES = {"default": env.db("DATABASE_URL", default="sqlite:///db.sqlite3")}
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
# test.py
from .base import *

DEBUG = False
DATABASES = {"default": env.db("TEST_DATABASE_URL")}
PASSWORD_HASHERS = ["django.contrib.auth.hashers.MD5PasswordHasher"]  # 加速测试
CACHES = {"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"}}
# prod.py
from .base import *

DEBUG = False
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS")
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
DATABASES = {"default": env.db("DATABASE_URL")}
CACHES = {"default": env.cache("REDIS_URL")}
LOGGING = {
    "version": 1,
    "handlers": {"file": {"class": "logging.FileHandler", "filename": "/var/log/app/django.log"}},
    "root": {"handlers": ["file"], "level": "INFO"},
}

可以看到,prod.py 关闭了 DEBUG,开启了 HTTPS 重定向、安全 Cookie 和 HSTS,并把日志写入文件;test.py 用更快的密码哈希器加速测试;dev.py 则允许任意主机、使用本地内存缓存和控制台邮件后端。这些差异正是“多环境配置”要解决的问题。

五、配置加载与校验流程

下图展示了从启动命令到最终生效配置的完整链路:环境变量与 .env 文件先被读取,再按 DJANGO_SETTINGS_MODULE 选择环境模块,环境模块继承 base.py 并覆盖差异项,最后交给 Django 运行时使用。图中同时标出了敏感信息的边界——只有 .env 和部署平台注入的变量携带真实密钥,代码仓库中只有 .env.example。

.env 文件 真实密钥(不入库) 部署平台 Secret 生产环境注入 .env.example 变量清单(入库) environ.Env 读取 类型转换 + 默认值 DJANGO_SETTINGS_MODULE 选择环境模块 base.py(公共配置) INSTALLED_APPS / MIDDLEWARE dev.py DEBUG=True / 本地缓存 test.py 独立测试库 / 快速哈希 prod.py DEBUG=False / HTTPS / HSTS Django 运行时(settings 对象生效) 数据库 / 缓存 / 邮件 / 日志 / 安全策略 敏感信息边界:仅存在于左侧

六、落地检查清单

把上述方案落到项目里,可以用下面这份清单逐项核对,避免遗漏:

检查项合格标准
配置目录settings/ 包内含 base.py、dev.py、test.py、prod.py
环境切换通过 DJANGO_SETTINGS_MODULE 指定,不修改代码
敏感信息SECRET_KEY、数据库密码、API Key 均来自环境变量
版本库.env 已加入 .gitignore,仅提交 .env.example
生产安全DEBUG=False、ALLOWED_HOSTS 明确、HTTPS 与安全 Cookie 开启
测试隔离测试使用独立数据库,不污染开发库
一个实用技巧:在 base.py 末尾加一段断言,当 DEBUG=False 时强制检查 SECRET_KEY 是否仍为默认值,若未替换则直接抛出 ImproperlyConfigured,可以在部署阶段提前暴露配置错误,而不是等到线上出问题。

配置工程化不是一次性工作,而是随着环境增多持续维护的约定。只要坚持“公共配置进 base、差异配置进环境文件、敏感值进环境变量”这三条边界,多环境切换就会从易错的手工操作变成可重复、可审计的标准流程。