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

目前,我正在将所有常量添加到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。没有重新导入黑客。

其他回答

为了在不同的环境中使用不同的设置配置,创建不同的设置文件。在部署脚本中,使用——settings=<my-settings.py>参数启动服务器,通过该参数可以在不同的环境中使用不同的设置。

使用这种方法的好处:

Your settings will be modular based on each environment You may import the master_settings.py containing the base configuration in the environmnet_configuration.py and override the values that you want to change in that environment. If you have huge team, each developer may have their own local_settings.py which they can add to the code repository without any risk of modifying the server configuration. You can add these local settings to .gitnore if you use git or .hginore if you Mercurial for Version Control (or any other). That way local settings won't even be the part of actual code base keeping it clean.

我认为最好的解决方案是@T提出的。但是我不知道为什么在Django中不使用DEBUG标志。我写下面的代码为我的网站:

if DEBUG:
    from .local_settings import *

简单的解决方案总是比复杂的解决方案好。

对于我的大多数项目,我使用以下模式:

创建settings_base.py,其中存储所有环境的通用设置 每当我需要使用具有特定要求的新环境时,我都会创建新的设置文件(例如。Settings_local.py),它继承settings_base.py的内容,并覆盖/添加适当的设置变量(从settings_base import *)

(要使用自定义设置文件运行manage.py,只需使用——settings命令option: manage.py <命令>——settings=settings_you_wish_to_use.py)

我在manage.py中区分它,并创建了两个单独的设置文件:local_settings.py和prod_settings.py。

在manage.py中检查服务器是本地服务器还是生产服务器。如果它是本地服务器,它将加载local_settings.py,如果它是生产服务器,它将加载prod_settings.py。基本上是这样的:

#!/usr/bin/env python
import sys
import socket
from django.core.management import execute_manager 

ipaddress = socket.gethostbyname( socket.gethostname() )
if ipaddress == '127.0.0.1':
    try:
        import local_settings # Assumed to be in the same directory.
        settings = local_settings
    except ImportError:
        import sys
        sys.stderr.write("Error: Can't find the file 'local_settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file local_settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
        sys.exit(1)
else:
    try:
        import prod_settings # Assumed to be in the same directory.
        settings = prod_settings    
    except ImportError:
        import sys
        sys.stderr.write("Error: Can't find the file 'prod_settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file prod_settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
        sys.exit(1)

if __name__ == "__main__":
    execute_manager(settings)

我发现将设置文件分开到两个单独的文件中更容易,而不是在设置文件中做大量的if。

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因素宝贝! 设置/。本地默认环境,便于启动