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

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

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


当前回答

在settings.py:

try:
    from local_settings import *
except ImportError as e:
    pass

你可以覆盖local_settings.py;它应该不在你的版本控制范围之内。但既然你提到了复制,我猜你没有使用;)

其他回答

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.py是一个活动代码文件。假设您没有在生产中设置DEBUG(这是一个最佳实践),您可以执行如下操作:

if DEBUG:
    STATIC_PATH = /path/to/dev/files
else:
    STATIC_PATH = /path/to/production/files

非常基本,但是理论上,您可以根据DEBUG的值或您想使用的任何其他变量或代码检查来提高任何复杂级别。

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

if DEBUG:
    from .local_settings import *

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

我使用settings_local.py和settings_production.py。在尝试了几个选项后,我发现当简单地拥有两个设置文件时,很容易在复杂的解决方案上浪费时间。

当你在Django项目中使用mod_python/mod_wsgi时,你需要将它指向你的设置文件。如果你把它指向本地服务器上的app/settings_local.py和生产服务器上的app/settings_production.py,那么事情就变得简单了。只需编辑适当的设置文件并重新启动服务器(Django开发服务器将自动重新启动)。

我使用Harper Shelby发布的“if DEBUG”风格设置的稍微修改版本。显然,根据环境(win/linux/等等),代码可能需要稍作调整。

我在过去使用“if DEBUG”,但我发现偶尔我需要用DEUBG设置为False进行测试。我真正想要区分的是环境是生产环境还是开发环境,这给了我选择DEBUG级别的自由。

PRODUCTION_SERVERS = ['WEBSERVER1','WEBSERVER2',]
if os.environ['COMPUTERNAME'] in PRODUCTION_SERVERS:
    PRODUCTION = True
else:
    PRODUCTION = False

DEBUG = not PRODUCTION
TEMPLATE_DEBUG = DEBUG

# ...

if PRODUCTION:
    DATABASE_HOST = '192.168.1.1'
else:
    DATABASE_HOST = 'localhost'

我仍然认为这种设置方式是一种正在进行中的工作。我还没有看到任何一种方法来处理Django设置,它涵盖了所有的基础,同时设置起来也不是很麻烦(我不喜欢5x设置文件方法)。