为本地开发和生产服务器处理设置的推荐方法是什么?其中一些(如常量等)可以在两者中更改/访问,但其中一些(如静态文件的路径)需要保持不同,因此不应该在每次部署新代码时都重写。

目前,我正在将所有常量添加到settings.py中。但是每次我在本地更改一些常量时,我都必须将其复制到生产服务器并编辑文件以进行特定于生产的更改……:(

编辑:看起来这个问题没有标准答案,我已经接受了最流行的方法。


当前回答

不要使用settings.py,而是使用下面的布局:

.
└── settings/
    ├── __init__.py  <= not versioned
    ├── common.py
    ├── dev.py
    └── prod.py

py是您的大部分配置所在的位置。

py从common中导入所有内容,并覆盖它需要覆盖的内容:

from __future__ import absolute_import # optional, but I like it
from .common import *

# Production overrides
DEBUG = False
#...

类似地,dev.py从common.py导入所有内容,并覆盖它需要覆盖的内容。

最后,__init__.py是你决定加载哪些设置的地方,也是你存储秘密的地方(因此这个文件不应该被版本化):

from __future__ import absolute_import
from .prod import *  # or .dev if you want dev

##### DJANGO SECRETS
SECRET_KEY = '(3gd6shenud@&57...'
DATABASES['default']['PASSWORD'] = 'f9kGH...'

##### OTHER SECRETS
AWS_SECRET_ACCESS_KEY = "h50fH..."

我喜欢这个解决方案的原因是:

所有东西都在您的版本控制系统中,除了机密信息 大多数配置都在一个地方:common.py。 特定于产品的东西放在prod。py中,特定于开发的东西放在dev。py中。这很简单。 你可以在prod.py或dev.py中覆盖common.py中的内容,也可以覆盖__init__.py中的任何内容。 这是简单的python。没有重新导入黑客。

其他回答

我在django split-settings的帮助下管理我的配置。

它是默认设置的临时替换。它很简单,但可配置。并且不需要重构现有设置。

下面是一个小例子(文件示例/settings/__init__.py):

from split_settings.tools import optional, include
import os

if os.environ['DJANGO_SETTINGS_MODULE'] == 'example.settings':
    include(
        'components/default.py',
        'components/database.py',
        # This file may be missing:
        optional('local_settings.py'),

        scope=globals()
    )

就是这样。

更新

我写了一篇关于用django-split-sttings管理django设置的博文。看看吧!

Django 1.5的最佳实践建议对你的设置文件使用版本控制,并将文件存储在一个单独的目录中:

project/
    app1/
    app2/
    project/
        __init__.py
        settings/
            __init__.py
            base.py
            local.py
            production.py
    manage.py

base.py文件包含常见设置(如MEDIA_ROOT或ADMIN),而local.py和production.py有特定于站点的设置:

在基本文件设置/base.py:

INSTALLED_APPS = (
    # common apps...
)

在本地开发设置文件settings/local.py中:

from project.settings.base import *

DEBUG = True
INSTALLED_APPS += (
    'debug_toolbar', # and other apps for local development
)

在文件生产设置文件设置/production.py:

from project.settings.base import *

DEBUG = False
INSTALLED_APPS += (
    # other apps for production site
)

然后当你运行django时,你添加——settings选项:

# Running django for local development
$ ./manage.py runserver 0:8000 --settings=project.settings.local

# Running django shell on the production site
$ ./manage.py shell --settings=project.settings.production

这本书的作者还在Github上发布了一个示例项目布局模板。

TL;DR:诀窍在于修改操作系统。在任何settings/<purpose>.py中导入settings/base.py之前,这将极大地简化事情。


一想到这些缠在一起的文件我就头疼。 合并、导入(有时是有条件的)、覆盖、修补已经设置的内容,以防稍后DEBUG设置更改。 真是个噩梦!

多年来,我尝试了各种不同的解决方案。它们都有一定的作用,但管理起来很痛苦。 WTF !我们真的需要那么多麻烦吗?我们从一个settings.py文件开始。 现在我们需要一个文档,以正确的顺序将所有这些组合在一起!

我希望我终于用下面的解决方案达到了我的最佳境界。

让我们回顾一下目标(有些是共同的,有些是我的)

Keep secrets a secret — don't store them in a repo! Set/read keys and secrets through environment settings, 12 factor style. Have sensible fallback defaults. Ideally for local development you don't need anything more beside defaults. …but try to keep defaults production safe. It's better to miss a setting override locally, than having to remember to adjust default settings safe for production. Have the ability to switch DEBUG on/off in a way that can have an effect on other settings (eg. using javascript compressed or not). Switching between purpose settings, like local/testing/staging/production, should be based only on DJANGO_SETTINGS_MODULE, nothing more. …but allow further parameterization through environment settings like DATABASE_URL. …also allow them to use different purpose settings and run them locally side by side, eg. production setup on local developer machine, to access production database or smoke test compressed style sheets. Fail if an environment variable is not explicitly set (requiring an empty value at minimum), especially in production, eg. EMAIL_HOST_PASSWORD. Respond to default DJANGO_SETTINGS_MODULE set in manage.py during django-admin startproject Keep conditionals to a minimum, if the condition is the purposed environment type (eg. for production set log file and it's rotation), override settings in associated purposed settings file.

not'的

Do not let django read DJANGO_SETTINGS_MODULE setting form a file. Ugh! Think of how meta this is. If you need to have a file (like docker env) read that into the environment before staring up a django process. Do not override DJANGO_SETTINGS_MODULE in your project/app code, eg. based on hostname or process name. If you are lazy to set environment variable (like for setup.py test) do it in tooling just before you run your project code. Avoid magic and patching of how django reads it's settings, preprocess the settings but do not interfere afterwards. No complicated logic based nonsense. Configuration should be fixed and materialized not computed on the fly. Providing a fallback defaults is just enough logic here. Do you really want to debug, why locally you have correct set of settings but in production on a remote server, on one of hundred machines, something computed differently? Oh! Unit tests? For settings? Seriously?

解决方案

我的策略包括与ini样式文件一起使用的优秀django-environ, 提供操作系统。环境默认为本地开发,一些最小和简短的设置/<purpose>.py文件有一个 导入设置/base.py环境是从INI文件设置的。这有效地给了我们一种设置注入。

这里的技巧是修改操作系统。导入settings/base.py。

要查看完整的示例,请执行回购:https://github.com/wooyek/django-settings-strategy

.
│   manage.py
├───data
└───website
    ├───settings
    │   │   __init__.py   <-- imports local for compatibility
    │   │   base.py       <-- almost all the settings, reads from proces environment 
    │   │   local.py      <-- a few modifications for local development
    │   │   production.py <-- ideally is empty and everything is in base 
    │   │   testing.py    <-- mimics production with a reasonable exeptions
    │   │   .env          <-- for local use, not kept in repo
    │   __init__.py
    │   urls.py
    │   wsgi.py

设置/ .env

本地开发的默认值。一个秘密文件,主要用于设置所需的环境变量。 如果在本地开发中不需要它们,则将它们设置为空值。 我们在这里提供默认值,而不是在settings/base.py中,如果环境中缺少这些值,则会在任何其他机器上失败。

设置/ local.py

这里发生的是从settings/加载环境。Env,然后导入公共设置 从设置/ base.py。之后,我们可以覆盖一些以缓解局部开发。

import logging
import environ

logging.debug("Settings loading: %s" % __file__)

# This will read missing environment variables from a file
# We wan to do this before loading a base settings as they may depend on environment
environ.Env.read_env(DEBUG='True')

from .base import *

ALLOWED_HOSTS += [
    '127.0.0.1',
    'localhost',
    '.example.com',
    'vagrant',
    ]

# https://docs.djangoproject.com/en/1.6/topics/email/#console-backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'

LOGGING['handlers']['mail_admins']['email_backend'] = 'django.core.mail.backends.dummy.EmailBackend'

# Sync task testing
# http://docs.celeryproject.org/en/2.5/configuration.html?highlight=celery_always_eager#celery-always-eager

CELERY_ALWAYS_EAGER = True
CELERY_EAGER_PROPAGATES_EXCEPTIONS = True

设置/ production.py

对于生产,我们不应该期望有一个环境文件,但是如果我们正在测试一些东西,那么有一个环境文件会更容易。 但是不管怎样,唯恐提供了很少的默认值,所以settings/base.py可以相应地响应。

environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *

这里的主要兴趣点是DEBUG和ASSETS_DEBUG覆盖, 它们将应用于python操作系统。只有当它们从环境和文件中丢失时才使用environ。

这些将是我们的产品默认值,不需要将它们放在环境或文件中,但如果需要,可以覆盖它们。整洁!

设置/ base.py

这些是基本的django设置,有一些条件和大量的从环境中读取它们。 几乎所有东西都在这里,保持所有目标环境的一致性和尽可能相似。

主要区别如下(我希望这些是不言自明的):

import environ

# https://github.com/joke2k/django-environ
env = environ.Env()

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Where BASE_DIR is a django source root, ROOT_DIR is a whole project root
# It may differ BASE_DIR for eg. when your django project code is in `src` folder
# This may help to separate python modules and *django apps* from other stuff
# like documentation, fixtures, docker settings
ROOT_DIR = BASE_DIR

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG', default=False)

INTERNAL_IPS = [
    '127.0.0.1',
]

ALLOWED_HOSTS = []

if 'ALLOWED_HOSTS' in os.environ:
    hosts = os.environ['ALLOWED_HOSTS'].split(" ")
    BASE_URL = "https://" + hosts[0]
    for host in hosts:
        host = host.strip()
        if host:
            ALLOWED_HOSTS.append(host)

SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=False)

# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases

if "DATABASE_URL" in os.environ:  # pragma: no cover
    # Enable database config through environment
    DATABASES = {
        # Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
        'default': env.db(),
    }

    # Make sure we use have all settings we need
    # DATABASES['default']['ENGINE'] = 'django.contrib.gis.db.backends.postgis'
    DATABASES['default']['TEST'] = {'NAME': os.environ.get("DATABASE_TEST_NAME", None)}
    DATABASES['default']['OPTIONS'] = {
        'options': '-c search_path=gis,public,pg_catalog',
        'sslmode': 'require',
    }
else:
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            # 'ENGINE': 'django.contrib.gis.db.backends.spatialite',
            'NAME': os.path.join(ROOT_DIR, 'data', 'db.dev.sqlite3'),
            'TEST': {
                'NAME': os.path.join(ROOT_DIR, 'data', 'db.test.sqlite3'),
            }
        }
    }

STATIC_ROOT = os.path.join(ROOT_DIR, 'static')

# django-assets
# http://django-assets.readthedocs.org/en/latest/settings.html

ASSETS_LOAD_PATH = STATIC_ROOT
ASSETS_ROOT = os.path.join(ROOT_DIR, 'assets', "compressed")
ASSETS_DEBUG = env('ASSETS_DEBUG', default=DEBUG)  # Disable when testing compressed file in DEBUG mode
if ASSETS_DEBUG:
    ASSETS_URL = STATIC_URL
    ASSETS_MANIFEST = "json:{}".format(os.path.join(ASSETS_ROOT, "manifest.json"))
else:
    ASSETS_URL = STATIC_URL + "assets/compressed/"
    ASSETS_MANIFEST = "json:{}".format(os.path.join(STATIC_ROOT, 'assets', "compressed", "manifest.json"))
ASSETS_AUTO_BUILD = ASSETS_DEBUG
ASSETS_MODULES = ('website.assets',)

最后一点显示了它的力量。ASSETS_DEBUG有一个合理的默认值, 可以在settings/production.py中覆盖,甚至可以通过环境设置覆盖!耶!

实际上,我们有一个混合的重要性等级:

Settings /.py -根据目的设置默认值,不存储秘密 Settings /base.py -主要由环境控制 进程环境设置- 12因素宝贝! 设置/。本地默认环境,便于启动

我将我的设置拆分如下

settings/
     |
     |- base.py
     |- dev.py
     |- prod.py  

我们有三种环境

dev 暂存 生产

现在,显然登台和生产应该有尽可能相似的环境。所以我们同时保留了prod.py。

但是在这种情况下,我必须确定正在运行的服务器是生产服务器。@T。斯通的回答帮助我写了如下支票。

from socket import gethostname, gethostbyname  
PROD_HOSTS = ["webserver1", "webserver2"]

DEBUG = False
ALLOWED_HOSTS = [gethostname(), gethostbyname(gethostname()),]


if any(host in PROD_HOSTS for host in ALLOWED_HOSTS):
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True  

不要使用settings.py,而是使用下面的布局:

.
└── settings/
    ├── __init__.py  <= not versioned
    ├── common.py
    ├── dev.py
    └── prod.py

py是您的大部分配置所在的位置。

py从common中导入所有内容,并覆盖它需要覆盖的内容:

from __future__ import absolute_import # optional, but I like it
from .common import *

# Production overrides
DEBUG = False
#...

类似地,dev.py从common.py导入所有内容,并覆盖它需要覆盖的内容。

最后,__init__.py是你决定加载哪些设置的地方,也是你存储秘密的地方(因此这个文件不应该被版本化):

from __future__ import absolute_import
from .prod import *  # or .dev if you want dev

##### DJANGO SECRETS
SECRET_KEY = '(3gd6shenud@&57...'
DATABASES['default']['PASSWORD'] = 'f9kGH...'

##### OTHER SECRETS
AWS_SECRET_ACCESS_KEY = "h50fH..."

我喜欢这个解决方案的原因是:

所有东西都在您的版本控制系统中,除了机密信息 大多数配置都在一个地方:common.py。 特定于产品的东西放在prod。py中,特定于开发的东西放在dev。py中。这很简单。 你可以在prod.py或dev.py中覆盖common.py中的内容,也可以覆盖__init__.py中的任何内容。 这是简单的python。没有重新导入黑客。