Django incluye un framework opcional llamado «sitios». Es una herramienta para asociar objetos y funcionalidades a sitios web específicos, y es un lugar donde se almacenan los nombres de dominio y los nombres «verbose» de tus sitios web gestionados por Django.
Utilízalo si tu instalación de Django gestiona más de un sitio y necesitas diferenciar entre ellos de alguna manera.
El framework de sitios se basa principalmente en este modelo:
Un modelo para almacenar los atributos domain y name de un sitio web.
El nombre de dominio completo asociado con el sitio web. Por ejemplo, www.example.com.
Un nombre «verbose» legible por humanos para el sitio web.
La configuración SITE_ID especifica la ID de base de datos del objeto Site asociado con ese archivo de configuración particular. Si se omite la configuración, la función get_current_site() intentará obtener el sitio actual comparando el domain con el nombre del host desde el método request.get_host().
Cómo usar esto es cosa tuya, pero Django lo utiliza de varias maneras automáticamente a través de algunas convenciones.
Por qué utilizar sitios. Es mejor explicarlo mediante ejemplos.
Los sitios LJWorld.com y Lawrence.com eran operados por la misma organización de noticias – el periódico en línea Lawrence Journal-World en Lawrence, Kansas. LJWorld.com se centró en las noticias, mientras que Lawrence.com se centró en la entretenimiento local. Pero a veces los editores querían publicar un artículo en ambos sitios.
La forma ingenua de resolver el problema sería requerir a los productores de sitio que publiquen la misma historia dos veces: una vez para LJWorld.com y otra vez para Lawrence.com. Pero eso es ineficiente para los productores de sitio, y es redundante almacenar múltiples copias de la misma historia en la base de datos.
Una solución mejor elimina la duplicación de contenido: Ambos sitios utilizan la misma base de datos de artículos, y un artículo está asociado con uno o más sitios. En términos de modelo Django, eso se representa mediante una ManyToManyField en el modelo Article:
from django.contrib.sites.models import Site
from django.db import models
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
sites = models.ManyToManyField(Site)
Esto logra varias cosas de manera muy agradable:
Permite que los productores de sitio editen todo el contenido – en ambos sitios – en una sola interfaz (la administración de Django).
Significa que la misma historia no tiene que publicarse dos veces en la base de datos; solo tiene un registro único en la base de datos.
Permite a los desarrolladores de sitio utilizar el mismo código de vista de Django para ambos sitios. El código de vista que muestra una historia determinada verifica si la historia solicitada está en el sitio actual. Puede tener algo como esto:
from django.contrib.sites.shortcuts import get_current_site
def article_detail(request, article_id):
try:
a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id)
except Article.DoesNotExist:
raise Http404("Article does not exist on this site")
# ...
De manera similar, puedes asociar un modelo a la Site modelo en una relación muchos-a-uno utilizando ForeignKey.
Por ejemplo, si un artículo solo está permitido en un solo sitio, utilizarías un modelo como este:
from django.contrib.sites.models import Site
from django.db import models
class Article(models.Model):
headline = models.CharField(max_length=200)
# ...
site = models.ForeignKey(Site, on_delete=models.CASCADE)
Tiene los mismos beneficios que se describieron en la última sección.
Puedes utilizar el marco de sitios en tus vistas de Django para hacer cosas particulares según el sitio en el que se está llamando la vista. Por ejemplo:
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
pass
else:
# Do something else.
pass
Es frágil codificar directamente los IDs del sitio de esa manera, ya que pueden cambiar. La forma más limpia de lograr lo mismo es verificar el dominio actual del sitio:
from django.contrib.sites.shortcuts import get_current_site
def my_view(request):
current_site = get_current_site(request)
if current_site.domain == "foo.com":
# Do something
pass
else:
# Do something else.
pass
También tiene la ventaja de comprobar si el marco de sitios está instalado, y devuelve una instancia de ~django.contrib.sites.requests.RequestSite si no lo está.
Si no tienes acceso al objeto de solicitud, puedes utilizar el método get_current() del administrador del modelo Site. Debes asegurarte entonces de que tu archivo de configuración contenga la configuración SITE_ID. Este ejemplo es equivalente al anterior:
from django.contrib.sites.models import Site
def my_function_without_request():
current_site = Site.objects.get_current()
if current_site.domain == "foo.com":
# Do something
pass
else:
# Do something else.
pass
LJWorld.com y Lawrence.com tienen funcionalidad de alertas por correo electrónico, que permite a los lectores suscribirse para recibir notificaciones cuando suceda alguna novedad. Es bastante básico: un lector se suscribe en un formulario web y recibe inmediatamente un correo electrónico diciendo: «Gracias por tu suscripción».
No sería eficiente ni redundante implementar este código de procesamiento de registro dos veces, por lo que los sitios utilizan el mismo código detrás de escena. Pero la notificación «gracias por registrarte» necesita ser diferente para cada sitio. Al utilizar objetos Site, podemos abstraer la notificación «gracias» para usar los valores del nombre y el dominio del sitio actual.
Aquí tienes un ejemplo de cómo se ve la vista que maneja el formulario:
from django.contrib.sites.shortcuts import get_current_site
from django.core.mail import send_mail
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
current_site = get_current_site(request)
send_mail(
"Thanks for subscribing to %s alerts" % current_site.name,
"Thanks for your subscription. We appreciate it.\n\n-The %s team."
% (current_site.name,),
"editor@%s" % current_site.domain,
[user.email],
)
# ...
En la página de Lawrence.com, este correo electrónico tiene como línea de asunto «Gracias por suscribirte a las alertas de lawrence.com». En la página de LJWorld.com, el correo electrónico tiene como título «Gracias por suscribirte a las alertas de LJWorld.com». Lo mismo ocurre con el cuerpo del mensaje del correo electrónico.
Ten en cuenta que una forma aún más flexible (pero más pesada) de hacer esto sería utilizar el sistema de plantillas de Django. Suponiendo que Lawrence.com y LJWorld.com tienen directorios de plantillas diferentes (DIRS), podrías delegar a través del sistema de plantillas de la siguiente manera:
from django.core.mail import send_mail
from django.template import loader
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
subject = loader.get_template("alerts/subject.txt").render({})
message = loader.get_template("alerts/message.txt").render({})
send_mail(subject, message, "editor@ljworld.com", [user.email])
# ...
En este caso, tendrías que crear los archivos de plantilla subject.txt y message.txt para ambos directorios de plantillas de LJWorld.com y Lawrence.com. Esto te da más flexibilidad, pero también es más complejo.
Es una buena idea explotar lo máximo posible los objetos Site para eliminar la complejidad innecesaria y la redundancia.
La convención de Django get_absolute_url() es agradable para obtener la URL de los objetos sin el nombre del dominio, pero en algunos casos podrías querer mostrar la URL completa – con https:// y el dominio y todo – para un objeto. Para hacer esto, puedes utilizar el marco de sitios. Un ejemplo:
>>> from django.contrib.sites.models import Site
>>> obj = MyModel.objects.get(id=3)
>>> obj.get_absolute_url()
'/mymodel/objects/3/'
>>> Site.objects.get_current().domain
'example.com'
>>> "https://%s%s" % (Site.objects.get_current().domain, obj.get_absolute_url())
'https://example.com/mymodel/objects/3/'
Para habilitar el marco de sitios, sigue estos pasos:
Agrega 'django.contrib.sites' a la configuración INSTALLED_APPS.
Define una configuración SITE_ID:
SITE_ID = 1
Ejecuta migrate.
django.contrib.sites registra un manejador de señal post_migrate que crea un sitio predeterminado llamado example.com con el dominio example.com. Este sitio también se creará después de que Django cree la base de datos de prueba. Para establecer el nombre y el dominio correctos para tu proyecto, puedes utilizar una migración de datos.
Para servir sitios diferentes en producción, crearías un archivo de configuración separado con cada SITE_ID (quizás importando desde un archivo de configuración común para evitar duplicar configuraciones compartidas) y luego especificarías el módulo de configuración apropiado DJANGO_SETTINGS_MODULE para cada sitio.
Site¶Como el sitio actual se almacena en la base de datos, cada llamada a Site.objects.get_current() podría dar como resultado una consulta a la base de datos. Pero Django es un poco más astuto que eso: en la primera solicitud, el sitio actual se cacheará y cualquier llamada posterior devolverá los datos cacheados en lugar de consultar la base de datos.
Si por alguna razón deseas forzar una consulta de base de datos, puedes pedirle a Django que limpie el caché utilizando Site.objects.clear_cache():
# First call; current site fetched from database.
current_site = Site.objects.get_current()
# ...
# Second call; current site fetched from cache.
current_site = Site.objects.get_current()
# ...
# Force a database query for the third call.
Site.objects.clear_cache()
current_site = Site.objects.get_current()
Si el modelo Site juega un papel clave en tu aplicación, considera utilizar el útil CurrentSiteManager en tus modelos. Es un administrador de modelos (manager) que filtra automáticamente sus consultas para incluir solo objetos asociados con el sitio actual Site.
Mandatory ID_DE_SITIO
La traducción es:
Usa :class:`~django.contrib.sites.managers.CurrentSiteManager` agregándolo explícitamente a tu modelo. Por ejemplo:
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
from django.db import models
class Photo(models.Model):
photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
site = models.ForeignKey(Site, on_delete=models.CASCADE)
objects = models.Manager()
on_site = CurrentSiteManager()
Con este modelo, Photo.objects.all() devolverá todos los objetos Photo en la base de datos, pero Photo.on_site.all() devolverá solo los objetos Photo asociados con el sitio actual, según la configuración SITE_ID.
Los textos traducidos manteniendo todas las etiquetas intactas son:
Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()
¿Cómo sabía CurrentSiteManager qué campo de Photo era la Site? Por defecto, CurrentSiteManager busca un campo llamado site o sites para filtrar. Si utilizas un campo con otro nombre que no sea site ni sites para identificar a los objetos de la clase Site relacionados con tu objeto, debes pasar explícitamente el nombre del campo personalizado como parámetro a CurrentSiteManager en tu modelo. El siguiente modelo, que tiene un campo llamado publish_on, demuestra esto:
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
from django.db import models
class Photo(models.Model):
photo = models.FileField(upload_to="photos")
photographer_name = models.CharField(max_length=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site, on_delete=models.CASCADE)
objects = models.Manager()
on_site = CurrentSiteManager("publish_on")
Si intentas utilizar CurrentSiteManager y pasas el nombre de un campo que no existe, Django levantará una ValueError.
Finalmente, ten en cuenta que probablemente querrás mantener un Manager normal (no específico del sitio) en tu modelo, incluso si utilizas CurrentSiteManager. Como se explica en la documentación sobre manejadores, si defines manualmente un manejador, Django no creará el objects = models.Manager() automático para ti. También ten en cuenta que ciertas partes de Django – específicamente, el sitio administrativo y las vistas genéricas – utilizan el primer manejador definido en el modelo, por lo que si deseas que tu sitio administrativo tenga acceso a todos los objetos (no solo los específicos del sitio), coloca objects = models.Manager() en tu modelo, antes de definir CurrentSiteManager.
Si utilizas frecuentemente este patrón:
from django.contrib.sites.models import Site
def my_view(request):
site = Site.objects.get_current()
...
Para evitar repeticiones, agrega django.contrib.sites.middleware.CurrentSiteMiddleware a MIDDLEWARE. El middleware establece el atributo site en cada objeto de solicitud, por lo que puedes utilizar request.site para obtener el sitio actual.
Aunque no es necesario que utilices el framework de sitios, se le aconseja fuertemente, ya que Django aprovecha su uso en algunas partes. Incluso si tu instalación de Django solo está alimentando un sitio único, debes tomar los dos segundos para crear la objeto del sitio con tu domain y name, y apuntar a su ID en tu SITE_ID configuración.
Aquí se muestra cómo utiliza Django el framework de sitios:
Los textos traducidos manteniendo todas sus etiquetas intactas son:
En el framework de páginas planas, cada página plana se asocia con un sitio particular. Cuando se crea una página plana, especificas su Site y el FlatpageFallbackMiddleware verifica el sitio actual al recuperar páginas planas para mostrar.
En el framework de sindicación, los templates para title y description tienen acceso automático a una variable {{ sitio }}, que es el objeto Site representando el sitio actual. Además, la función de proporcionar URLs de items utiliza el dominio del objeto Site actual si no se especifica un dominio completamente cualificado.
En el framework de autenticación, django.contrib.auth.views.LoginView pasa el nombre del sitio actual al template como {{ sitio_name }}.
La vista corta (django.contrib.contenttypes.views.shortcut) utiliza el dominio del objeto Site actual cuando calcula la URL de un objeto.
En el framework administrativo, el enlace «ver en sitio» utiliza el sitio actual para determinar el dominio del sitio al que se redirigirá.
RequestSite¶Algunas aplicaciones de django.contrib aprovechan el framework de sitios pero están arquitectadas de manera que no requieren que el framework de sitios esté instalado en la base de datos. (Algunas personas no quieren o simplemente no pueden instalar la tabla de base de datos adicional que requiere el framework de sitios.) Para esos casos, el framework proporciona una clase django.contrib.sites.requests.RequestSite, que se puede utilizar como fallback cuando el framework de sitios con base de datos no esté disponible.
Una clase que comparte la interfaz principal de Site (es decir, tiene atributos domain y name) pero obtiene sus datos de un objeto HttpRequest Django en lugar de una base de datos.
Establece los atributos name y domain con el valor de get_host().
Un objeto de tipo RequestSite tiene una interfaz similar a la de un objeto normal de tipo Site, excepto que su método __init__() toma un objeto de tipo HttpRequest. Puede deducir los parámetros domain y name examinando el dominio de la solicitud. Tiene métodos save() y delete() para coincidir con la interfaz del objeto de tipo Site, pero los métodos lanzan una NotImplementedError.
get_current_site¶Finalmente, para evitar código de fallback repetitivo, el marco proporciona la función django.contrib.sites.shortcuts.get_current_site().
Una función que comprueba si está instalado django.contrib.sites y devuelve el objeto de tipo Site actual o un objeto de tipo RequestSite basándose en la solicitud. Busca el sitio actual según request.get_host() si no está definido el parámetro SITE_ID.
Ambos, dominio y puerto pueden ser devueltos por request.get_host() cuando la cabecera Host tiene un puerto explícitamente especificado, e.g. example.com:80. En tales casos, si el lookup falla porque el host no coincide con ningún registro en la base de datos, se elimina el puerto y se vuelve a intentar el lookup con solo el dominio. Esto no aplica a RequestSite que siempre utiliza el host sin modificar.
may 31, 2026