Как управлять локальными и производственными настройками в Django? - PullRequest
276 голосов
/ 26 октября 2009

Каков рекомендуемый способ обработки настроек для локальной разработки и рабочего сервера? Некоторые из них (например, константы и т. Д.) Могут быть изменены / доступны в обоих, но некоторые из них (например, пути к статическим файлам) должны оставаться разными и, следовательно, не должны перезаписываться при каждом развертывании нового кода.

В настоящее время я добавляю все константы к settings.py. Но каждый раз, когда я изменяю некоторую константу локально, мне приходится копировать ее на рабочий сервер и редактировать файл для конкретных производственных изменений ... :(

Редактировать: похоже, что нет стандартного ответа на этот вопрос, я принял самый популярный метод.

Ответы [ 22 ]

276 голосов
/ 10 марта 2013

Two Scoops of Django: Best Practices для 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.

124 голосов
/ 27 октября 2009

В settings.py:

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

Вы можете переопределить то, что нужно в local_settings.py; тогда он должен оставаться вне вашего контроля версий. Но так как вы упоминаете о копировании, я думаю, вы не используете ни один;)

66 голосов
/ 09 марта 2013

Вместо 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..."

Что мне нравится в этом решении:

  1. Все в вашей системе управления версиями, кроме секретов
  2. Большая часть конфигурации находится в одном месте: common.py.
  3. Вещи, специфичные для продукта, входят в prod.py, вещи, специфичные для разработчика, входят в dev.py. Это просто.
  4. Вы можете переопределить материал из common.py в prod.py или dev.py, и вы можете переопределить что-нибудь в __init__.py.
  5. Это простой питон. Нет повторного импорта хаков.
20 голосов
/ 26 октября 2009

Я использую слегка модифицированную версию стиля настроек "if DEBUG", который опубликовал Harper Shelby. Очевидно, что в зависимости от среды (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).

14 голосов
/ 26 октября 2009

Я использую settings_local.py и settings_production.py. Попробовав несколько вариантов, я обнаружил, что со сложными решениями легко тратить время, когда два файла настроек кажутся легкими и быстрыми.

Когда вы используете mod_python / mod_wsgi для вашего проекта Django, вам нужно указать его в файле настроек. Если вы укажете на app / settings_local.py на локальном сервере и app / settings_production.py на рабочем сервере, жизнь станет проще. Просто отредактируйте соответствующий файл настроек и перезапустите сервер (сервер разработки Django перезапустится автоматически).

7 голосов
/ 02 ноября 2015

Я управляю своими настройками с помощью 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. Посмотри!

6 голосов
/ 01 марта 2012

Проблема большинства этих решений заключается в том, что к вашим локальным настройкам применяются до общих настроек или после их.

Так что невозможно переопределить такие вещи, как

  • специфичные для env настройки определяют адреса для пула memcached, а в основном файле настроек это значение используется для настройки бэкенда кеша
  • специфичные для env настройки добавляют или удаляют приложения / промежуточное ПО по умолчанию

одновременно.

Одно решение может быть реализовано с использованием конфигурационных файлов в стиле "ini" с классом ConfigParser. Он поддерживает несколько файлов, ленивую интерполяцию строк, значения по умолчанию и много других полезностей. После загрузки нескольких файлов можно загружать больше файлов, и их значения будут заменять предыдущие, если они есть.

Вы загружаете один или несколько файлов конфигурации в зависимости от адреса машины, переменных среды и даже значений в ранее загруженных файлах конфигурации. Затем вы просто используете проанализированные значения, чтобы заполнить настройки.

Одна стратегия, которую я успешно использовал, была:

  • Загрузить файл по умолчанию defaults.ini файл
  • Проверьте имя машины и загрузите все файлы, которые соответствуют полностью измененному FQDN, от самого короткого совпадения до самого длинного совпадения (поэтому я загрузил net.ini, затем net.domain.ini, затем net.domain.webserver01.ini, каждый из которых, возможно, переопределяет значения предыдущего). Эта учетная запись также предназначена для машин разработчиков, поэтому каждый из них может настроить предпочитаемый драйвер базы данных и т. Д. Для локальной разработки
  • Проверьте, объявлено ли «имя кластера», и в этом случае загрузите cluster.cluster_name.ini, который может определять такие вещи, как IP-адреса базы данных и кэша

В качестве примера того, чего вы можете достичь с помощью этого, вы можете определить значение «subdomain» для per-env, которое затем используется в настройках по умолчанию (как hostname: %(subdomain).whatever.net), чтобы определить все необходимые имена хостов и файлы cookie django. должен работать.

Это СУХОЙ, что я мог получить, большинство (существующих) файлов имели только 3 или 4 настройки. Кроме того, мне пришлось управлять конфигурацией клиента, поэтому существовал дополнительный набор файлов конфигурации (с такими именами, как имена баз данных, пользователи и пароли, назначенный поддомен и т. Д.), По одному или более для каждого клиента.

Можно масштабировать это значение настолько низко или настолько высоко, как необходимо, вы просто помещаете в файл конфигурации ключи, которые вы хотите настроить для каждой среды, и как только требуется новая конфигурация, поместите предыдущее значение в конфигурацию по умолчанию, и переопределите его, где необходимо.

Эта система доказала свою надежность и хорошо работает с контролем версий. Он долгое время использовался для управления двумя отдельными кластерами приложений (15 или более отдельных экземпляров сайта django на машину) с более чем 50 клиентами, где кластеры меняли размер и участников в зависимости от настроения системного администратора. .

5 голосов
/ 04 августа 2014

Я также работаю с Laravel, и мне нравится реализация там. Я попытался имитировать и комбинировать это с решением, предложенным Т. Стоуном (см. Выше):

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

Может быть, что-то подобное поможет тебе.

5 голосов
/ 12 мая 2017

TL; DR: хитрость заключается в том, чтобы изменить os.environment перед импортом settings/base.py в любой settings/<purpose>.py, это значительно упростит вещи.


От одной мысли о всех этих переплетающихся файлах у меня болит голова. Объединение, импорт (иногда условно), переопределение, исправление того, что уже было установлено в случае, если настройка DEBUG изменилась позже. Какой кошмар!

Через годы я прошел через все различные решения. Все они несколько работают, но им так больно управлять. WTF! Нам действительно нужны все эти хлопоты? Мы начали с одного settings.py файла. Теперь нам нужна документация только для того, чтобы правильно объединить все это в правильном порядке!

Я надеюсь, что наконец-то достиг (моей) сладости с решением ниже.

Давайте вспомним цели (некоторые общие, некоторые мои)

  1. Храните секреты в секрете - не храните их в репо.

  2. Установка / чтение ключей и секретов с помощью настроек среды, 12-факторный стиль .

  3. Имеют разумные запасные значения по умолчанию. В идеале для местного развития вам не нужно ничего, кроме значений по умолчанию.

  4. … но старайтесь обеспечить безопасность производства по умолчанию. Лучше пропустить настройку локально, чем необходимость помнить о необходимости настройки параметров по умолчанию, безопасных для производства.

  5. Иметь возможность включать / выключать DEBUG таким образом, чтобы это могло повлиять на другие настройки (например, с использованием сжатого JavaScript или нет).

  6. Переключение между целевыми настройками, такими как локальные / тестирование / подготовка / производство, должно основываться только на DJANGO_SETTINGS_MODULE, не более.

  7. … но разрешить дальнейшую параметризацию с помощью настроек среды, таких как DATABASE_URL.

  8. … также позволяют им использовать различные настройки назначения и запускать их локально рядом, например. настройка производства на локальной машине разработчика для доступа к производственной базе данных или сжатым таблицам стилей для дымовых испытаний.

  9. Ошибка, если переменная окружения не установлена ​​явно (требуется как минимум пустое значение), особенно на производстве, например. EMAIL_HOST_PASSWORD.

  10. Ответ по умолчанию DJANGO_SETTINGS_MODULE, заданный в manage.py во время django-admin startproject

  11. Сохранение условий на минимуме, если условие предназначенный тип среды (например, для файла журнала производственных наборов и его ротации), переопределить настройки в связанном целевом файле настроек.

Не надо

  1. Не позволяйте django читать настройки DJANGO_SETTINGS_MODULE из файла.
    Тьфу! Подумайте, как это мета. Если вам нужен файл (например, докер env) прочитайте это в среду перед запуском процесса django.

  2. Не переопределяйте DJANGO_SETTINGS_MODULE в коде вашего проекта / приложения, например. на основе имени хоста или имени процесса.
    Если вам лень устанавливать переменные среды (например, для setup.py test), сделайте это в инструментах непосредственно перед запуском кода проекта.

  3. Избегайте магии и исправлений того, как django читает свои настройки, предварительно обрабатывайте настройки, но не вмешивайтесь впоследствии.

  4. Нет сложной логики, основанной на глупости. Конфигурация должна быть фиксированной и материализованной, а не вычисленной на лету. Предоставление запасных значений по умолчанию здесь достаточно логики.
    Вы действительно хотите отладить, почему локально у вас есть правильный набор настроек, но на производстве на удаленном сервере, на одной из ста машин что-то вычисляется по-другому? Ой! Модульные тесты? Для настроек? Шутки в сторону?

Решение

Моя стратегия состоит из превосходных django-environment , используемых с файлами стиля ini, os.environment по умолчанию для локальной разработки, некоторые минимальные и короткие settings/<purpose>.py файлы, которые имеют import settings/base.py ПОСЛЕ os.environment был установлен из файла INI. Это эффективно дает нам своего рода инъекцию настроек.

Хитрость в том, чтобы изменить os.environment перед импортом settings/base.py.

Дляполный пример см. в репо: https://github.com/wooyek/django-settings-strategy

.
│   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

settings / .env

По умолчанию для локальной разработки.Секретный файл, в основном для установки необходимых переменных среды.Установите для них пустые значения, если они не требуются при локальной разработке.Здесь мы предоставляем значения по умолчанию, а не в settings/base.py для сбоя на любом другом компьютере, если они отсутствуют в среде.

settings / local.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 / production.py

Для производства нам не следует ожидать файл среды, но его проще получить, если мыТестирую что-то.Но в любом случае, чтобы не обеспечить несколько встроенных значений по умолчанию, поэтому settings/base.py может ответить соответственно.

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

Основными интересными моментами здесь являются переопределения DEBUG и ASSETS_DEBUG, они будут применяться ТОЛЬКО к питону os.environ, если они ПРОПУСКАЮТСЯ из среды и файла.

Это будут наши производственные настройки по умолчанию, нет необходимости помещать их в среду или файл, но они могут быть переопределены при необходимости.Neat!

settings / 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 и даже то, что может быть переопределено настройкой среды!Ура!

По сути, мы имеем смешанную иерархию важности:

  1. settings / .py - устанавливает значения по умолчанию в зависимости от цели, не хранит секреты
  2. settings / base.py - в основном управляется средой
  3. настройки среды процесса - 12 фактор baby!
  4. settings / .env - локальные настройки по умолчанию для простого запуска
4 голосов
/ 26 октября 2009

Помните, что settings.py - это файл с живым кодом. Предполагая, что у вас нет DEBUG, установленного на производстве (что является наилучшей практикой), вы можете сделать что-то вроде:

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

Довольно просто, но теоретически вы можете подняться на любой уровень сложности, основываясь только на значении DEBUG - или любой другой переменной или проверке кода, которые вы хотели использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...