Múltiples configuraciones (desarrollo/producción) en Django

Múltiples configuraciones (desarrollo/producción) en Django

En una aplicación de Django seguramente querremos distinguir dos o más configuraciones; por ejemplo, tener configuración una para el escenario de desarrollo y otra para el de producción. Existen varias alternativas para conseguir esto. Sin embargo, la idea es siempre la misma: dado que la configuración se realiza típicamente dentro del archivo settings.py ubicado en la carpeta de nuestro proyecto, crearemos un archivo de configuración adicional por cada escenario que queramos tener y luego le indicaremos a Django cuál debe cargar.

Opción 1

Esta primera solución es la más sencilla si solo queremos tener dos escenarios de configuración y no requiere emplear variables de entorno. Consiste en mantener en el archivo settings.py nuestra configuración de producción y crear un nuevo archivo settings_dev.py donde pondremos la configuración de desarrollo. En este archivo no es necesario que repitamos los valores que son comunes para ambos escenarios: escribiremos solo aquellas variables que difieran de las de producción. Así, por ejemplo, en settings.py tendremos, entre muchas otras cosas, estas líneas:

# etc., etc., etc.

# Desactivar el modo depuración en producción.
DEBUG = False
# Direcciones permitidas en producción.
ALLOWED_HOSTS = ["www.mi-sitio-en-produccion.com"]

# etc., etc., etc.

Luego crearemos un nuevo settings_dev.py donde solo pondremos las variables de desarrollo cuyo valor difiera del valor en settings.py. Por ejemplo:

# Activar el modo de depuración.
DEBUG = True
# Correr en localhost o 127.0.0.1.
ALLOWED_HOSTS = ["*"]

Ahora bien, Django carga por defecto la configuración desde settings.py, por lo cual al final de este archivo pondremos:

try:
    # Importar la configuración de desarrollo.
    from .settings_dev import *
except ModuleNotFoundError:
    # Ignorar el error si el archivo no se encuentra.
    pass

Al ubicar este código al final del archivo logramos que Django cargue la configuración de producción y luego agregue (reemplazando los valores de producción) las variables de desarrollo cuando encuentre el archivo settings_dev.py. Cuando el archivo no exista se cargará intacta la configuración de producción de settings.py. La clave de este procedimiento consiste en no subir nunca el archivo settings_dev.py a nuestro servidor de producción o, si estamos usando Docker, no copiarlo a la imagen usada en producción.

Puesto que en el archivo de desarrollo settings_dev.py solo pondremos las variables que difieran de la configuración de producción, podremos reemplazar valores ya definidos en el settings.py, pero no eliminarlos. Por ejemplo, en settings.py seguramente configuraremos una ruta para cargar los archivos estáticos en producción:

STATIC_URL = '/static/'
STATIC_ROOT = '/home/appuser/myproject/static'

Pero en desarrollo no queremos definir STATIC_ROOT, puesto que el servidor HTTP integrado de Django (manage.py runserver) se ocupará de servir los archivos estáticos directamente desde la carpeta static en las aplicaciones de nuestro proyecto. Entonces en settings_dev.py definiremos la variable como None para que reemplace el valor de producción y Django entienda que no está definida (y por ende tome su valor por defecto):

# settings_dev.py
STATIC_ROOT = None

Opción 2

Otra solución consiste en usar una variable de entorno para indicar cuál es el escenario actual y así cargar el archivo de configuración correspondiente. Esta alternativa es especialmente útil si queremos tener más de dos escenarios de configuración. En este caso es conveniente mantener en settings.py solo las variables que son comunes a todas las configuraciones y luego crear tantos otros archivos de configuracion como escenarios queramos tener. Alguno de estos archivos será cargado al final del settings.py para agregar las variables propias del escenario que defina la variable de entorno.

Por ejemplo, supongamos que queremos definir tres escenarios distintos para nuestro proyecto, cada uno con su propia configuración:

  1. Desarrollo (settings_dev.py)
  2. Producción con Docker (settings_prod_docker.py)
  3. Producción sin Docker (settings_prod.py)

Nuestro settings.py ha de determinar cuál es el escenario actual a partir del valor de una variable de entorno. Llamemos a esa variable MYPROJECT_STAGE. Luego de leer la variable de entorno, se cargará el archivo de configuración correspondiente. Para esto pondremos al final de settings.py:

import os
# Cargar la variable de entorno. El segundo argumento es
# el valor que ha de tomarse cuando la variable no esté
# definida.
stage = os.getenv("MYPROJECT_STAGE", "dev")
if stage == "prod":
    # Producción sin Docker.
    from .settings_prod import *
elif stage == "prod_docker":
    # Producción con Docker.
    from .settings_prod_docker import *
elif stage == "dev":
    # Desarrollo.
    from .settings_dev import *
else:
    # Arrojar un error si MYPROJECT_STAGE tiene un valor desconocido.
    raise ValueError(f"Unknown stage: {stage}")

Nótese que cada escenario está identificado por un valor de la variable de entorno. Así, debemos ocuparnos de definir MYPROJECT_STAGE=prod en nuestra computadora de producción y MYPROJECT_STAGE=prod_docker en nuestra imagen de Docker (vía ENV). Para el escenario de desarrollo podemos dejar la variable sin definir ya que el valor por defecto, como se observa en el código, es dev.

Así logramos cargar el archivo de configuración correspondiente al final de settings.py. Aquí, al igual que en la primera solución, los archivos settings_prod.py, settings_prod_docker.py y settings_dev.py han de contener únicamente las variables de configuración que queremos que se agreguen a las ya definidas en settings.py o que las modifiquen.

El principal beneficio de esta segunda solución es que podemos modificar el condicional para contemplar una infinidad de escenarios con distintas configuraciones. El único requisito es que tengamos la posibilidad de definir la variable de entorno MYPROJECT_STAGE antes de correr el código de Django.

Opción 3

Django lee el nombre del archivo de configuración desde la variable de entorno DJANGO_SETTINGS_MODULE, por lo cual una tercera solución consiste sencillamente en pasarle a esa variable un módulo de configuración alternativo. Los archivos wsgi.py y asgi.py generados por el comando startproject establecen el valor por defecto de esta variable en caso de que no esté definida. En wsgi.py, por ejemplo, ubicado en la carpeta de nuestro proyecto, encontraremos algo como esto:

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

application = get_wsgi_application()

Como se observa, si DJANGO_SETTINGS_MODULE no está definida, la configuración se carga desde settings.py.

En este caso, suponiendo los mismos tres escenarios que en la opción anterior, será conveniente crear los tres archivos de cada configuración y un cuarto para la configuración común a todos los escenarios:

myproject/
    - settings_common.py
    - settings_dev.py
    - settings_prod.py
    - settings_prod_docker.py

En cada una de las configuraciones particulares importaremos al principio del archivo las variables de settings_common.py y luego definiremos o reemplazaremos las que correspondan. Por ejemplo, en settings_dev.py:

from .settings_common.py import *

DEBUG = True
ALLOWED_HOSTS = ["*"]

Curso online 👨‍💻

¡Ya lanzamos el curso oficial de Recursos Python en Udemy! Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter en 2024.

Consultoría 💡

Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.

Deja una respuesta