Proyecto Django multidominio para las webs de proyectos del ICCMU
Este repositorio será la nueva base común para las webs de proyectos del ICCMU (Fondos, Madmusic, etc.) en un solo despliegue Django.
fondos_v1 desplegado en Azure con:
- PostgreSQL
- Azure Blob Storage
Este nuevo proyecto NO debe romper ni tocar ese despliegue. La estrategia es:
- Crear primero la infraestructura básica (
proyectos,fondos_app,madmusic,test_app) - Más adelante, migrar el código existente de
fondos_v1afondos_app - Reutilizar la misma conexión PostgreSQL y Blob Storage cuando toque hacer el corte
- Crear un proyecto Django nuevo llamado
proyectos - Crear las apps:
core→ modelos genéricos reutilizablesfondos_app→ futura migración de la web actual de fondosmadmusic→ web Madmusictest_app→ app mínima para pruebas / sanidad
- Preparar un sistema multi-dominio:
fondos.iccmu.es→ rutas defondos_appmadmusic.iccmu.es→ rutas demadmusic- (otros dominios futuros → podrán añadirse igual)
- Configurar
settings.pypara leer:- PostgreSQL desde variables de entorno (las mismas que ya se usan en Azure si es posible)
- Azure Blob Storage para MEDIA (también vía variables de entorno)
- De momento, dejar
fondos_appcon una estructura mínima, sin intentar replicar todavía todo el código defondos_v1. La migración será una fase posterior.
- Python 3.10 o superior
- Django 4.2 o superior
- PostgreSQL (para producción, opcional en desarrollo)
- Variables de entorno configuradas (para producción)
Django>=4.2,<5.0
dj-database-url>=2.0.0
django-storages[azure]>=1.14.0
Pillow>=10.0.0iccmu_proyectos/
├── manage.py
├── proyectos/ # Proyecto Django
│ ├── __init__.py
│ ├── settings.py
│ ├── urls_root.py # URLConf por defecto / fallback
│ ├── urls_fondos.py # URLConf para fondos.iccmu.es
│ ├── urls_madmusic.py # URLConf para madmusic.iccmu.es
│ ├── urls_test.py # (opcional) URLConf para test_app u otros
│ ├── middleware.py # DomainUrlConfMiddleware
│ └── wsgi.py
├── core/ # Modelos genéricos (Proyecto, Entrada, Pagina, etc.)
├── fondos_app/ # Futura migración del código de fondos_v1
├── madmusic/ # Web Madmusic
├── test_app/ # App de pruebas
└── templates/
python -m venv venv
source venv/bin/activate # En Windows: venv\Scripts\activatepip install -r requirements.txtPara desarrollo local, puedes usar SQLite (por defecto). Para conectar a PostgreSQL o Azure Blob Storage, crea un archivo .env:
# .env (no commiteado)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
AZURE_ACCOUNT_NAME=tu_cuenta
AZURE_ACCOUNT_KEY=tu_clave
AZURE_MEDIA_CONTAINER=mediapython manage.py makemigrations
python manage.py migratepython manage.py createsuperuserpython manage.py runserverNota: Para probar el multi-dominio localmente, puedes añadir entradas en /etc/hosts:
127.0.0.1 fondos.iccmu.es
127.0.0.1 madmusic.iccmu.es
ALLOWED_HOSTS = [
"fondos.iccmu.es",
"madmusic.iccmu.es",
"localhost", # para desarrollo
"127.0.0.1", # para desarrollo
# se pueden añadir otros más adelante
]
ROOT_URLCONF = "proyectos.urls_root"
URLCONFS_BY_HOST = {
"fondos.iccmu.es": "proyectos.urls_fondos",
"madmusic.iccmu.es": "proyectos.urls_madmusic",
# p.ej. "test.iccmu.es": "proyectos.urls_test"
}Archivo: proyectos/middleware.py
import logging
from django.conf import settings
logger = logging.getLogger(__name__)
class DomainUrlConfMiddleware:
"""
Middleware que selecciona el URLConf apropiado basado en el dominio del host.
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
host = request.get_host().split(":")[0]
urlconf = settings.URLCONFS_BY_HOST.get(host, settings.ROOT_URLCONF)
if urlconf != settings.ROOT_URLCONF:
logger.debug(f"Usando URLConf '{urlconf}' para host '{host}'")
request.urlconf = urlconf
return self.get_response(request)En settings.py, añadir a MIDDLEWARE:
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",
"proyectos.middleware.DomainUrlConfMiddleware", # Añadir aquí
]proyectos/urls_root.py:
from django.urls import path
from django.http import HttpResponse
def default_view(request):
return HttpResponse("ICCMU – Proyecto multidominio (root)")
urlpatterns = [
path("", default_view),
]proyectos/urls_fondos.py:
from django.urls import path, include
urlpatterns = [
path("", include("fondos_app.urls")),
]proyectos/urls_madmusic.py:
from django.urls import path, include
urlpatterns = [
path("", include("madmusic.urls")),
]proyectos/urls_test.py (opcional):
from django.urls import path, include
urlpatterns = [
path("", include("test_app.urls")),
]Estos modelos servirán para Madmusic y otros proyectos futuros. Más adelante también se pueden reutilizar para Fondos si encaja.
core/models.py:
from django.db import models
class Proyecto(models.Model):
slug = models.SlugField(unique=True)
titulo = models.CharField(max_length=200)
acronimo = models.CharField(max_length=50, blank=True)
resumen = models.TextField(blank=True)
cuerpo = models.TextField(blank=True)
fecha_inicio = models.DateField(null=True, blank=True)
fecha_fin = models.DateField(null=True, blank=True)
url_oficial = models.URLField(blank=True)
def __str__(self):
return self.titulo
class Entrada(models.Model):
proyecto = models.ForeignKey(Proyecto, on_delete=models.CASCADE, related_name="entradas")
titulo = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
fecha_publicacion = models.DateField(auto_now_add=True)
resumen = models.TextField(blank=True)
cuerpo = models.TextField()
imagen_destacada = models.ImageField(
upload_to="entradas/portadas/%Y/%m/%d", blank=True, null=True
)
class Meta:
ordering = ["-fecha_publicacion"]
def __str__(self):
return self.titulo
class Pagina(models.Model):
proyecto = models.ForeignKey(
Proyecto, on_delete=models.SET_NULL, null=True, blank=True, related_name="paginas"
)
titulo = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
cuerpo = models.TextField()
def __str__(self):
return self.tituloRegistrar estos modelos en core/admin.py:
from django.contrib import admin
from .models import Proyecto, Entrada, Pagina
@admin.register(Proyecto)
class ProyectoAdmin(admin.ModelAdmin):
list_display = ['titulo', 'slug', 'acronimo', 'fecha_inicio', 'fecha_fin']
prepopulated_fields = {'slug': ('titulo',)}
@admin.register(Entrada)
class EntradaAdmin(admin.ModelAdmin):
list_display = ['titulo', 'proyecto', 'fecha_publicacion']
list_filter = ['proyecto', 'fecha_publicacion']
prepopulated_fields = {'slug': ('titulo',)}
@admin.register(Pagina)
class PaginaAdmin(admin.ModelAdmin):
list_display = ['titulo', 'slug', 'proyecto']
prepopulated_fields = {'slug': ('titulo',)}Por ahora, solo necesitamos lo básico para comprobar que el dominio fondos.iccmu.es enruta bien:
fondos_app/urls.py:
from django.urls import path
from .views import fondos_home
urlpatterns = [
path("", fondos_home, name="fondos_home"),
]fondos_app/views.py:
from django.http import HttpResponse
def fondos_home(request):
return HttpResponse("Futuros contenidos de FONDOS (fondos_app)")Más adelante se migrará aquí todo lo que ahora está en el proyecto fondos_v1 (modelos, vistas, templates…), tratando de reutilizar la misma base de datos y tablas.
madmusic/urls.py:
from django.urls import path
from .views import madmusic_home
urlpatterns = [
path("", madmusic_home, name="madmusic_home"),
]madmusic/views.py:
from django.shortcuts import render
from core.models import Proyecto
def madmusic_home(request):
proyecto = Proyecto.objects.filter(slug="madmusic").first()
return render(request, "madmusic/home.html", {"proyecto": proyecto})templates/madmusic/home.html puede ser una plantilla mínima:
<!DOCTYPE html>
<html>
<head>
<title>{% if proyecto %}{{ proyecto.titulo }}{% else %}Madmusic{% endif %}</title>
</head>
<body>
<h1>{% if proyecto %}{{ proyecto.titulo }}{% else %}Madmusic{% endif %}</h1>
{% if proyecto %}
<p>{{ proyecto.resumen }}</p>
{% endif %}
</body>
</html>App de pruebas para tener endpoints de diagnóstico si hace falta.
test_app/urls.py:
from django.urls import path
from .views import test_home
urlpatterns = [
path("", test_home, name="test_home"),
]test_app/views.py:
from django.http import JsonResponse
def test_home(request):
return JsonResponse({
"status": "ok",
"message": "Test app funcionando correctamente",
"host": request.get_host(),
})La idea es reutilizar la conexión PostgreSQL y el Blob Storage que ya usa fondos_v1, pero eso se conectará más adelante, cuando el nuevo proyecto esté estable. De momento, el proyecto debe:
- Preparar
settings.pypara leer todo de variables de entorno - Usar por defecto SQLite para desarrollo local si no hay variables definidas
Base de datos:
import os
from pathlib import Path
import dj_database_url
BASE_DIR = Path(__file__).resolve().parent.parent
DATABASE_URL = os.environ.get("DATABASE_URL")
if DATABASE_URL:
DATABASES = {
"default": dj_database_url.parse(DATABASE_URL, conn_max_age=600),
}
else:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}Azure Blob Storage:
AZURE_ACCOUNT_NAME = os.environ.get("AZURE_ACCOUNT_NAME")
AZURE_ACCOUNT_KEY = os.environ.get("AZURE_ACCOUNT_KEY")
AZURE_CONTAINER = os.environ.get("AZURE_MEDIA_CONTAINER", "media")
if AZURE_ACCOUNT_NAME and AZURE_ACCOUNT_KEY:
INSTALLED_APPS += ["storages"]
DEFAULT_FILE_STORAGE = "storages.backends.azure_storage.AzureStorage"
AZURE_STORAGE_KEY = AZURE_ACCOUNT_KEY
AZURE_STORAGE_ACCOUNT_NAME = AZURE_ACCOUNT_NAME
AZURE_CONTAINER = AZURE_CONTAINER
# La clase de storage personalizada se puede añadir más adelante
else:
# Usar almacenamiento local en desarrollo
MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = "/media/"- Crear proyecto Django llamado
proyectos - Crear apps:
core,fondos_app,madmusic,test_app - Configurar
settings.pycon:ALLOWED_HOSTSROOT_URLCONFURLCONFS_BY_HOSTDomainUrlConfMiddlewareañadido aMIDDLEWARE- Configuración de
DATABASESbasada en variableDATABASE_URLcon fallback a SQLite - Bloque preparado para Azure Blob (sin necesidad de implementar todo ahora)
- Crear
proyectos/urls_root.py - Crear
proyectos/urls_fondos.py - Crear
proyectos/urls_madmusic.py - Crear
proyectos/urls_test.py(opcional, básico) - Implementar
DomainUrlConfMiddlewareenproyectos/middleware.py
- Implementar modelos
Proyecto,Entrada,Paginaencore/models.py - Registrar modelos en
core/admin.py - Crear
fondos_app/urls.pyyfondos_app/views.pycon vista mínima - Crear
madmusic/urls.py,madmusic/views.pyy plantilla mínimatemplates/madmusic/home.html - Crear
test_app/urls.pyytest_app/views.pycon vista mínima de prueba
- Ejecutar
python manage.py makemigrations - Ejecutar
python manage.py migrate - Verificar que el multi-dominio funciona correctamente
- Probar cada dominio localmente (usando
/etc/hosts)
- Migrar código existente de
fondos_v1dentro defondos_app - Respetar nombres de tablas /
app_labelpara reutilizar la misma PostgreSQL - Migrar templates y assets
- Realizar pruebas exhaustivas antes del corte
- El proyecto está diseñado para ser fácilmente extensible a nuevos dominios
- La migración de
fondos_v1se realizará en una fase posterior, cuando la infraestructura base esté estable - Se recomienda usar un entorno de staging antes de conectar a la base de datos de producción
- Considerar añadir tests automatizados para el middleware multi-dominio