📚 Unidad ISS-01 · Settings y selección del motor — capa 🧠 APRENDER
🛠 Construir este bloque → · ✅ Condiciones de cierre (GATE) · 📝 Evaluación
Capa Página Para qué 🧠 Aprender esta página comprender, explicar y relacionar 🛠 Construir Settings y selección del motor ejecutar, programar y verificar ✅ GATE Cierre de la unidad condición para pasar al bloque siguiente
Mapa de correspondencias. Cada fila enlaza el mismo tema en las dos capas; los enlaces apuntan a secciones reales del material.
| Tema | 🧠 Aprender (esta página) | 🛠 Construir (ISS técnico) |
|---|---|---|
| Objetivo | 1.0 Objetivos, Requisitos y Contexto Operativo | Objetivo |
| Recorrido | 4.0 Arquitectura de Configuración y Módulos Creados | Construcción |
| Cierre | 8.0 Matriz de Aceptación, Evidencias y Tabla GATE | Criterios de aceptación · GATE |
| Evaluación | 9.0 Cuestionario de Defensa Oral y Evaluación Técnica | GATE |
🖥 Presentación del ISS
Presentación de la unidad. Diapositivas de ISS-01 — Settings y selección del motor (19 diapositivas). Se visualiza aquí, dentro del sitio.
🎬 Video explicativo
Recorrido audiovisual de la unidad. El video recorre el ISS técnico de ISS-01 — Settings y selección del motor, bloque por bloque.
14:20 min · narración en español · subtítulos activables desde el reproductor.
Infografía

Mapa conceptual
- Objetivo y Requisitos
- Objetivo: Elegir un solo motor por proceso desde .env sin SQL
- Requisito: ISS-00 superado y terminal con .venv activo
- Requisito: Variables separadas para PostgreSQL, MySQL, SQL Server y Oracle
- Instalación de Paquetes
- python-dotenv==1.2.3: Lectura de .env
- psycopg[binary]==3.3.6: Driver para PostgreSQL
- mysqlclient==2.3.0: Driver para MySQL
- mssql-django==2.0.0: Driver para SQL Server
- oracledb==26.0.1: Driver para Oracle
- requirements.txt: Actualizado con python -m pip freeze
- Plantilla de Entorno (.env.example)
- Configuración: DJANGO_SECRET_KEY y DJANGO_DEBUG=True
- Configuración: ALLOWED_HOSTS con localhost y 127.0.0.1
- Selector: DB_ENGINE con postgresql por defecto
- Variables por motor: Credenciales y puertos específicos
- CORS: CORS_ALLOW_ALL_ORIGINS=True para laboratorio académico
- Traducción de Motor (config/database.py)
- ALLOWED_ENGINES: postgresql, mysql, mssql y oracle
- Validación: ImproperlyConfigured ante motor o variable inválida
- _postgresql: Retorna django.db.backends.postgresql
- _mysql: Retorna django.db.backends.mysql con charset utf8mb4
- _mssql: Retorna mssql con ODBC Driver 18 y TrustServerCertificate
- _oracle: Retorna django.db.backends.oracle con DSN y SERVICE_NAME
- Paquete de Settings (config/settings/)
- Sustitución: Borrar settings.py y crear directorio init.py
- Carga: load_dotenv(BASE_DIR / '.env') antes de SECRET_KEY
- INSTALLED_APPS: Apps base de Django (admin, auth, sessions, etc.)
- DATABASES: Asignación con database_from_environ(os.environ)
- Zona e Idioma: LANGUAGE_CODE: es y TIME_ZONE: America/Bogota
- Intérprete y Parches de Arranque
- config/project_python.py: use_project_python para reejecutar con .venv
- manage.py: Inserción de sys.path y llamada a use_project_python
- wsgi.py y asgi.py: Inserción de load_dotenv antes de aplicación
- Criterios de Aceptación y Verificación
- AC-01-01: Motores producen el ENGINE documentado
- AC-01-02: Motor desconocido falla con ImproperlyConfigured
- AC-01-03: Falta de variable obligatoria falla antes de conectar
- AC-01-04: DSN de Oracle contiene SERVICE_NAME
- Verificación: DatabaseSelectionTests con SimpleTestCase
- Evidencia EVI-01-01: DatabaseSelectionTests PASS
Guía de Estudio Exhaustiva: ISS-01 — Configuración de Motor de Base de Datos y Entorno Multidialecto en Django
1.0 Objetivos, Requisitos y Contexto Operativo
1.1 Contextualización y Propósito del ISS-01
En la evolución del proyecto StoreLab, la unidad ISS-01 representa un hito infraestructural crítico. Su propósito estratégico es transformar la configuración por defecto monolítica generada por el comando startproject de Django —originalmente limitada a una base de datos local SQLite en un único archivo— en una arquitectura de configuración dinámica y desacoplada, guiada exclusivamente por variables de entorno y capaz de alternar entre cuatro motores relacionales de clase empresarial (PostgreSQL, MySQL, SQL Server y Oracle) por proceso de ejecución. En este punto de la construcción del sistema no existen aún modelos de negocio ni tablas de aplicación declaradas; el foco absoluto radica en establecer un núcleo de configuración sólido, dinámico y resiliente que servirá de cimiento para todos los incrementos posteriores del proyecto.
Para abordar con éxito la implementación del ISS-01, es imprescindible haber superado de manera rigurosa los requisitos del ISS-00. Específicamente, el sistema debe partir de una estructura limpia donde el entorno virtual aislado .venv haya sido creado con Python 3.12 y Django 5.2.17. Antes de ejecutar cualquier instalación de paquetes adicionales o reestructuración de archivos, es un requisito estricto y no negociable verificar que la sesión de la terminal se encuentre ejecutándose dentro del entorno virtual mediante los comandos source .venv/bin/activate y which python. Esta comprobación garantiza que la ruta del ejecutable apunte inequívocamente a .venv/bin/python, evitando la contaminación del intérprete de Python del sistema operativo global.
- Punto de partida validado (ISS-00): Existencia de la carpeta
.venv/, el scriptmanage.pyen la raíz y el paquete inicialconfig/generado por la CLI de Django. - Entorno virtual activo: El prompt de la terminal debe mostrar explícitamente el prefijo
(.venv). - Confirmación de intérprete: La ejecución de
which pythondebe retornar la ruta absoluta hacia.venv/bin/python. - Aislamiento de paquetes: Garantía de que cualquier invocación a
pipse realice mediantepython -m pipdentro del entorno activo. - Ausencia de modelos: Confirmación de que el árbol del proyecto no contiene aún modelos de negocio ni migraciones aplicadas.
Una vez validadas las condiciones previas del entorno operativo, resulta indispensable dominar el marco conceptual y el vocabulario técnico que hacen posible el desacoplamiento entre el código fuente y el motor de base de datos subyacente.
2.0 Vocabulario Técnico y Conceptos Fundamentales
2.1 Marco Conceptual del Proyecto Multidialecto
El diseño de un sistema capaz de operar sobre múltiples dialectos de base de datos sin modificar una sola línea del código de aplicación exige la asimilación precisa de términos y mecanismos de bajo nivel. El dominio de este vocabulario técnico es indispensable para comprender cómo Django abstrae la infraestructura mediante controladores, variables de proceso y llamadas al sistema.
| Término | Definición Técnica | Propósito en el Sistema |
|---|---|---|
DB_ENGINE |
Variable de control de entorno utilizada para seleccionar de forma dinámica el motor de base de datos activo por cada proceso. | Permite conmutar en tiempo de ejecución entre postgresql, mysql, mssql y oracle sin alterar archivos de código. |
DSN (Data Source Name) |
Cadena de conexión estructurada que describe los parámetros de red, protocolo e instancia requeridos por motores empresariales. | Formatear explícitamente la dirección, puerto y SERVICE_NAME en conexiones hacia bases de datos Oracle. |
ImproperlyConfigured |
Excepción nativa del núcleo de Django (django.core.exceptions) lanzada de forma temprana ante inconsistencias de entorno. |
Interrumpir inmediatamente la ejecución si falta una variable obligatoria o si se especifica un motor no soportado. |
load_dotenv |
Función provista por la librería python-dotenv que lee pares clave-valor desde un archivo .env y los inyecta en el entorno. |
Cargar la configuración local en el diccionario os.environ antes de que Django inicialice el paquete de settings. |
sys.prefix |
Atributo de la biblioteca estándar de Python que contiene la ruta absoluta del directorio raíz del intérprete en ejecución. | Comparar si la instancia actual de Python corre dentro del directorio .venv o en el intérprete del sistema operativo. |
os.execv |
Invocación al sistema que reemplaza la imagen de memoria del proceso actual por un nuevo ejecutable con sus argumentos. | Re-ejecutar de forma transparente el comando manage.py utilizando el binario .venv/bin/python si se arrancó por error con el Python global. |
Comprendido el marco conceptual y los términos que gobiernan el comportamiento del sistema, el siguiente paso práctico consiste en la gestión e instalación de las dependencias y controladores necesarios a nivel de línea de comandos.
3.0 Gestión de Dependencias y Drivers CLI
3.1 Instalación y Análisis de Conectores de Base de Datos
Django incluye internamente la capa ORM para abstraer el lenguaje SQL de los motores soportados, pero no empaqueta los controladores (drivers) de bajo nivel necesarios para establecer la comunicación por red o interfaz C con los servidores de base de datos. Por esta razón, cada conector debe instalarse de forma explícita en el entorno virtual. Si se omite la instalación de alguno de estos paquetes, cualquier intento de ejecutar migraciones u operaciones de lectura fallará inmediatamente con errores de importación (ImportError) al cargar el backend correspondiente, incluso si el código de configuración en database.py está correctamente escrito.
Para asegurar la repetibilidad del entorno, se debe ejecutar el siguiente bloque de comandos CLI en la terminal con el entorno virtual previamente activado:
source .venv/bin/activate
which python
python -m pip install "python-dotenv==1.2.3"
python -m pip install "psycopg[binary]==3.3.6"
python -m pip install "mysqlclient==2.3.0"
python -m pip install "mssql-django==2.0.0"
python -m pip install "oracledb==26.0.1"
python -m pip freeze > requirements.txt
Desglose Analítico de las Librerías Instaladas
python-dotenv==1.2.3: Expone la funciónload_dotenv. Su función principal es leer el archivo.envdel disco e inyectar sus valores enos.environ. Módulos clave de entrada comomanage.py,wsgi.pyyasgi.pylo importan de manera prioritaria antes de invocar la carga de los settings. Si esta librería falta, el proceso no logra inicializar las variables críticas del sistema.psycopg[binary]==3.3.6: Es el controlador oficial de C precompilado que utiliza el backenddjango.db.backends.postgresql. El extra[binary]incluye las librerías dinámicas de C compiladas, evitando la necesidad de disponer de herramientas de compilación o cabeceras de desarrollo de C en el sistema operativo cliente.mysqlclient==2.3.0: Es el controlador nativo en C documentado y recomendado oficialmente por Django para el backenddjango.db.backends.mysql. Proporciona la interfaz de alto rendimiento entre la capa de abstracción de Django y el servidor MySQL o MariaDB.mssql-django==2.0.0: Adaptador especializado para Microsoft SQL Server. Registra el nombre de motormssqldentro del ecosistema de Django y arrastra como dependencia interna apyodbc.oracledb==26.0.1: Es el controlador moderno de Python para Oracle Database que sustituye de forma oficial acx_Oracle(deprecado a partir de Django 5.0). Sirve de soporte para el backenddjango.db.backends.oracle. Se instala en el entorno para garantizar la compatibilidad multidialecto completa del proyecto.
┌──────────────────────────────────────────────────────────────────────────────────────────────────┐ │ DISTINCIÓN ARQUITECTÓNICA DE CONTROLADORES: BINARIOS DE C VS. ADAPTADORES DEL SISTEMA OPERATIVO │ ├──────────────────────────────────────────────────────────────────────────────────────────────────┤ │ Es crucial diferenciar dos categorías de conectores instalados en el entorno: │ │ │ │ 1. Controladores Binarios / C Puros (
psycopg[binary],mysqlclient,oracledb): │ │ Empaquetan sus propias extensiones o interfaces C dentro del propio wheel de Python. Su │ │ instalación queda completamente contenida dentro del directorio.venv/sin requerir │ │ librerías del sistema operativo adicionales. │ │ │ │ 2. Adaptadores Capa sobre Drivers del SO (mssql-django/pyodbc): │ │mssql-djangoes un wrapper o adaptador Python que depende internamente depyodbc. │ │ Atención: Este paquete Python NO instala el controlador nativo de red. Requiere de forma │ │ estricta que el sistema operativo anfitrión tenga instalado el paquete nativo │ │ODBC Driver 18 for SQL Server. Si este driver del SO falta, Python fallará al conectar. │ └──────────────────────────────────────────────────────────────────────────────────────────────────┘
Con todos los controladores y librerías instalados y registrados en requirements.txt, se procede a la reestructuración física de los archivos de configuración del proyecto.
4.0 Arquitectura de Configuración y Módulos Creados
4.1 Diseño Estructural: De Archivo a Paquete de Settings
El comando django-admin startproject config . ejecutado en el ISS-00 genera por defecto un archivo monolítico en la ruta config/settings.py. Dado que en Python un archivo y una carpeta no pueden compartir el mismo nombre en el mismo nivel de espacio de nombres, el primer paso arquitectónico del ISS-01 consiste en eliminar dicho archivo e instituir un paquete de Python llamado config/settings/ que contendrá la lógica dividida, complementado por un módulo auxiliar config/database.py dedicado en exclusiva a la resolución del motor de base de datos.
Transformación de la Estructura de Archivos
ESTRUCTURA ANTES (ISS-00):
config/
├── __init__.py
├── asgi.py
├── settings.py <-- Archivo monolítico generado por startproject
├── urls.py
└── wsgi.py
ESTRUCTURA DESPUÉS (ISS-01):
config/
├── __init__.py
├── asgi.py
├── database.py <-- NUEVO: Módulo de selección dinámica de motor
├── project_python.py<-- NUEVO: Mecanismo de re-ejecución de intérprete
├── settings/
│ └── __init__.py <-- NUEVO: Paquete de configuración modularizado
├── urls.py
└── wsgi.py
.env.example <-- NUEVO: Plantilla no versionada de variables
.env <-- NUEVO: Archivo local con secretos de entorno
Gestión del Entorno: .env.example y .env
El archivo .env.example actúa como una plantilla pública de documentación. No contiene contraseñas ni secretos reales, únicamente las claves de las variables y sus valores por defecto seguros.
cat > .env.example <<'EOF'
# Copie este archivo a .env y complete los valores.
# .env no se versiona.
DJANGO_SECRET_KEY=
DJANGO_DEBUG=True
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1,testserver
# Motor activo. Uno solo por ejecución: postgresql | mysql | mssql | oracle
DB_ENGINE=postgresql
POSTGRES_DB=
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432
MYSQL_DB=
MYSQL_USER=
MYSQL_PASSWORD=
MYSQL_HOST=127.0.0.1
MYSQL_PORT=3306
MSSQL_DB=
MSSQL_USER=
MSSQL_PASSWORD=
MSSQL_HOST=127.0.0.1
MSSQL_PORT=1433
ORACLE_DB=
ORACLE_USER=
ORACLE_PASSWORD=
ORACLE_HOST=127.0.0.1
ORACLE_PORT=1521
ORACLE_SERVICE_NAME=
# Laboratorio académico. En producción liste orígenes explícitos.
CORS_ALLOW_ALL_ORIGINS=True
EOF
cp .env.example .env
Regla Estricta de Seguridad: El archivo
.envcontiene credenciales operativas y secretos de la aplicación (DJANGO_SECRET_KEY, contraseñas de bases de datos). Bajo ninguna circunstancia debe versionarse en Git. Su exclusión está garantizada por la regla.envpresente en el archivo.gitignorecreado en el ISS-00.
Módulo de Selección de Base de Datos: config/database.py
Este módulo no constituye una app de Django, sino una utilidad pura de infraestructura que traduce la variable DB_ENGINE a la estructura del diccionario DATABASES["default"].
"""Selección del motor de base de datos a partir del entorno.
Un solo alias, default, queda activo en cada ejecución.
"""
from django.core.exceptions import ImproperlyConfigured
ALLOWED_ENGINES = ("postgresql", "mysql", "mssql", "oracle")
def database_from_environ(env):
engine = str(env.get("DB_ENGINE", "postgresql")).strip().lower()
if engine not in ALLOWED_ENGINES:
allowed = ", ".join(ALLOWED_ENGINES)
raise ImproperlyConfigured(
f"DB_ENGINE='{engine}' no es válido. Valores permitidos: {allowed}."
)
builders = {
"postgresql": _postgresql,
"mysql": _mysql,
"mssql": _mssql,
"oracle": _oracle,
}
return {"default": builders[engine](env)}
def _required(env, key):
value = env.get(key)
if value is None or str(value).strip() == "":
raise ImproperlyConfigured(
f"Falta la variable de entorno {key} para DB_ENGINE={env.get('DB_ENGINE')}."
)
return str(value).strip()
def _postgresql(env):
return {
"ENGINE": "django.db.backends.postgresql",
"NAME": _required(env, "POSTGRES_DB"),
"USER": _required(env, "POSTGRES_USER"),
"PASSWORD": _required(env, "POSTGRES_PASSWORD"),
"HOST": _required(env, "POSTGRES_HOST"),
"PORT": _required(env, "POSTGRES_PORT"),
}
def _mysql(env):
return {
"ENGINE": "django.db.backends.mysql",
"NAME": _required(env, "MYSQL_DB"),
"USER": _required(env, "MYSQL_USER"),
"PASSWORD": _required(env, "MYSQL_PASSWORD"),
"HOST": _required(env, "MYSQL_HOST"),
"PORT": _required(env, "MYSQL_PORT"),
"OPTIONS": {"charset": "utf8mb4"},
}
def _mssql(env):
return {
"ENGINE": "mssql",
"NAME": _required(env, "MSSQL_DB"),
"USER": _required(env, "MSSQL_USER"),
"PASSWORD": _required(env, "MSSQL_PASSWORD"),
"HOST": _required(env, "MSSQL_HOST"),
"PORT": _required(env, "MSSQL_PORT"),
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"extra_params": "TrustServerCertificate=yes",
},
}
def _oracle(env):
host = _required(env, "ORACLE_HOST")
port = _required(env, "ORACLE_PORT")
service = _required(env, "ORACLE_SERVICE_NAME")
dsn = (
f"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={host})(PORT={port}))"
f"(CONNECT_DATA=(SERVICE_NAME={service})))"
)
return {
"ENGINE": "django.db.backends.oracle",
"NAME": dsn,
"USER": _required(env, "ORACLE_USER"),
"PASSWORD": _required(env, "ORACLE_PASSWORD"),
"HOST": host,
"PORT": "",
}
Deconstrucción Analítica del Módulo database.py por Bloque de Código
4.1.1 Análisis de database_from_environ(env) y _required(env, key)
def database_from_environ(env):
engine = str(env.get("DB_ENGINE", "postgresql")).strip().lower()
if engine not in ALLOWED_ENGINES:
allowed = ", ".join(ALLOWED_ENGINES)
raise ImproperlyConfigured(
f"DB_ENGINE='{engine}' no es válido. Valores permitidos: {allowed}."
)
builders = {
"postgresql": _postgresql,
"mysql": _mysql,
"mssql": _mssql,
"oracle": _oracle,
}
return {"default": builders[engine](env)}
def _required(env, key):
value = env.get(key)
if value is None or str(value).strip() == "":
raise ImproperlyConfigured(
f"Falta la variable de entorno {key} para DB_ENGINE={env.get('DB_ENGINE')}."
)
return str(value).strip()
- Desglose Técnico: La función
database_from_environactúa como la puerta de entrada principal del módulo. Extrae la variableDB_ENGINEdel diccionario enviado (env, habitualmenteos.environ), eliminando espacios y convirtiendo la cadena a minúsculas. Si la cadena resultante no pertenece aALLOWED_ENGINES(postgresql,mysql,mssql,oracle), interrumpe inmediatamente el arranque lanzando la excepcióndjango.core.exceptions.ImproperlyConfigured. - Mecanismo de Invariante Fail-Fast: La función auxiliar
_requiredevalúa la presencia y vacuidad de cada clave requerida. Si una variable esNoneo una cadena compuesta únicamente por espacios en blanco, lanzaImproperlyConfiguredindicando exactamente cuál variable falta para el motor seleccionado. Esto evita que Django intente abrir conexiones con parámetros nulos o defectuosos en etapas avanzadas de la ejecución.
4.1.2 Análisis de _postgresql(env)
def _postgresql(env):
return {
"ENGINE": "django.db.backends.postgresql",
"NAME": _required(env, "POSTGRES_DB"),
"USER": _required(env, "POSTGRES_USER"),
"PASSWORD": _required(env, "POSTGRES_PASSWORD"),
"HOST": _required(env, "POSTGRES_HOST"),
"PORT": _required(env, "POSTGRES_PORT"),
}
- Desglose Técnico: Retorna el diccionario de configuración para el backend nativo de Django
django.db.backends.postgresql. Utiliza_requiredpara exigir las cinco variables indispensables (POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_HOST,POSTGRES_PORT), asegurando que el conectorpsycopgreciba todos los parámetros de red y autenticación necesarios.
4.1.3 Análisis de _mysql(env)
def _mysql(env):
return {
"ENGINE": "django.db.backends.mysql",
"NAME": _required(env, "MYSQL_DB"),
"USER": _required(env, "MYSQL_USER"),
"PASSWORD": _required(env, "MYSQL_PASSWORD"),
"HOST": _required(env, "MYSQL_HOST"),
"PORT": _required(env, "MYSQL_PORT"),
"OPTIONS": {"charset": "utf8mb4"},
}
- Desglose Técnico: Vincula el motor
django.db.backends.mysqlsobre el controladormysqlclient. Incorpora de forma implícita la clave"OPTIONS": {"charset": "utf8mb4"}. Esta configuración es mandatoria para forzar a MySQL/MariaDB a operar con la codificación de caracteres UTF-8 de 4 bytes completa, previniendo la corrupción de datos ante caracteres especiales, acentos o emojis.
4.1.4 Análisis de _mssql(env)
def _mssql(env):
return {
"ENGINE": "mssql",
"NAME": _required(env, "MSSQL_DB"),
"USER": _required(env, "MSSQL_USER"),
"PASSWORD": _required(env, "MSSQL_PASSWORD"),
"HOST": _required(env, "MSSQL_HOST"),
"PORT": _required(env, "MSSQL_PORT"),
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"extra_params": "TrustServerCertificate=yes",
},
}
- Desglose Técnico: Asigna el valor corto
"ENGINE": "mssql". A diferencia de los motores integrados de Django,mssql-djangose registra como una librería de terceros que expone el identificador"mssql". Dentro del subdiccionario"OPTIONS", declara explícitamente la cadena del driver del sistema operativo (ODBC Driver 18 for SQL Server) y el parámetroTrustServerCertificate=yespara permitir conexiones TLS/SSL locales sin requerir la instalación previa de un certificado de CA autofirmado en la máquina de desarrollo.
4.1.5 Análisis de _oracle(env)
def _oracle(env):
host = _required(env, "ORACLE_HOST")
port = _required(env, "ORACLE_PORT")
service = _required(env, "ORACLE_SERVICE_NAME")
dsn = (
f"(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST={host})(PORT={port}))"
f"(CONNECT_DATA=(SERVICE_NAME={service})))"
)
return {
"ENGINE": "django.db.backends.oracle",
"NAME": dsn,
"USER": _required(env, "ORACLE_USER"),
"PASSWORD": _required(env, "ORACLE_PASSWORD"),
"HOST": host,
"PORT": "",
}
- Desglose Técnico: Modela la conexión hacia Oracle Database mediante el controlador
oracledb. En lugar de pasar un nombre de base de datos simple enNAME, construye un descriptorDSNestructurado de baja capa utilizando la sintaxis de red de Oracle con el parámetroSERVICE_NAME. - Regla Crítica del Puerto Vacío (
"PORT": ""): En el diccionario retornado, se exigeORACLE_PORTpara formatear la cadenadsn, pero fuerza deliberadamente"PORT": "". Si Django detecta un número de puerto en la clavePORT, su backend interno altera la consulta de conexión e intenta interpretar el valor deNAMEcomo un SID tradicional en lugar de un DSN. Al fijar"PORT": "", se obliga a Django a utilizar el valor deNAMEde forma literal como una cadena de conexión DSN completa.
Paquete Inicial de Settings: config/settings/__init__.py
"""Configuración de StoreLab.
El paquete sustituye al settings.py generado por startproject.
DJANGO_SETTINGS_MODULE sigue siendo config.settings.
"""
import os
from pathlib import Path
# python-dotenv, instalado en este ISS. Lee .env antes de SECRET_KEY y DATABASES.
from dotenv import load_dotenv
from config.database import database_from_environ
BASE_DIR = Path(__file__).resolve().parent.parent.parent
load_dotenv(BASE_DIR / ".env")
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "").strip()
if not SECRET_KEY:
from django.core.exceptions import ImproperlyConfigured
raise ImproperlyConfigured("Defina DJANGO_SECRET_KEY en el archivo .env.")
DEBUG = os.environ.get("DJANGO_DEBUG", "False").strip().lower() in {"1", "true", "yes", "on"}
ALLOWED_HOSTS = [
host.strip()
for host in os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",")
if host.strip()
]
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
]
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "config.urls"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
],
},
},
]
WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"
DATABASES = database_from_environ(os.environ)
AUTH_PASSWORD_VALIDATORS = [
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]
LANGUAGE_CODE = "es"
TIME_ZONE = "America/Bogota"
USE_I18N = True
USE_TZ = True
STATIC_URL = "static/"
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
El módulo __init__.py ejecuta load_dotenv(BASE_DIR / ".env") de manera inmediata para cargar el entorno local. Establece la zona horaria en America/Bogota, el idioma en es con soporte I18N/TZ activo, valida de forma estricta que DJANGO_SECRET_KEY no esté vacía e invoca a database_from_environ(os.environ) para poblar el diccionario DATABASES.
Definida la arquitectura modular de la configuración, es preciso implementar la protección automatizada que evita la ejecución errónea bajo el intérprete global del sistema operativo.
5.0 Re-ejecución del Intérprete y Parches en los Puntos de Entrada
5.1 Mecanismo Anti-Error de Intérprete del Sistema (config/project_python.py)
Un problema recurrente en equipos de desarrollo ocurre cuando un desarrollador ejecuta el comando python3 manage.py en la terminal sin haber activado previamente el entorno virtual .venv. En esta situación, el sistema utiliza el intérprete global, el cual no posee instalados paquetes como python-dotenv, Django o los drivers de base de datos, generando fallos inmediatos por módulos no encontrados (ImportError).
Para solucionar esta fragilidad de forma totalmente transparente, se introduce el módulo config/project_python.py. Este módulo evalúa en tiempo de arranque si el proceso en ejecución está utilizando el Python del entorno .venv. De no ser así, re-ejecuta de manera automática el comando sustituyendo el proceso actual en memoria por el intérprete correcto alojado en .venv/bin/python.
"""Intérprete del proyecto.
Los paquetes se instalan dentro de .venv. El comando python3 manage.py
usa el Python del sistema y no ve esos paquetes.
use_project_python() vuelve a lanzar el mismo comando con .venv/bin/python.
"""
import os
import sys
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parent.parent
def use_project_python():
"""Reejecuta el proceso con el Python de .venv si aún no es ese intérprete."""
venv_root = PROJECT_ROOT / ".venv"
venv_python = venv_root / "bin" / "python"
if not venv_python.is_file():
return
try:
if Path(sys.prefix).resolve() == venv_root.resolve():
return
except OSError:
return
os.execv(venv_python, [str(venv_python), *sys.argv])
Análisis del Mecanismo use_project_python()
El script resuelve la ruta raíz del proyecto y verifica la existencia del binario .venv/bin/python. Compara la ruta resuelta del prefijo del sistema (Path(sys.prefix).resolve()) contra la raíz del entorno virtual (venv_root.resolve()). La comparación se realiza obligatoriamente sobre sys.prefix y no sobre sys.executable debido a que el binario de un entorno virtual en sistemas Linux/macOS es un enlace simbólico que apunta de regreso hacia el ejecutable global del sistema. Si las rutas no coinciden, invoca la llamada al sistema os.execv, la cual reemplaza la imagen del proceso en memoria por una nueva ejecución invocando directamente .venv/bin/python y preservando la totalidad de los argumentos pasados originalmente en sys.argv.
Parcheado Estricto de los Puntos de Entrada del Sistema
Para activar este mecanismo transparente y asegurar que .env sea leído antes de cargar cualquier componente de Django, se aplican parches explícitos en manage.py, config/wsgi.py y config/asgi.py:
1. Parche Completo en manage.py
- UBICAR:
- REEMPLAZAR POR:
import sys from pathlib import Path _ROOT = Path(__file__).resolve().parent if str(_ROOT) not in sys.path: sys.path.insert(0, str(_ROOT)) from config.project_python import use_project_python use_project_python() import os # python-dotenv se instaló en este ISS, dentro de .venv. from dotenv import load_dotenv - UBICAR:
- AGREGAR DEBAJO:
2. Parche Completo en config/wsgi.py
- UBICAR:
- REEMPLAZAR POR:
3. Parche Completo en config/asgi.py
- UBICAR:
- REEMPLAZAR POR:
6.0 Flujo de Ejecución del Entorno y Resolución de Invariantes
6.1 Traza de Carga de Configuración
En tiempo de ejecución, la información de infraestructura fluye a través de una cadena secuencial estricta desde los archivos planos de texto hasta quedar representada en las estructuras internas de Django.
Diagrama del Flujo de Datos
┌──────────────┐
│ Archivo │
│ .env │
└──────┬───────┘
│ (1) Inyección de variables
▼
┌──────────────┐
│ load_dotenv()│
└──────┬───────┘
│ (2) Poblamiento de os.environ (respetando variables preexistentes)
▼
┌────────────────────────┐
│ settings/__init__.py │
└──────┬─────────────────┘
│ (3) Invocación con os.environ
▼
┌──────────────────────────────┐
│ database_from_environ(env) │
└──────┬───────────────────────┘
│ (4) Selección e inspección de DB_ENGINE
▼
┌────────────────────────┐
│ DATABASES['default'] │
└──────┬─────────────────┘
│ (5) Diccionario configurado
▼
┌────────────────────────┐
│ ORM de Django │
└────────────────────────┘
Invariantes Arquitectónicas de la Solución
- Un Solo Alias Activo: En el diccionario
DATABASESexpuesto por Django solo existe activado el alias"default"en cada proceso. El ORM opera de forma agnóstica sin conocer cuál motor físico está respondiendo por debajo. - Precedencia de Sobreescritura CLI (CLI Overrides): La función
load_dotenv()está diseñada por especificación para no sobrescribir variables de entorno que ya hayan sido definidas explícitamente en la sesión del shell. Esta propiedad permite alternar el motor de base de datos sobre la marcha anteponiendo la variable en la línea de comandos sin modificar el archivo.env.
Demostración Práctica: Traza de Sobreescritura CLI
Supóngase que el archivo .env contiene la configuración por defecto para PostgreSQL (DB_ENGINE=postgresql). Si el desarrollador desea validar el motor MySQL en una ejecución aislada, invoca el comando inyectando la variable en el shell:
Traza Interna de Precedencia del Proceso:
1. El Shell del sistema operativo inicializa el proceso hijo de Python asignando os.environ["DB_ENGINE"] = "mysql".
2. manage.py intercepta el proceso mediante use_project_python(), preservando el diccionario de entorno os.environ.
3. Se ejecuta load_dotenv(). Al inspeccionar os.environ, detecta que DB_ENGINE ya posee el valor "mysql". Por ende, omite la lectura de esa clave desde .env.
4. settings/__init__.py evalúa database_from_environ(os.environ).
5. database_from_environ recibe "mysql", invoca a _mysql(os.environ) y configura DATABASES["default"] con la clave "ENGINE": "django.db.backends.mysql".
6. La comprobación del sistema de Django valida la configuración contra MySQL sin alterar físicamente el archivo .env del disco.
7.0 Diagnóstico de Errores Frecuentes y Soluciones
7.1 Matriz de Fallos Comunes en ISS-01
| Escenario de Error | Causa Raíz | Efecto Observado | Solución Paso a Paso |
|---|---|---|---|
| Instalación Global de Librerías | Ejecución del comando pip sin haber activado previamente el entorno virtual con source .venv/bin/activate. |
Los paquetes se instalan en el Python global. El directorio .venv permanece sin los drivers. python -m pip freeze no refleja dependencias. |
1. Ejecute source .venv/bin/activate.2. Verifique con which python que apunte a .venv/bin/python.3. Reinstale los paquetes mediante python -m pip install -r requirements.txt. |
| Variable Obligatoria Faltante | Ausencia o vacuidad de una clave requerida en .env (ej. POSTGRES_DB="") para el motor activo. |
El arranque del proceso se interrumpe y la función _required lanza la excepción ImproperlyConfigured. |
1. Abra el archivo .env.2. Localice la sección correspondiente al DB_ENGINE seleccionado.3. Asigne un valor no vacío a la variable reportada en la traza. |
| Declaración de Motor No Permitido | Asignación de un nombre de motor no soportado en .env (ejemplo: DB_ENGINE=sqlite). |
La función database_from_environ valida contra ALLOWED_ENGINES y lanza ImproperlyConfigured. |
1. Abra el archivo .env.2. Corrija la variable DB_ENGINE utilizando únicamente: postgresql, mysql, mssql o oracle. |
| Omisión del Driver ODBC del SO | Se selecciona DB_ENGINE=mssql pero la máquina cliente no tiene instalado el driver C nativo de Microsoft. |
Al intentar conectar, la librería pyodbc lanza un error del sistema operativo por imposibilidad de cargar el driver dinámico. |
1. Instale en el sistema operativo el paquete oficial ODBC Driver 18 for SQL Server.2. Verifique en database.py que la cadena "driver" coincida con el nombre exacto del driver instalado. |
| Error de Sintaxis en DSN de Oracle | La función _oracle no vacía la clave PORT, provocando que Django interprete la cadena como un SID. |
La conexión falla con error de sintaxis de red Oracle (ORA-12514 o ORA-12541). |
1. Abra config/database.py.2. Confirme que la función _oracle retorne obligatoriamente "PORT": "".3. Asegúrese de que ORACLE_SERVICE_NAME esté asignado en .env. |
8.0 Matriz de Aceptación, Evidencias y Tabla GATE
8.1 Verificación Formal del Incremento
La evaluación del incremento ISS-01 sigue una metodología formal basada en Criterios de Aceptación (AC), Evidencias Técnicas de ejecución (EVI) y una Puerta de Control (GATE) que determina la aprobación del estado del sistema.
Criterios de Aceptación Detallados
AC-01-01: Las cuatro funciones generadoras (postgresql,mysql,mssql,oracle) producen exactamente la claveENGINEdocumentada para cada backend.AC-01-02: La especificación de un motor no permitido (ej.DB_ENGINE=sqlite) falla de manera inmediata lanzando la excepciónImproperlyConfigured.AC-01-03: La ausencia o vacuidad de una variable requerida para el motor activo detiene la configuración lanzandoImproperlyConfiguredantes de intentar la conexión.AC-01-04: El DSN generado dinámicamente para el motor Oracle contiene de forma explícita el parámetroSERVICE_NAME.
Evidencia Técnica Asociada
EVI-01-01: Ejecución exitosa de la suite de pruebas unitariasDatabaseSelectionTests(definida formalmente e integrada en el proyecto durante el ISS-03 dentro deapps/security/tests.py). La suite utilizaSimpleTestCasepara validar la lógica pura de selección de base de datos en memoria sin requerir la apertura de sockets de red reales. Para el alcance del ISS-01, la verificación temporal de sistema se efectúa mediantepython manage.py check.
Tabla del GATE Formal
| AC | Verificación | Evidencia | Resultado |
|---|---|---|---|
| AC-01-01 | Test por motor | EVI-01-01 | PASS |
| AC-01-02 | Test de motor inválido | EVI-01-01 | PASS |
| AC-01-03 | Test de variable vacía | EVI-01-01 | PASS |
| AC-01-04 | Aserción SERVICE_NAME | EVI-01-01 | PASS |
Habiendo auditado y alcanzado el estado PASS en la totalidad de las verificaciones del GATE, la sección final presenta una evaluación teórica orientada a consolidar la defensa del trabajo realizado.
9.0 Cuestionario de Defensa Oral y Evaluación Técnica
9.1 Preguntas de Evaluación Teórico-Práctica
Las siguientes preguntas están diseñadas para evaluar la comprensión conceptual profunda y las decisiones de diseño arquitectónico tomadas durante la ejecución del ISS-01, superando la mera memorización de comandos CLI.
1. ¿Por qué se requiere reemplazar el archivo config/settings.py generado originalmente por Django por un paquete de Python config/settings/ con un archivo __init__.py?
Respuesta Pautada:
En Python, un archivo de módulo y una carpeta de paquete no pueden coexistir con el mismo nombre en un mismo nivel de directorio. El comando startproject crea un archivo simple config/settings.py. Para evolucionar hacia una arquitectura modular sin romper la variable de entorno DJANGO_SETTINGS_MODULE="config.settings", es necesario eliminar el archivo monolítico settings.py y crear el directorio config/settings/. Al incluir un archivo __init__.py dentro de dicho directorio, Python interpreta el paquete config.settings como un módulo importable estándar, permitiendo fragmentar la configuración interna manteniendo una ruta de importación estable para el resto del sistema.
2. ¿Cuál es el mecanismo técnico mediante el cual config/project_python.py evita el uso accidental del Python del sistema al ejecutar python3 manage.py? Explique el rol de sys.prefix y os.execv.
Respuesta Pautada:
La función use_project_python() resuelve la ruta física del directorio del entorno virtual .venv. Compara la ruta de sys.prefix (que indica la raíz de instalación del intérprete actualmente en ejecución) con la ruta resuelta de .venv. Se utiliza sys.prefix en lugar de sys.executable porque el ejecutable binario en un entorno virtual suele ser un enlace simbólico hacia el Python del sistema operativo. Si las rutas no coinciden, significa que el usuario ejecutó el comando con el Python global. En ese punto, el script invoca la llamada al sistema os.execv(venv_python, [str(venv_python), *sys.argv]), la cual reemplaza de forma transparente la imagen del proceso actual en memoria por una nueva instancia del ejecutable .venv/bin/python, preservando exactamente los mismos argumentos pasados por la CLI (sys.argv).
3. ¿Por qué la función _oracle fuerza un parámetro PORT vacío ("") en el diccionario resultante de la configuración de base de datos?
Respuesta Pautada:
En la capa de abstracción de base de datos de Django para Oracle (django.db.backends.oracle), si la clave PORT dentro de DATABASES["default"] contiene una cadena con un número de puerto (ejemplo: "1521"), el backend intenta construir internamente la conexión asumiendo una sintaxis de SID tradicional. Al forzar la clave PORT a una cadena vacía (""), se obliga a Django a tomar el contenido presente en el campo NAME como un descriptor de conexión literal DSN (Data Source Name). Esto permite inyectar la cadena estructurada con el parámetro ORACLE_SERVICE_NAME requerida por instancias modernas de Oracle sin interferencias de la lógica por defecto del backend.
4. ¿Qué diferencia técnica existe entre la forma en que mssql-django y los conectores nativos de Django registran sus motores en la clave ENGINE?
Respuesta Pautada:
Los conectores nativos incluidos e integrados en el núcleo de Django utilizan rutas de importación de módulos Python completas dentro del espacio de nombres del marco de trabajo (por ejemplo, django.db.backends.postgresql o django.db.backends.mysql). Por el contrario, el adaptador para SQL Server es una librería de terceros (mssql-django). Este paquete registra explícitamente un alias corto denominado "mssql" dentro del punto de entrada de conectores de Django. Por lo tanto, al configurar SQL Server, la clave ENGINE debe declararse literalmente como "ENGINE": "mssql", y no mediante el espacio de nombres tradicional de Django.
5. ¿Por qué la función load_dotenv() no sobrescribe variables de entorno previamente definidas en la sesión de la terminal y qué ventaja práctica ofrece esto durante las pruebas?
Respuesta Pautada:
Por diseño y comportamiento estándar, la función load_dotenv() respeta las variables que ya existen previamente en el diccionario del sistema os.environ. Si una variable ya fue declarada en el entorno del proceso padre (la terminal), load_dotenv() no reemplaza su valor con el contenido del archivo .env. La ventaja práctica fundamental de este comportamiento es que permite realizar sobreescrituras sobre la marcha en la línea de comandos (CLI Overrides). Un desarrollador o un pipeline de CI/CD puede ejecutar comandos como DB_ENGINE=mysql python manage.py check para probar temporalmente un motor distinto en un proceso aislado sin necesidad de modificar ni alterar físicamente los archivos .env o el código fuente del proyecto.
Navegación de la ruta: ← ISS-00 · 🛠 Construir · ↑ Ruta Django · → ISS-01 · 🛠 Construir