How to manage local vs production settings in Django?
为本地开发和生产服务器处理设置的建议方法是什么?其中一些(如常量等)可以在两者中进行更改/访问,但其中一些(如到静态文件的路径)需要保持不同,因此不应在每次部署新代码时被覆盖。
目前,我正在将所有常量添加到
编辑:看起来这个问题没有标准答案,我接受了最流行的方法。
两勺django:django 1.5的最佳实践建议对设置文件使用版本控制并将文件存储在单独的目录中:
1 2 3 4 5 6 7 8 9 10 11 | project/ app1/ app2/ project/ __init__.py settings/ __init__.py base.py local.py production.py manage.py |
在基本文件
1 2 3 | INSTALLED_APPS = ( # common apps... ) |
在本地开发设置文件
1 2 3 4 5 6 | from project.settings.base import * DEBUG = True INSTALLED_APPS += ( 'debug_toolbar', # and other apps for local development ) |
在文件生产设置文件
1 2 3 4 5 6 | from project.settings.base import * DEBUG = False INSTALLED_APPS += ( # other apps for production site ) |
然后在运行django时,添加
1 2 3 4 5 | # 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上建立了一个示例项目布局模板。
在
1 2 3 4 | try: from local_settings import * except ImportError as e: pass |
您可以覆盖
不使用
1 2 3 4 5 6 | . └── settings/ ├── __init__.py <= not versioned ├── common.py ├── dev.py └── prod.py |
1 2 3 4 5 6 | from __future__ import absolute_import # optional, but I like it from .common import * # Production overrides DEBUG = False #... |
同样,
最后,
1 2 3 4 5 6 7 8 9 | 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..." |
我喜欢这个解决方案的原因是:
我使用了哈珀·谢尔比发布的稍微修改过的"if-debug"设置样式。显然,根据环境(win/linux/etc),代码可能需要稍微调整一下。
我以前使用过"if-debug",但我发现有时候我需要使用deubg设置为false进行测试。我真正想要区分的是环境是生产还是开发,这给了我选择调试级别的自由。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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设置文件方法)。
我使用设置"local.py"和设置"production.py"。在尝试了几个选项后,我发现,当简单地拥有两个设置文件时,很容易在复杂的解决方案中浪费时间。
在Django项目中使用mod_python/mod_wsgi时,需要将其指向设置文件。如果您将其指向本地服务器上的app/settings_local.py和生产服务器上的app/settings_production.py,那么生活就变得简单了。只需编辑适当的设置文件并重新启动服务器(Django Development Server将自动重新启动)。
我使用Django拆分设置管理配置。
它是默认设置的替代品。它很简单,但也是可配置的。不需要重构现有设置。
下面是一个小例子(文件
1 2 3 4 5 6 7 8 9 10 11 12 | 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() ) |
就是这样。
更新我写了一篇关于用
大多数这些解决方案的问题在于,要么在普通解决方案之前或之后应用本地设置。
所以不可能超越
- env特定的设置定义memcached池的地址,在主设置文件中,该值用于配置缓存后端
- 特定于env的设置将应用程序/中间件添加或删除到默认的应用程序/中间件中。
同时。
一个解决方案可以使用带有configParser类的"ini"样式的配置文件来实现。它支持多个文件、延迟字符串插值、默认值和许多其他优点。一旦加载了多个文件,就可以加载更多的文件,如果有的话,这些文件的值将覆盖以前的文件。
根据机器地址、环境变量甚至之前加载的配置文件中的值,可以加载一个或多个配置文件。然后您只需使用解析的值来填充设置。
我成功使用的一个策略是:
- 加载默认的
defaults.ini 文件 - 检查机器名,并加载所有与反向的fqdn匹配的文件,从最短匹配到最长匹配(因此,我加载了
net.ini ,然后加载了net.domain.ini ,然后加载了net.domain.webserver01.ini ,每个文件都可能覆盖前面的值)。这个帐户也适用于开发人员的机器,因此每个机器都可以为本地开发设置其首选的数据库驱动程序等。 - 检查是否声明了"集群名称",在这种情况下,加载
cluster.cluster_name.ini ,它可以定义数据库和缓存IP等内容。
作为一个可以用这个实现的示例,您可以为每个env定义一个"子域"值,然后在默认设置(如
这是我可以得到的干燥,大多数(现有)文件只有3或4个设置。除此之外,我还必须管理客户配置,因此存在一组额外的配置文件(包括数据库名称、用户和密码、分配的子域等),每个客户一个或多个。
您可以根据需要将其调低或调高,只需在配置文件中输入要为每个环境配置的键,一旦需要新的配置,就将以前的值调到默认配置中,并在必要时覆盖它。
该系统已被证明是可靠的,并与版本控制工作良好。它已经用于管理两个独立的应用程序集群(每台机器15个或更多独立的django站点实例),拥有50多个客户,其中集群的大小和成员根据系统管理员的心情而变化……
tl;dr:诀窍是在任何
想到这些相互缠绕的文件,我就头疼。合并、导入(有时是有条件的)、重写、修补已设置的内容,以防稍后
这些年来,我经历了所有不同的解决方案。它们都有点作用,但管理起来很痛苦。世界跆拳道联盟!我们真的需要这么多麻烦吗?我们从一个
我希望我终于找到了下面的解决方案。好的。让我们回顾一下目标(一些共同的,一些我的)
保守秘密-不要把它们储存在回购协议中。好的。
通过环境设置设置/读取密钥和机密,12因子样式。好的。
具有合理的回退默认值。对于本地开发来说,除了默认值之外,您不需要其他任何东西。好的。
…但是尽量保证默认的生产安全。最好在本地忽略设置覆盖,比必须记住调整默认设置安全生产。好的。
能够以对其他设置有影响的方式(例如,是否使用Javascript压缩)打开/关闭
在目的设置之间的切换,如本地/测试/分段/生产,应该只基于
…但是允许通过环境设置进行进一步的参数化,如
…还允许他们使用不同的用途设置并在本地并排运行它们,例如本地开发人员计算机上的生产设置,以访问生产数据库或烟雾测试压缩样式表。好的。
如果没有显式设置环境变量(至少需要空值),特别是在生产环境中,例如
在Django管理启动项目期间响应在manage.py中设置的默认
将条件保持在最小值,如果条件是目标环境类型(例如,对于生产集日志文件及其旋转),则覆盖关联目标设置文件中的设置。好的。
不要
不要让django读取django_设置_模块设置形成文件。呸!想想这是多么的元。如果你需要一个文件(比如Dockerenv)在启动Django进程之前将其读到环境中。好的。
不要覆盖项目/应用程序代码中的django_设置_模块,例如基于主机名或进程名。如果您懒于设置环境变量(如
避免魔术和补丁如何德扬戈读取它的设置,预处理设置,但不要干涉以后。好的。
没有复杂的逻辑基础的废话。配置应该是固定的和物化的,而不是动态计算的。提供一个回退默认值只是这里的逻辑而已。您真的想调试,为什么在本地设置正确,但在远程服务器上生产,在一百台机器中,有什么计算方法不同吗?哦!单元测试?设置?真的吗?好的。
解决方案
我的策略包括与
这里的技巧是在导入
要查看完整的示例,请执行回购:https://github.com/wooyek/django-settings-strategy好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | . │ manage.py ├───data └───website ├───settings │ │ __init__.py <-- imports local for compatybility │ │ base.py <-- almost all the settings, reads from proces environment │ │ local.py <-- a few modifications for local development │ │ production.py <-- ideally is empy 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 |
设置/
本地开发的默认值。一个秘密文件,主要用于设置所需的环境变量。如果在本地开发中不需要这些值,请将它们设置为空值。我们在这里提供缺省值,而不是在
这里发生的是从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 |
设置/生产.py
对于生产,我们不应该期望有一个环境文件,但是如果我们在测试一些东西,就更容易有一个。但是无论如何,LEST提供了很少的内联默认值,因此
1 2 | environ.Env.read_env(Path(__file__) /"production.env", DEBUG='False', ASSETS_DEBUG='False') from .base import * |
这里的主要关注点是
这些将是我们的生产默认值,不需要将它们放在环境或文件中,但如果需要,可以覆盖它们。整洁!好的。设置/base.py
这些都是您最普通的django设置,有一些条件和很多从环境中读取它们。几乎所有的东西都在这里,保持所有目标环境的一致性和尽可能相似。好的。
主要区别如下(我希望这些是不言而喻的):好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # 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'), } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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',) |
最后一位显示了这里的功率。
实际上,我们有一个复杂的重要性层次:好的。
好啊。
记住,settings.py是一个实时代码文件。假设在生产环境中没有调试集(这是一种最佳实践),可以执行以下操作:
1 2 3 4 | if DEBUG: STATIC_PATH = /path/to/dev/files else: STATIC_PATH = /path/to/production/files |
非常基本,但是理论上,您可以根据调试的值或您想要使用的任何其他变量或代码检查,达到任何复杂程度。
我也在与Laravel合作,我喜欢那里的实现。我试图模仿它,并将其与T.Stone提出的解决方案结合起来(见上图):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | PRODUCTION_SERVERS = ['*.webfaction.com','*.whatever.com',] def check_env(): for item in PRODUCTION_SERVERS: match = re.match(r"(^." + item +"$)", socket.gethostname()) if match: return True if check_env(): PRODUCTION = True else: PRODUCTION = False DEBUG = not PRODUCTION |
也许像这样的东西会对你有所帮助。
我使用了JPartogi上面提到的一个变体,我发现它有点短:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import platform from django.core.management import execute_manager computername = platform.node() try: settings = __import__(computername + '_settings') except ImportError: import sys sys.stderr.write("Error: Can't find the file '%r_settings.py' in the directory containing %r. It appears you've customized things. You'll have to run django-admin.py, passing it your settings module. (If the file local_settings.py does indeed exist, it's causing an ImportError somehow.) " % (computername, __file__)) sys.exit(1) if __name__ =="__main__": execute_manager(settings) |
基本上,在每台计算机(开发或生产)上,我都有相应的主机名_settings.py文件,可以动态加载。
对于我的大多数项目,我使用以下模式:
(要使用自定义设置文件运行manage.py,只需使用--settings命令选项:
我对这个问题的解决方案也有点混合了一些已经在这里说明的解决方案:
- 我保存了一个名为
local_settings.py 的文件,它在dev中有USING_LOCAL = True 的内容,在prod中有USING_LOCAL = False 的内容。 - 在
settings.py 中,我对该文件执行导入,以获取USING_LOCAL 设置。
然后,我将所有依赖于环境的设置都基于该设置:
1 2 3 4 5 | DEBUG = USING_LOCAL if USING_LOCAL: # dev database settings else: # prod database settings |
我更喜欢使用两个单独的settings.py文件,因为我可以将设置保持在单个文件中,而不是将它们分散在多个文件中。像这样,当我更新一个设置时,我不会忘记为两个环境都这样做。
当然,每种方法都有其缺点,这一点也不例外。这里的问题是,每当我将更改推到生产中时,我不能覆盖
还有Django Classy设置。我个人很喜欢它。它是由Django IRC上最活跃的人建造的。您将使用环境变量来设置内容。
http://django-classy-settings.readthedocs.io/en/latest(最新)/
为了在不同的环境中使用不同的
使用这种方法的好处:
您的设置将根据每个环境进行模块化。
您可以导入包含
如果您有庞大的团队,那么每个开发人员可能都有自己的
1-在应用程序中创建一个新文件夹并为其命名设置。
2-现在在其中创建一个新的in it.py文件并在其中写入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | from .base import * try: from .local import * except: pass try: from .production import * except: pass |
3-在设置文件夹中创建三个新文件:local.py和production.py以及base.py。
4-在base.py中,复制先前settings.p文件夹中的所有内容,并用不同的名称重命名,比如说old_settings.py。
5-在base.py中,更改基本路径以指向新的设置路径
old path->base_dir=os.path.dirname(os.path.dirname(os.path.abspath(file)))
new path->base_dir=os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(file)))
现在,通过这种方式,项目目录可以结构化,并且可以在生产和本地开发之间进行管理。
作为维护不同文件的替代方法,如果您愿意:如果您使用Git或任何其他VCS将代码从本地推送到服务器,您可以做的是将设置文件添加到.gitignore。
这将允许您在两个地方都有不同的内容,没有任何问题。因此,在服务器上,您可以配置一个独立版本的settings.py,在本地进行的任何更改都不会反映在服务器上,反之亦然。
此外,它还将从Github中删除settings.py文件,这是一个很大的错误,我已经看到许多新手在做这个。
我在manage.py中对其进行了区分,并创建了两个单独的设置文件:local_settings.py和prod_settings.py。
在manage.py中,我检查服务器是本地服务器还是生产服务器。如果是本地服务器,它将加载local_settings.py;如果是生产服务器,它将加载prod_settings.py。基本上是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #!/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. You'll have to run django-admin.py, passing it your settings module. (If the file local_settings.py does indeed exist, it's causing an ImportError somehow.) " % __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. You'll have to run django-admin.py, passing it your settings module. (If the file prod_settings.py does indeed exist, it's causing an ImportError somehow.) " % __file__) sys.exit(1) if __name__ =="__main__": execute_manager(settings) |
我发现将设置文件分为两个单独的文件要容易一些,而不是在设置文件中进行大量的IFS。
我的设置拆分如下
1 2 3 4 5 | settings/ | |- base.py |- dev.py |- prod.py |
我们有3个环境
- DEV
- 分期
- 生产
很明显,分段生产应该有尽可能多的相似环境。所以我们保留了
但有一个案例,我必须确定运行的服务器是生产服务器。@斯通的回答帮助我写了如下支票。
1 2 3 4 5 6 7 8 9 10 | 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 |
我认为最好的解决方案是由@t.stone提出的,但我不知道为什么不在django中使用debug标志。我为我的网站编写以下代码:
1 2 | if DEBUG: from .local_settings import * |
简单的解决方案总是比复杂的解决方案好。
制作多个版本的settings.py是12因子应用程序方法的反模式。使用python decouple或django environ代替。
我发现这里的回答很有帮助。(这是否得到了更明确的解决?最后一个回答是一年前。)在考虑了所有列出的方法之后,我提出了一个解决方案,但在这里我没有看到。
我的标准是:
- 所有内容都应该在源代码管理中。我不喜欢到处胡闹。
- 理想情况下,将设置保存在一个文件中。如果我看得不对,我就会忘记这些事情。)
- 没有要部署的手动编辑。应该能够使用单个结构命令测试/推送/部署。
- 避免将开发设置泄漏到生产中。
- 尽可能靠近"标准"(*咳嗽*)django布局。
我认为打开主机是有意义的,但后来发现真正的问题是不同环境下的不同设置,并有了一个"啊哈"的时刻。我将此代码放在settings.py文件的末尾:
1 2 3 4 5 6 7 8 9 10 11 12 13 | try: os.environ['DJANGO_DEVELOPMENT_SERVER'] # throws error if unset DEBUG = True TEMPLATE_DEBUG = True # This is naive but possible. Could also redeclare full app set to control ordering. # Note that it requires a list rather than the generated tuple. INSTALLED_APPS.extend([ 'debug_toolbar', 'django_nose', ]) # Production database settings, alternate static/media paths, etc... except KeyError: print 'DJANGO_DEVELOPMENT_SERVER environment var not set; using production settings' |
这样,应用程序默认为生产设置,这意味着您的开发环境显式地"白名单"。忘记在本地设置环境变量要比从另一个角度设置环境变量安全得多,并且忘记在生产环境中设置一些内容,并使用一些开发人员设置。
在本地开发时,无论是从shell还是在.bash_概要文件中还是在任何地方:
1 | $ export DJANGO_DEVELOPMENT_SERVER=yep |
(或者,如果您正在Windows上开发,可以通过控制面板或其他所谓的工具来设置…Windows总是让它变得如此模糊,以至于您可以设置环境变量。)
使用这种方法,开发人员的设置都在一个(标准)位置,并在需要时简单地覆盖生产设置。任何与开发设置有关的混乱都应该是完全安全的,以承诺在不影响生产的情况下进行源代码控制。