基本原則
Django 1.8 有約 140 個(gè)配置項(xiàng)可通過(guò) settings 模塊進(jìn)行設(shè)置。settings 模塊在每次啟動(dòng) Django 服務(wù)時(shí)進(jìn)行初始化,因此對(duì) settings.py 文件修改后,都要重啟 Django 服務(wù)器才能生效。
所有 settings 文件都應(yīng)進(jìn)行版本控制,包括對(duì)配置項(xiàng)的修改日期/時(shí)間和注釋信息進(jìn)行版本控制
DRY,通過(guò)
import base_settings進(jìn)行繼承,避免復(fù)制粘貼機(jī)密信息不要放在版本控制中
使用多個(gè) settings 文件
settings/
__init__.py
base.py
local.py
staging.py
test.py
production.py
并且每個(gè) settings 文件都對(duì)應(yīng)有一個(gè) requirements 文件
settings 文件名 | 目的
-----------------------|
local.py, dev.py | 本地開(kāi)發(fā)環(huán)境配置內(nèi)容,如 DEBUG=True, 開(kāi)啟 django-debug-toolbar 等
staging.py | 針對(duì) Staging 階段的配置內(nèi)容
test.py | 針對(duì)運(yùn)行測(cè)試的配置內(nèi)容
production.py, prod.py | 生產(chǎn)環(huán)境下的配置內(nèi)容
ci.py | 針對(duì)持續(xù)集成服務(wù)器的配置內(nèi)容
使用方法:
- shell 中
python manage.py shell --settings=twoscoops.settings.local
- 啟動(dòng)服務(wù)
python manage.py runserver --settings=twoscoops.settings.local
- 設(shè)置 DJANGO_SETTINGS_MODULE 和 PYTHONPATH 環(huán)境變量。若使用了 virtualenv,可以在每個(gè)環(huán)境的激活腳本中設(shè)置 DJANGO_SETTINGS_MODULE 和 PYTHONPATH。
一個(gè)開(kāi)發(fā)環(huán)境中的 settings 文件的例子:
# settings/local.py
from .base import *
DEBUG = True
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql_psycopg2",
"NAME": "twoscoops",
"USER": "",
"PASSWORD": "",
"HOST": "localhost",
"PORT": "",
}
}
INSTALLED_APPS += ("debug_toolbar", )
上面的例子中有 from .base import *, 這是 import * 在 Django 中唯一被贊成使用的地方。因?yàn)槲覀兿朐?settings 文件中覆蓋所有的命名空間。
針對(duì)開(kāi)發(fā)環(huán)境也可以有多個(gè) settings 文件
基本原則是每個(gè) settings 文件都需要版本控制??舍槍?duì)不同的開(kāi)發(fā)者創(chuàng)建相應(yīng)的 settings 文件,如:
# settings/dev_pydanny.py
from .dev import *
# Set short cache timeout
CACHE_TIMEOUT = 30
而創(chuàng)建后的所有 settings 文件會(huì)是:
settings/
__init__.py
base.py
dev.py
dev_audreyr.py
dev_pydanny.py
local.py
staging.py
test.py
production.py
將配置信息從代碼中分離出來(lái)
將 SECRET_KEY、API KEY 等信息存放在代碼庫(kù)中有以下問(wèn)題:
這些信息針對(duì)每次部署都要變動(dòng)
SECRET_KEY 等值是配置值,不是代碼
存放在代碼庫(kù)中,有代碼庫(kù)訪問(wèn)權(quán)限的人都能看到
多數(shù) PaaS 不提供針對(duì)單臺(tái)服務(wù)器的配置功能
解決方案是使用 環(huán)境變量。
使用環(huán)境變量來(lái)存放 SECRET_KEY 等信息的好處:
由于這些敏感信息已保存它處,你會(huì)毫不猶豫地對(duì)每個(gè)文件進(jìn)行版本控制
針對(duì)每次部署,不再需要對(duì)這些配置信息進(jìn)行修改
多數(shù) PaaS 平臺(tái)推薦使用環(huán)境變量,并提供了相應(yīng)的配置和管理工具
如何在本地進(jìn)行環(huán)境變量設(shè)置
在 Linux/Mac 的 bash 中,通過(guò)將配置代碼添加到 .bashrc、.bash_profile 或 .profile 等文件后面進(jìn)行配置。若使用 virtualenv,也可以在 virtualenv 的 bin/activate 腳本中添加配置代碼進(jìn)行配置:
配置代碼:
$ export SOME_SECRET_KEY=1c3-cr3am-15-yummy
$ export AUDREY_FREEZER_KEY=y34h-r1ght-d0nt-t0uch-my-1c3-cr34m
在 Win 上,可以在 cmd.exe 中通過(guò) setx 命令進(jìn)行配置,也可以在 virtualenv 的 bin/activate.bat 腳本中進(jìn)行配置。
配置代碼:
> set SOME_SECRET_KEY 1c3-cr3am-15-yummy
PowerShell 比 cmd.exe 更加強(qiáng)大,在 Vista 及以上版本中可用。使用 PowerShell 進(jìn)行環(huán)境變量設(shè)置:
只針對(duì)當(dāng)前用戶:
[Environment]::SetEnvironmentVariable("SOME_SECRET_KEY",
"1c3-cr3am-15-yummy", "User")
[Environment]::SetEnvironmentVariable("AUDREY_FREEZER_KEY",
"y34h-r1ght-d0nt-t0uch-my-1c3-cr34m", "User")
針對(duì)本機(jī)的全部用戶:
[Environment]::SetEnvironmentVariable("SOME_SECRET_KEY",
"1c3-cr3am-15-yummy", "Machine")
[Environment]::SetEnvironmentVariable("AUDREY_FREEZER_KEY",
"y34h-r1ght-d0nt-t0uch-my-1c3-cr34m", "Machine")
生產(chǎn)環(huán)境中的環(huán)境變量配置舉例
- 在 Heroku 上配置
$ heroku config:set SOME_SECRET_KEY=1c3-cr3am-15-yummy
- 在 Python 中存取這些配置信息
>>> import os
>>> os.environ["SOME_SECRET_KEY"]
"1c3-cr3am-15-yummy"
- 在 settings 文件中存取這些配置信息
# Top of settings/production.py
import os
SOME_SECRET_KEY = os.environ["SOME_SECRET_KEY"]
對(duì)未設(shè)置 SECRET_KEY 的異常進(jìn)行處理
如果沒(méi)有 SECRET_KEY 值, 上面的存取代碼會(huì)拋出 KeyError,項(xiàng)目也無(wú)法啟動(dòng)。但是該異常沒(méi)有提供有效的提示信息,不利于調(diào)試。
在 settings/base.py 中使用以下代碼進(jìn)行處理:
# settings/base.py
import os
# Normally you should not import ANYTHING from Django directly
# into your settings, but ImproperlyConfigured is an exception.
from django.core.exceptions import ImproperlyConfigured
def get_env_variable(var_name):
"""Get the environment variable or return exception."""
try:
return os.environ[var_name]
except KeyError:
error_msg = "Set the {} environment variable".format(var_name)
raise ImproperlyConfigured(error_msg)
然后在 settings 文件中,使用:
SOME_SECRET_KEY = get_env_variable("SOME_SECRET_KEY")
之后,如果沒(méi)有設(shè)置 SOME_SECRET_KEY 這個(gè)環(huán)境變量,會(huì)出現(xiàn)以下的錯(cuò)誤提示:
django.core.exceptions.ImproperlyConfigured: Set the SOME_SECRET_KEY
environment variable.
manage.py 會(huì)默認(rèn)將 DJANGO_SETTINGS_MODULE 指向 settings.py,推薦在多 settings 文件時(shí)使用 django-admin,而單 settings 文件時(shí)使用 manage.py, 這兩個(gè)命令基本是等同的:
$ django-admin <command> [options]
$ manage.py <command> [options]
當(dāng)不能設(shè)置環(huán)境變量時(shí)
Apache 等使用自己的環(huán)境變量,如以上的針對(duì)系統(tǒng)的環(huán)境變量設(shè)置方法無(wú)效,此時(shí)可以將敏感信息保存在一個(gè)不可執(zhí)行的文件中,并且不對(duì)該文件進(jìn)行版本控制:
為保存敏感信息生成一個(gè)文件,格式可以為 JSON、Config、YAML 或 XML。
增加一個(gè) loader 來(lái)對(duì)這些信息進(jìn)行管理
將該文件名增加到 .gitignore 和 .hgignore
使用 JSON 格式
- 生成 secrets.json 文件:
{
"FILENAME": "secrets.json",
"SECRET_KEY": "I've got a secret!",
"DATABASES_HOST": "127.0.0.1",
"PORT": "5432"
}
- 在 settings/base.py 中添加 loader,來(lái)存取這些信息:
# settings/base.py
import json
# Normally you should not import ANYTHING from Django directly
# into your settings, but ImproperlyConfigured is an exception.
from django.core.exceptions import ImproperlyConfigured
# JSON-based secrets module
with open("secrets.json") as f:
secrets = json.loads(f.read())
def get_secret(setting, secrets=secrets):
"""Get the secret variable or return explicit exception."""
try:
return secrets[setting]
except KeyError:
error_msg = "Set the {0} environment variable".format(setting)
raise ImproperlyConfigured(error_msg)
SECRET_KEY = get_secret("SECRET_KEY")
使用多個(gè) requirements 文件
每個(gè) settings 文件需對(duì)應(yīng)有一個(gè) requirements 文件,并且對(duì)應(yīng)不同的配置,只安裝相應(yīng)的依賴文件。
requirements 文件舉例:
requirements/
base.txt
local.txt
staging.txt
production.txt
base.txt 中存放全局依賴,如:
Django==1.8.0
psycopg2==2.6
djangorestframework==3.1.1
而針對(duì)本地開(kāi)發(fā)環(huán)境的 local.txt,可以在 base.txt 的基礎(chǔ)上添加其它依賴:
-r base.txt # includes the base.txt requirements file
coverage==3.7.1
django-debug-toolbar==1.3.0
對(duì)于持續(xù)集成服務(wù)器的 ci.txt 可以是:
-r base.txt # includes the base.txt requirements file
coverage==3.7.1
django-jenkins==0.16.4
而 production.txt 基本會(huì)和 base.txt 相同,可能會(huì)是:
-r base.txt # includes the base.txt requirements file
安裝
針對(duì)本地開(kāi)發(fā):
$ pip install -r requirements/local.txt
針對(duì)生產(chǎn)環(huán)境:
$ pip install -r requirements/production.txt
所有 requirements 文件中的依賴包都指定為特定的一個(gè)版本,這樣能確保項(xiàng)目更加穩(wěn)定。
在 settings 文件中處理文件路徑
不要對(duì)文件路徑進(jìn)行硬編碼
- 使用 Unipath 進(jìn)行文件路徑處理
# At the top of settings/base.py
from unipath import Path
BASE_DIR = Path(__file__).ancestor(3)
MEDIA_ROOT = BASE_DIR.child("media")
STATIC_ROOT = BASE_DIR.child("static")
STATICFILES_DIRS = (
BASE_DIR.child("assets"),
)
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
DIRS = (BASE_DIR.child("templates"),)
},
]
- 使用 os.path 進(jìn)行文件路徑處理
# At the top of settings/base.py
from os.path import join, abspath, dirname
here = lambda *dirs: join(abspath(dirname(__file__)), *dirs)
BASE_DIR = here("..", "..")
root = lambda *dirs: join(abspath(BASE_DIR), *dirs)
# Configuring MEDIA_ROOT
MEDIA_ROOT = root("media")
# Configuring STATIC_ROOT
STATIC_ROOT = root("collected_static")
# Additional locations of static files
STATICFILES_DIRS = (
root("assets"),
)
# Configuring TEMPLATE_DIRS
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
DIRS = (root("templates"),)
},
]
要找到你的配置文件與 Django 默認(rèn)配置的區(qū)別,使用 Django 的 diffsettings 命令。
參考文獻(xiàn): Two Scoops of Django: Best Practices for Django 1.8