处理本地开发和生产服务器设置的推荐方法是什么?其中一些(如常量等)可以在两者中进行更改/访问,但其中一些(如静态文件的路径)需要保持不同,因此每次部署新代码时都不应覆盖它们.
目前,我正在添加所有常量settings.py
.但每次我在本地更改一些常量,我必须将其复制到生产服务器并编辑文件以进行生产特定的更改...... :(
编辑:看起来这个问题没有标准答案,我接受了最流行的方法.
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
在网站有特定的设置:
在基本文件中settings/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 )
在文件生成设置文件中settings/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上提出了一个示例项目布局模板.
在settings.py
:
try: from local_settings import * except ImportError as e: pass
你可以覆盖所需的东西local_settings.py
; 它应该远离你的版本控制.但既然你提到复制,我猜你没有使用;)
而不是settings.py
,使用此布局:
. ??? settings/ ??? __init__.py <= not versioned ??? common.py ??? dev.py ??? prod.py
common.py
是您的大多数配置存在的地方.
prod.py
从普通中导入所有内容,并覆盖它需要覆盖的任何内容:
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
.这很简单.
您可以从覆盖的东西common.py
在prod.py
或者dev.py
,你可以覆盖任何东西__init__.py
.
这是直截了当的蟒蛇.没有重新进口黑客.
我使用了Harper Shelby发布的"if DEBUG"设置风格的略微修改版本.显然取决于环境(win/linux/etc.),代码可能需要稍微调整一下.
我过去使用"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设置文件方法).
我使用了settings_local.py和settings_production.py.在尝试了几个选项之后,我发现只需简单地让两个设置文件感觉简单快捷,就很容易浪费时间使用复杂的解决方案.
当您为Django项目使用mod_python/mod_wsgi时,您需要将其指向您的设置文件.如果您将其指向本地服务器上的app/settings_local.py和生产服务器上的app/settings_production.py,那么生活将变得轻松.只需编辑相应的设置文件并重新启动服务器(Django开发服务器将自动重启).
我在django-split-settings的帮助下管理我的配置.
它是默认设置的替代品.它很简单,但可配置.并且不需要重构您的现有设置.
这是一个小例子(文件example/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
设置的博客文章django-split-sttings
.看一看!
大多数这些解决方案的问题是,你要么有应用本地设置前的常见的,或经过它们.
所以不可能覆盖像这样的东西
特定于env的设置定义了memcached池的地址,在主设置文件中,该值用于配置缓存后端
特定于env的设置将应用程序/中间件添加或删除为默认设置
同时.
可以使用ConfigParser类使用"ini"式配置文件来实现一个解决方案.它支持多个文件,惰性字符串插值,默认值和许多其他好东西.一旦加载了许多文件,就可以加载更多的文件,如果有的话,它们的值将覆盖以前的文件.
您加载一个或多个配置文件,具体取决于机器地址,环境变量甚至以前加载的配置文件中的值.然后,您只需使用已解析的值来填充设置.
我成功使用的一个策略是:
加载默认defaults.ini
文件
检查机器名称,并加载它相匹配的逆转FQDN,从最短的匹配最长匹配的所有文件(所以,我装net.ini
的话net.domain.ini
,那么net.domain.webserver01.ini
,以前的每一个可能会覆盖值).此帐户也适用于开发人员的计算机,因此每个人都可以为本地开发设置其首选数据库驱动程序等
检查是否声明了"群集名称",在这种情况下,加载cluster.cluster_name.ini
可以定义数据库和缓存IP等内容
作为您可以通过此实现的一个示例,您可以定义"子域"值per-env,然后在默认设置(as hostname: %(subdomain).whatever.net
)中使用它来定义django需要工作的所有必需的主机名和cookie.
这就像我可以获得的干,大多数(现有)文件只有3或4个设置.除此之外,我必须管理客户配置,因此存在一组额外的配置文件(包括数据库名称,用户和密码,分配的子域等),每个客户一个或多个.
可以根据需要将其扩展为低或高,只需在配置文件中输入要为每个环境配置的密钥,并且一旦需要新配置,将先前的值放在默认配置中,并覆盖它在必要时.
该系统经证明可靠,可与版本控制配合使用.它已被用于长时间管理两个独立的应用程序集群(每台机器的15个或更多单独的django站点实例),有超过50个客户,其中集群根据系统管理员的情绪改变大小和成员. .
TL; DR:关键是要修改os.environment
导入之前settings/base.py
的任何settings/
,这将大大简化事情.
想到所有这些交织在一起的文件让我很头疼.组合,导入(有时是有条件的),覆盖,修补已在案例DEBUG
设置中设置的内容稍后更改.什么样的恶梦!
多年来,我经历了所有不同的解决方案.他们都有点工作,但管理起来很痛苦.WTF!我们真的需要一切麻烦吗?我们从一个settings.py
文件开始.现在我们需要一个文档才能正确地将所有这些组合在一起!
我希望我终于用下面的解决方案击中(我的)最佳位置.
让我们回顾一下目标(一些常见的,一些是我的)秘密保密 - 不要将它们存放在回购中.
通过环境设置,12因子样式设置/读取密钥和秘密.
有明智的后备默认值.理想情况下,对于本地开发,除默认值之外不需要任何其他内容.
...但尝试保持默认生产安全.最好错过本地设置覆盖,而不是必须记住调整生产安全的默认设置.
能够以DEBUG
对其他设置有影响的方式打开/关闭(例如,使用javascript压缩或不压缩).
在目的设置(如本地/测试/登台/生产)之间切换应该仅基于,仅此DJANGO_SETTINGS_MODULE
而已.
...但允许通过环境设置进一步参数化DATABASE_URL
.
...还允许他们使用不同的目的设置并在本地并排运行,例如.本地开发人员机器上的生产设置,访问生产数据库或烟雾测试压缩样式表.
如果未明确设置环境变量(至少需要空值),尤其是在生产环境中,则失败,例如.EMAIL_HOST_PASSWORD
.
DJANGO_SETTINGS_MODULE
在django-admin startproject期间响应manage.py中的默认设置
条件语句保持到最低限度,如果条件的旨意环境类型(例如,用于生产集日志文件和它的旋转),覆盖设置在相关的旨意设置文件.
不要让django读取DJANGO_SETTINGS_MODULE设置形成文件.
啊! 想想这是多么美好.如果您需要在启动django进程之前将文件(如docker env)读入环境中.
不要在项目/应用程序代码中覆盖DJANGO_SETTINGS_MODULE,例如.基于主机名或进程名称.
如果你懒得设置环境变量(比如setup.py test
),就可以在运行项目代码之前在工具中执行.
避免魔法和修补django如何读取它的设置,预处理设置但不会干扰.
没有复杂的逻辑基础废话.配置应该是固定的,具体化不是动态计算的.提供回退默认值就足够了.
你真的想调试吗,为什么本地你有正确的设置,但在远程服务器上生产,在一百台机器上,计算方式不同?哦! 单元测试?对于设置?真的吗?
我的策略是由优秀的Django的ENVIRON与使用的ini
样式文件,提供os.environment
当地发展的默认设置,一些最起码的,短settings/
的是有一个文件
import settings/base.py
后的os.environment
是从一个设置INI
文件.这有效地为我们提供了一种注入设置.
这里的技巧是os.environment
在导入之前进行修改settings/base.py
.
要查看完整示例,请执行repo: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
本地开发的默认值.一个秘密文件,主要用于设置所需的环境变量.如果在本地开发中不需要它们,则将它们设置为空值.我们在这里提供默认值,settings/base.py
如果环境中缺少任何其他机器,则不会失败.
这里发生的是从中加载环境settings/.env
,然后从中导入常用设置settings/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
对于生产,我们不应该期望一个环境文件,但如果我们正在测试一些东西,那么更容易拥有一个.但无论如何,以免提供内联的默认值,因此settings/base.py
可以做出相应的响应.
environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False') from .base import *
这里主要关心的是点DEBUG
和ASSETS_DEBUG
覆盖,他们将被应用到蟒蛇os.environ
,只有当他们从环境和文件丢失.
这些将是我们的生产默认值,无需将它们放在环境或文件中,但如果需要,可以覆盖它们.整齐!
这些是你的大多数香草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/.env - 本地默认值,便于启动
我也在与Laravel合作,我喜欢在那里的实现。我试图模仿它,并将其与T.Stone提出的解决方案结合起来(见上):
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
也许这样的事情会帮助您。