¿Cómo administrar la configuración local frente a la producción en Django?

298

¿Cuál es la forma recomendada de manejar la configuración para el desarrollo local y el servidor de producción? Algunos de ellos (como las constantes, etc.) se pueden cambiar / acceder en ambos, pero algunos de ellos (como las rutas a los archivos estáticos) deben permanecer diferentes y, por lo tanto, no se deben sobrescribir cada vez que se implementa el nuevo código.

Actualmente, estoy agregando todas las constantes a settings.py. Pero cada vez que cambio algo constante localmente, tengo que copiarlo al servidor de producción y editar el archivo para cambios específicos de producción ... :(

Editar: parece que no hay una respuesta estándar a esta pregunta, acepté el método más popular.

akv
fuente
Por favor, eche un vistazo a las configuraciones de django .
JJD
2
El método aceptado ya no es el más popular.
Daniel
2
django-split-settings es muy fácil de usar. No requiere reescribir ninguna configuración predeterminada.
sobolevn
debería usar el archivo base.py y en su local.py "de .base import *", lo mismo en su production.py "de .base import *", necesita ejecutar su proyecto con: python manage.py runserver - settings = project_name.settings.local
Roberth Solís

Respuestas:

127

En settings.py:

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

Puede anular lo que necesita en local_settings.py; entonces debería mantenerse fuera de su control de versiones. Pero como mencionas copiar, supongo que no usas ninguno;)

ohnoes
fuente
3
Para facilitar el seguimiento / despliegue de nuevas configuraciones, use un "local_settings.py" en las máquinas de producción / prueba y ninguno en el desarrollo.
John Mee el
8
Así es como lo hago: agregando esas líneas al final de settings.py para que puedan anular la configuración predeterminada
daonb
61
Este enfoque significa que tiene código no versionado ejecutándose en desarrollo y producción. Y cada desarrollador tiene una base de código diferente. Llamo anti-patrón aquí.
pydanny
8
@pydanny El problema es que Django almacena su configuración en un archivo .py. No puede esperar que todos los desarrolladores y el servidor de producción usen la misma configuración, por lo que debe modificar este archivo .py o implementar alguna solución alternativa (archivos .ini, entorno, etc.).
Tupteq
3
Yo prefiero llamar el módulo settings_localen lugar de local_settingspara agruparlo con los settings.pyde las listas de carpetas por orden alfabético. Manténgase settings_local.pyfuera del control de versiones, .gitignoreya que las credenciales no pertenecen a Git. Imagínese abierto abasteciéndolos por accidente. Sigo en git un archivo de plantilla llamado en su settings_local.py.txtlugar.
fmalina
297

Dos primicias de Django: las mejores prácticas para Django 1.5 sugiere usar el control de versiones para sus archivos de configuración y almacenar los archivos en un directorio separado:

project/
    app1/
    app2/
    project/
        __init__.py
        settings/
            __init__.py
            base.py
            local.py
            production.py
    manage.py

El base.pyarchivo contiene valores comunes (como MEDIA_ROOT o ADMIN), mientras que local.pyy production.pytener configuraciones específicas de sitio:

En el archivo base settings/base.py:

INSTALLED_APPS = (
    # common apps...
)

En el archivo de configuración de desarrollo local settings/local.py:

from project.settings.base import *

DEBUG = True
INSTALLED_APPS += (
    'debug_toolbar', # and other apps for local development
)

En el archivo de configuración de producción de archivos settings/production.py:

from project.settings.base import *

DEBUG = False
INSTALLED_APPS += (
    # other apps for production site
)

Luego, cuando ejecuta django, agrega la --settingsopción:

# 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

Los autores del libro también han puesto una plantilla de diseño de proyecto de muestra en Github.

gene_wood
fuente
62
Tenga en cuenta que en lugar de usarlo --settingscada vez, puede configurar el DJANGO_SETTINGS_MODULEenvvar. Esto funciona muy bien con, por ejemplo, Heroku: configúrelo globalmente en producción, luego anúlelo con dev en su archivo .env.
Simon Weber
99
Usar DJANGO_SETTINGS_MODULEenv var es la mejor idea aquí, gracias Simon.
kibibu
20
Es posible que deba cambiar la BASE_DIRconfiguración aos.path.dirname(os.path.realpath(os.path.dirname(__file__) + "/.."))
Petr Peller
55
@rsp de acuerdo con los documentos de django, importa lo from django.conf import settingsque es un objeto que abstrae la interfaz y desacopla el código de la ubicación de la configuración, docs.djangoproject.com/en/dev/topics/settings/…
3
Si configuro DJANGO_SETTINGS_MODULE a través de una variable ambiental, ¿todavía necesito os.environ.setdefault ("DJANGO_SETTINGS_MODULE", "projectname.settings.production") en mi archivo wsgi.py? Además, configuré la variable ambiental usando: export DJANGO_SETTINGS_MODULE = projectname.settings.local, pero luego se pierde cuando cierro el terminal. ¿Qué puedo hacer para garantizar que se guarde? ¿Debo agregar esa línea al archivo bashrc?
Kritz
71

En lugar de settings.pyusar este diseño:

.
└── settings/
    ├── __init__.py  <= not versioned
    ├── common.py
    ├── dev.py
    └── prod.py

common.py es donde vive la mayor parte de su configuración.

prod.py importa todo de común, y anula lo que sea necesario para anular:

from __future__ import absolute_import # optional, but I like it
from .common import *

# Production overrides
DEBUG = False
#...

Del mismo modo, dev.pyimporta todo common.pyy anula todo lo que necesita anular.

Finalmente, __init__.pyes donde usted decide qué configuraciones cargar, y también es donde almacena los secretos (por lo tanto, este archivo no debe ser versionado):

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

Lo que me gusta de esta solución es:

  1. Todo está en su sistema de versiones, excepto los secretos.
  2. La mayor parte de configuración es en un lugar: common.py.
  3. Las cosas específicas del producto entran prod.py, las cosas específicas del desarrollador entran dev.py. Es sencillo.
  4. Puede anular cosas desde common.pyadentro prod.pyo dev.py, y puede anular cualquier cosa en __init__.py.
  5. Es una pitón sencilla. No volver a importar hacks.
MiniQuark
fuente
2
Todavía estoy tratando de averiguar qué configurar en mis archivos project.wsgi y manage.py para el archivo de configuración. ¿Vas a arrojar algo de luz sobre esto? Específicamente, en mi archivo manage.py tengo os.environ.setdefault("DJANGO_SETTINGS_MODULE", "foobar.settings")foobar es una carpeta con un __init__.pyarchivo y la configuración es una carpeta con un __init__.pyarchivo que contiene mis secretos e importa dev.py, que luego importa common.py. EDITAR No importa, no tenía un módulo instalado que fuera necesario. ¡Culpa mía! ¡Esto funciona muy bien!
teewuane
55
Dos cosas: 1) mejor configurar Debug = True en su dev.py en lugar de = False en su prod.py. 2) En lugar de cambiar en init .py, cambie usando el entorno DJANGO_SETTINGS_MODULE var. Esto ayudará con las implementaciones de PAAS (por ejemplo, Heroku).
Rob Grant
Cuando uso esta configuración en django 1.8.4 e intento runserver obtengo "django.core.exceptions.ImproperlyConfigured: La configuración SECRET_KEY no debe estar vacía". Incluso tengo SECRET_KEY en mi archivo init .py. ¿Me estoy perdiendo de algo?
polarcare
¿No es el uso de algo como AWS_SECRET_ACCESS_KEY = os.getenv ("AWS_SECRET_ACCESS_KEY") más seguro? Pregunta honesta: sé por qué no quieres que esté versionada, pero la otra alternativa es obtenerla del entorno. Lo que plantea la cuestión de establecer la variable de entorno, por supuesto, pero eso se puede dejar a su mecanismo de implementación, ¿no?
JL Peyret
20

Utilizo una versión ligeramente modificada del estilo de configuración "if DEBUG" que publicó Harper Shelby. Obviamente, dependiendo del entorno (win / linux / etc.), es posible que el código deba modificarse un poco.

En el pasado estaba usando "if DEBUG" pero descubrí que ocasionalmente necesitaba hacer pruebas con DEUBG configurado en False. Lo que realmente quería distinguir si el entorno era producción o desarrollo, lo que me dio la libertad de elegir el nivel DEPURAR.

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'

Todavía consideraría esta forma de configuración un trabajo en progreso. No he visto ninguna forma de manejar la configuración de Django que cubriera todas las bases y al mismo tiempo no fuera una molestia total de configuración (no estoy caído con los métodos de archivos de configuración 5x).

T. Stone
fuente
Este es el tipo de cosas que permite que la configuración de Django sea un archivo de código real, y estaba insinuando. Yo no he hecho nada como esto, pero definitivamente es el tipo de solución que podría ser una mejor respuesta general que la mía.
Harper Shelby
3
Me topé con esto por primera vez y elegí (¡con éxito!) Usar su solución, con una ligera diferencia: usé uuid.getnode () para encontrar el uuid de mi sistema. Así que estoy probando si uuid.getnode () == 12345678901 (en realidad un número diferente) en lugar de la prueba os.environ que usaste. No pude encontrar documentación para convencerme de que os.environ ['COMPUTERNAME'] es único por computadora.
Joe Golton
os.environ ['COMPUTERNAME'] no funciona en Amazon AWS Ubuntu. Me sale un KeyError.
nu everest
Cuando uso el UUID, esta solución ha demostrado ser la mejor y más simple para mí. No requiere muchos mosaicos complicados y sobre modularizados. En un entorno de producción, aún necesita colocar las contraseñas de la base de datos y SECRET_KEY en un archivo separado que se encuentre fuera del control de versiones.
nu everest
os.environ['COMPUTERNAME']desafortunadamente no funciona en PythonAnywhere. Obtienes un KeyError.
nbeuchat
14

Yo uso settings_local.py y settings_production.py. Después de probar varias opciones, descubrí que es fácil perder el tiempo con soluciones complejas cuando simplemente tener dos archivos de configuración se siente fácil y rápido.

Cuando usa mod_python / mod_wsgi para su proyecto Django, debe apuntarlo a su archivo de configuración. Si lo señala a app / settings_local.py en su servidor local y app / settings_production.py en su servidor de producción, entonces la vida se vuelve más fácil. Simplemente edite el archivo de configuración apropiado y reinicie el servidor (el servidor de desarrollo de Django se reiniciará automáticamente).

Kai
fuente
2
¿Y qué hay del servidor de desarrollo local? ¿hay alguna manera de decirle al servidor web django (ejecutar usando python manage.py runserver), qué archivo de configuración usar?
akv
2
@akv si agrega --settings = [nombre del módulo] (sin extensión .py) al final del comando runserver, puede especificar qué archivo de configuración usar. Si va a hacer eso, hágase un favor y cree un script de shell / archivo por lotes con la configuración de desarrollo configurada. Confía en mí, tus dedos te lo agradecerán.
T. Stone
Esta es la solución que uso. hackear un archivo de configuración para usarlo tanto en producción como en desarrollo es complicado
George Godik
44
Creo que es mejor usar settings.py en desarrollo, ya que no tienes que especificarlo todo el tiempo.
Andre Bossard
¿Estoy en lo cierto al suponer que este método requiere la importación del módulo de configuración a través del proxy, django.conf.settings? De lo contrario, necesitaría editar las declaraciones de importación para apuntar al archivo de configuración correcto al presionar en vivo.
Groady
8

TL; DR: El truco es modificar os.environmentantes de importar settings/base.pyen cualquiera settings/<purpose>.py, esto simplificará enormemente las cosas.


Solo pensar en todos estos archivos entrelazados me da dolor de cabeza. Combinar, importar (a veces condicionalmente), anular, parchear lo que ya estaba configurado en caso de que la DEBUGconfiguración cambie más adelante. ¡Qué pesadilla!

A través de los años pasé por diferentes soluciones. Todos funcionan de alguna manera , pero son muy dolorosos de manejar. WTF! ¿Realmente necesitamos toda esa molestia? Comenzamos con solo un settings.pyarchivo. ¡Ahora necesitamos una documentación solo para combinar correctamente todo esto en un orden correcto!

Espero finalmente alcanzar el (mi) punto dulce con la solución a continuación.

Recapitulemos los objetivos (algunos comunes, algunos míos)

  1. Mantenga los secretos en secreto: ¡no los guarde en un repositorio!

  2. Establecer / leer claves y secretos a través de la configuración del entorno, estilo de 12 factores .

  3. Tener valores predeterminados razonables. Idealmente para el desarrollo local, no necesita nada más aparte de los valores predeterminados.

  4. … Pero trate de mantener segura la producción predeterminada. Es mejor omitir una anulación de configuración localmente, que tener que recordar ajustar la configuración predeterminada de forma segura para la producción.

  5. Tener la capacidad de encender DEBUG/ apagar de una manera que pueda tener un efecto en otras configuraciones (por ejemplo, usando JavaScript comprimido o no).

  6. El cambio entre configuraciones de propósito, como local / testing / puesta en escena / producción, debe basarse solo en DJANGO_SETTINGS_MODULEnada más.

  7. ... pero permitir necesidad de parametrización a través de la configuración del entorno como DATABASE_URL.

  8. … También les permite usar configuraciones de propósito diferente y ejecutarlas localmente lado a lado, por ejemplo. configuración de producción en la máquina del desarrollador local, para acceder a la base de datos de producción o las hojas de estilo comprimidas de prueba de humo.

  9. Falla si una variable de entorno no se establece explícitamente (lo que requiere un valor vacío como mínimo), especialmente en producción, por ejemplo. EMAIL_HOST_PASSWORD.

  10. Responda al DJANGO_SETTINGS_MODULEconjunto predeterminado en manage.py durante el proyecto de inicio django-admin

  11. Mantenga los condicionales al mínimo, si la condición es el tipo de entorno intencionado (por ejemplo, para el archivo de registro del conjunto de producción y su rotación), anule la configuración en el archivo de configuración intencionado asociado.

No

  1. No permita que django lea la configuración DJANGO_SETTINGS_MODULE de un archivo.
    Ugh! Piensa en lo meta que es esto. Si necesita tener un archivo (como docker env), léalo en el entorno antes de iniciar un proceso de django.

  2. No anule DJANGO_SETTINGS_MODULE en su proyecto / código de aplicación, por ejemplo. basado en el nombre de host o el nombre del proceso.
    Si tiene pereza para establecer una variable de entorno (como for setup.py test), hágalo en herramientas justo antes de ejecutar el código del proyecto.

  3. Evite la magia y los parches de cómo django lee su configuración, preprocese la configuración pero no interfiera después.

  4. Sin complicaciones lógicas basadas en tonterías. La configuración debe ser fija y materializada, no calculada sobre la marcha. Proporcionar valores predeterminados alternativos es suficiente lógica aquí.
    ¿Realmente desea depurar? ¿Por qué localmente tiene la configuración correcta pero en producción en un servidor remoto, en una de cientos de máquinas, algo calculado de manera diferente? Oh! Pruebas unitarias? Para la configuración? ¿Seriamente?

Solución

Mi estrategia consiste en excelente django-Environ se utiliza con iniarchivos de estilo, que proporciona os.environmentvalores por defecto para el desarrollo local, algunos mínimos y cortas settings/<purpose>.pyarchivos que tienen una import settings/base.py DESPUÉS el os.environmentse estableció a partir de un INIarchivo. Esto efectivamente nos da un tipo de ajustes de inyección.

El truco aquí es modificar os.environmentantes de importar settings/base.py.

Para ver el ejemplo completo, vaya al repositorio: 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 / .env

A por defecto para el desarrollo local. Un archivo secreto, para establecer principalmente las variables de entorno requeridas. Póngalos en valores vacíos si no son necesarios en el desarrollo local. Proporcionamos valores predeterminados aquí y no settings/base.pyfallar en ninguna otra máquina si faltan en el entorno.

settings / local.py

Lo que sucede aquí es cargar el entorno desde settings/.envy luego importar configuraciones comunes desde settings/base.py. Después de eso, podemos anular algunos para facilitar el desarrollo local.

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

Para la producción no deberíamos esperar un archivo de entorno, pero es más fácil tener uno si estamos probando algo. Pero de todos modos, no proporcionamos algunos valores predeterminados en línea, por lo que settings/base.pypodemos responder en consecuencia.

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

El principal punto de interés aquí son DEBUGy ASSETS_DEBUGreemplaza, se aplicarán a la pitón os.environSOLAMENTE si faltan del entorno y el archivo.

Estos serán nuestros valores predeterminados de producción, no es necesario ponerlos en el entorno o archivo, pero pueden anularse si es necesario. ¡Ordenado!

settings / base.py

Estas son sus configuraciones de django en su mayoría de vainilla, con algunos condicionales y muchas lecturas del entorno. Casi todo está aquí, manteniendo todos los entornos propuestos consistentes y lo más similares posible.

Las principales diferencias están a continuación (espero que se expliquen por sí mismas):

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',)

El último bit muestra el poder aquí. ASSETS_DEBUGtiene un valor predeterminado sensible, que puede ser anulado settings/production.pye incluso eso puede ser anulado por una configuración de entorno. ¡Hurra!

En efecto, tenemos una jerarquía mixta de importancia:

  1. settings / .py: establece los valores predeterminados según el propósito, no almacena secretos
  2. settings / base.py: está controlado principalmente por el entorno
  3. ajustes del entorno del proceso - 12 factor baby!
  4. settings / .env: valores predeterminados locales para un inicio fácil
Janusz Skonieczny
fuente
Hola Janusz ... ¿en el archivo .env irían todas las claves de API y claves de autenticación y contraseñas, etc.? ¿Como TWILLIO_API = "abc123"? ¿O TWILLIO_API = env ("TWILLIO_API")?
dbinott
Sí, pero esto es solo una alternativa para la configuración del entorno. Este archivo es útil para el desarrollo, pero no se guarda en repositorio ni se envía a producción, donde debe usar estrictamente la configuración del entorno o el equivalente de su plataforma, que a su vez establecerá la configuración del entorno para el proceso del servidor.
Janusz Skonieczny
7

Administro mis configuraciones con la ayuda de django-split-settings .

Es un reemplazo directo para la configuración predeterminada. Es simple, pero configurable. Y no se requiere refactorizar sus configuraciones existentes.

Aquí hay un pequeño ejemplo (archivo 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()
    )

Eso es.

Actualizar

Escribí una publicación de blog sobre cómo administrar djangola configuración de django-split-sttings. ¡Echar un vistazo!

sobolevn
fuente
1
He intentado que .. se topó con una pared, una vez traté de correr mis pruebas unitarias django .. yo no podía encontrar la manera de especificar qué archivo de configuración para leer desde
Abbood
He creado una esencia para usted: gist.github.com/sobolevn/006c734f0520439a4b6c16891d65406c
sobolevn
Obtuve algo como esto en mi código, así que verifico la configuración. DEBUG flag para saber si quiero importar cosas ... esa bandera siempre está configurada como falsa en las pruebas unitarias de django (ver aquí ), así que mi trabajo es anularlas en cada prueba así
abbood
Sin embargo, aquí hay otra pregunta: mi uwsgi.iniarchivo tiene diferentes configuraciones en dev / prod ... ¿alguna idea de cómo hacer que elija valores de mi archivo de configuración?
abbood
lo siento, no entiendo la configuración. puedes hacer una pregunta por separado con más detalles e intentaré ayudarte.
sobolevn
6

El problema con la mayoría de estas soluciones es que tiene su configuración local aplicada antes que las comunes, o después de ellas.

Entonces es imposible anular cosas como

  • la configuración específica del entorno define las direcciones para el grupo de memoria caché, y en el archivo de configuración principal este valor se usa para configurar el backend de caché
  • la configuración específica del entorno agrega o elimina aplicaciones / middleware a la predeterminada

al mismo tiempo.

Se puede implementar una solución utilizando archivos de configuración de estilo "ini" con la clase ConfigParser. Admite múltiples archivos, interpolación de cadenas diferidas, valores predeterminados y muchas otras ventajas. Una vez que se han cargado varios archivos, se pueden cargar más archivos y sus valores anularán los anteriores, si los hay.

Carga uno o más archivos de configuración, dependiendo de la dirección de la máquina, las variables de entorno e incluso los valores en los archivos de configuración cargados previamente. Luego, solo usa los valores analizados para completar la configuración.

Una estrategia que he usado con éxito ha sido:

  • Cargar un defaults.iniarchivo predeterminado
  • Compruebe el nombre de la máquina, y cargar todos los archivos que han concordado con el FQDN invertido, desde la más corta para el partido más largo (así, he cargado net.ini, a continuación net.domain.ini, a continuación net.domain.webserver01.ini, cada uno de ellos posiblemente valores de la anterior anulando). Esta cuenta también es para máquinas de desarrolladores, por lo que cada uno podría configurar su controlador de base de datos preferido, etc. para el desarrollo local
  • Compruebe si hay un "nombre de clúster" declarado y, en ese caso cluster.cluster_name.ini, cargue , lo que puede definir cosas como IP de base de datos y caché

Como ejemplo de algo que puede lograr con esto, puede definir un valor de "subdominio" por env, que luego se utiliza en la configuración predeterminada (as hostname: %(subdomain).whatever.net) para definir todos los nombres de host y cookies necesarios que django necesita para funcionar.

Esto es lo más SECO que pude obtener, la mayoría de los archivos (existentes) tenían solo 3 o 4 configuraciones. Además de esto, tuve que administrar la configuración del cliente, por lo que existía un conjunto adicional de archivos de configuración (con cosas como nombres de bases de datos, usuarios y contraseñas, subdominios asignados, etc.), uno o más por cliente.

Uno puede escalar esto tan bajo o tan alto como sea necesario, solo tiene que poner en el archivo de configuración las claves que desea configurar por entorno, y una vez que haya necesidad de una nueva configuración, coloque el valor anterior en la configuración predeterminada y lo anule donde sea necesario.

Este sistema ha demostrado ser confiable y funciona bien con el control de versiones. Se ha utilizado durante mucho tiempo administrando dos grupos de aplicaciones separados (15 o más instancias separadas del sitio django por máquina), con más de 50 clientes, donde los grupos estaban cambiando de tamaño y miembros dependiendo del estado de ánimo del administrador del sistema. .

reescrito
fuente
1
¿Tiene un ejemplo de cómo carga la configuración desde el ini en la configuración de Django?
kaleissin
Ver docs.python.org/2/library/configparser.html . Puede cargar un analizador y config = ConfigParser.ConfigParser() luego leer sus archivos config.read(array_of_filenames)y obtener valores utilizando config.get(section, option). Entonces, primero carga su configuración, y luego la usa para leer valores para la configuración.
reescrito
5

También estoy trabajando con Laravel y me gusta la implementación allí. Traté de imitarlo y combinarlo con la solución propuesta por T. Stone (ver arriba):

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

Tal vez algo como esto te ayudaría.

Robert Kuzma
fuente
4

Recuerde que settings.py es un archivo de código en vivo. Suponiendo que no tiene DEBUG configurado en producción (que es una práctica recomendada), puede hacer algo como:

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

Bastante básico, pero podría, en teoría, subir a cualquier nivel de complejidad basado solo en el valor de DEBUG, o cualquier otra variable o verificación de código que desee usar.

Harper Shelby
fuente
4

Para la mayoría de mis proyectos utilizo el siguiente patrón:

  1. Crear settings_base.py donde almaceno configuraciones que son comunes para todos los entornos
  2. Cada vez que necesito usar un nuevo entorno con requisitos específicos, creo un nuevo archivo de configuración (por ejemplo, settings_local.py) que hereda el contenido de settings_base.py y anula / agrega las variables de configuración adecuadas ( from settings_base import *)

(Para ejecutar manage.py con una configuración personalizada de archivos sólo tiene que utilizar --settings opción de comando: manage.py <command> --settings=settings_you_wish_to_use.py)

dzida
fuente
3

Mi solución a ese problema también es una mezcla de algunas soluciones ya mencionadas aquí:

  • Mantengo un archivo llamado local_settings.pyque tiene el contenido USING_LOCAL = Trueen dev y USING_LOCAL = Falseen prod
  • En settings.pyhago una importación en ese archivo para obtener la USING_LOCALconfiguración

Luego baso todas mis configuraciones dependientes del entorno en esa:

DEBUG = USING_LOCAL
if USING_LOCAL:
    # dev database settings
else:
    # prod database settings

Prefiero esto a tener dos archivos settings.py separados que necesito mantener, ya que puedo mantener mi configuración estructurada en un solo archivo más fácilmente que distribuirlos en varios archivos. De esta manera, cuando actualizo una configuración, no me olvido de hacerlo para ambos entornos.

Por supuesto, cada método tiene sus desventajas y este no es una excepción. El problema aquí es que no puedo sobrescribir el local_settings.pyarchivo cada vez que empujo mis cambios a producción, lo que significa que no puedo copiar todos los archivos a ciegas, pero eso es algo con lo que puedo vivir.

Miguel Ventura
fuente
3

Utilizo una variación de lo que jpartogi mencionó anteriormente, que encuentro un poco más corto:

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.\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" % (computername, __file__))
  sys.exit(1)

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

Básicamente, en cada computadora (desarrollo o producción) tengo el archivo hostname_settings.py apropiado que se carga dinámicamente.

stratosgear
fuente
3

1 - Crea una nueva carpeta dentro de tu aplicación y ponle nombre.

2 - Ahora crea un nuevo __init__.pyarchivo y escribe dentro

from .base import *

try:
    from .local import *
except:
    pass

try:
    from .production import *
except:
    pass

3 - Cree tres archivos nuevos en el nombre de la carpeta de configuración local.pyy production.pyy base.py.

4 - Dentro base.py, copia todo el contenido de la settings.pycarpeta anterior y renómbralo con algo diferente, digamos old_settings.py.

5 - En base.py cambia tu ruta BASE_DIR para que apunte a tu nueva ruta de configuración

Camino viejo-> BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

Nuevo camino -> BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

De esta manera, el directorio del proyecto puede estructurarse y ser manejable entre la producción y el desarrollo local.

Jack Ryan
fuente
2

Para utilizar una settingsconfiguración diferente en un entorno diferente, cree un archivo de configuración diferente. Y en su secuencia de comandos de implementación, inicie el servidor usando el --settings=<my-settings.py>parámetro, a través del cual puede usar diferentes configuraciones en diferentes entornos.

Beneficios de usar este enfoque :

  1. Su configuración será modular en función de cada entorno.

  2. Puede importar el que master_settings.pycontiene la configuración base en el environmnet_configuration.pyy anular los valores que desea cambiar en ese entorno.

  3. Si tiene un equipo enorme, cada desarrollador puede tener el suyo propio local_settings.pyque puede agregar al repositorio de código sin riesgo de modificar la configuración del servidor. Puede agregar estas configuraciones locales .gitnoresi usa git o .hginoresi usa Mercurial para Control de versiones (o cualquier otro). De esa forma, la configuración local ni siquiera formará parte de la base de código real que la mantiene limpia.

Moinuddin Quadri
fuente
2

Tenía mi configuración dividida de la siguiente manera

settings/
     |
     |- base.py
     |- dev.py
     |- prod.py  

Tenemos 3 ambientes

  • dev
  • puesta en escena
  • producción

Ahora, obviamente, la puesta en escena y la producción deben tener el máximo entorno similar posible. Así que nos quedamos prod.pypara los dos.

Pero hubo un caso en el que tuve que identificar que el servidor en ejecución es un servidor de producción. @T. La respuesta de Stone me ayudó a escribir el cheque de la siguiente manera.

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  
Kishor Pawar
fuente
1

Lo distingo en manage.py y creé dos archivos de configuración separados: local_settings.py y prod_settings.py.

En manage.py verifico si el servidor es un servidor local o un servidor de producción. Si es un servidor local, cargaría local_settings.py y es un servidor de producción, cargaría prod_settings.py. Básicamente así es como se vería:

#!/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)

Descubrí que es más fácil separar el archivo de configuración en dos archivos separados en lugar de hacer muchos ifs dentro del archivo de configuración.

Joshua Partogi
fuente
1

Como alternativa para mantener un archivo diferente si lo desea: si está utilizando git o cualquier otro VCS para insertar códigos de local a servidor, lo que puede hacer es agregar el archivo de configuración a .gitignore.

Esto le permitirá tener contenido diferente en ambos lugares sin ningún problema. SO en el servidor puede configurar una versión independiente de settings.py y cualquier cambio realizado en el local no se reflejará en el servidor y viceversa.

Además, eliminará el archivo settings.py de github también, la gran falla, que he visto hacer a muchos novatos.

sprksh
fuente
0

Creo que @T sugiere la mejor solución. Stone, pero no sé por qué no usar la bandera DEBUG en Django. Escribo el siguiente código para mi sitio web:

if DEBUG:
    from .local_settings import *

Siempre las soluciones simples son mejores que las complejas.

seyedrezafar
fuente
-2

Encontré las respuestas aquí muy útiles. (¿Se resolvió esto de manera más definitiva? La última respuesta fue hace un año). Después de considerar todos los enfoques enumerados, se me ocurrió una solución que no vi enumerada aquí.

Mis criterios fueron:

  • Todo debería estar en control de la fuente. No me gustan los trozos de violín por ahí.
  • Idealmente, mantenga la configuración en un archivo. Olvidé las cosas si no las estoy mirando directamente :)
  • No hay ediciones manuales para implementar. Debería poder probar / empujar / desplegar con un solo comando de estructura.
  • Evite filtrar configuraciones de desarrollo a producción.
  • Manténgase lo más cerca posible del diseño Django "estándar" (* tos *) como sea posible.

Pensé que encender la máquina host tenía sentido, pero luego pensé que el problema real aquí es la configuración diferente para diferentes entornos , y tuve un momento aha. Pongo este código al final de mi archivo settings.py:

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'

De esta manera, la aplicación adopta de manera predeterminada la configuración de producción, lo que significa que está "listando" explícitamente su entorno de desarrollo. Es mucho más seguro olvidarse de establecer la variable de entorno localmente que si fuera al revés y olvidarse de establecer algo en producción y dejar que se usen algunas configuraciones de desarrollo.

Al desarrollar localmente, ya sea desde el shell o en un .bash_profile o donde sea:

$ export DJANGO_DEVELOPMENT_SERVER=yep

(O si está desarrollando en Windows, configúrelo a través del Panel de control o como se llame en estos días ... Windows siempre lo hizo tan oscuro que podía establecer variables de entorno).

Con este enfoque, las configuraciones de desarrollo están todas en un lugar (estándar) y simplemente anulan las de producción cuando es necesario. Cualquier cambio en la configuración de desarrollo debe ser completamente seguro para comprometerse con el control de origen sin impacto en la producción.

Jason Boyd
fuente
Es mejor simplemente mantener diferentes archivos de configuración y elegir usando la variable env estándar DJango DJANGO_SETTINGS_MODULE
Rob Grant