El sistema sites de Django es un framework genérico que te permite operar múltiples sitios web desde la misma base de datos, y desde el mismo proyecto de Django. Éste es un concepto abstracto, y puede ser difícil de entender, así que comenzaremos mostrando algunos escenarios en donde sería útil usarlo.
14.2.1. Escenario 1: reuso de los datos en múltiples sitios
Como explicamos en el Capítulo 1, los sitios LJWorld.com y Lawrence.com, que funcionan gracias a Django, son operados por la misma organización de prensa, el diario Lawrence Journal-World de Lawrence, Kansas. LJWorld.com se enfoca en noticias, mientras que Lawrence.com se enfoca en el entretenimiento local. Pero a veces los editores quieren publicar un artículo en ambos sitios.
La forma cabeza dura de resolver el problema sería usar una base de datos para cada sitio, y pedirle a los productores que publiquen la misma nota dos veces: una para LJWorld.com y nuevamente para Lawrence.com. Pero esto es ineficiente para los productores del sitio, y es redundante conservar múltiples copias de la misma nota en las bases de datos.
¿Una solución mejor? Que ambos sitios usen la misma base de datos de artículos, y que un artículo esté asociado con uno o más sitios por una relación de muchos-a-muchos. El framework sites de Django, proporciona la tabla de base de datos que hace que los artículos se puedan relacionar de esta forma. Sirve para asociar datos con uno o más "sitios".
14.2.2. Escenario 2: alojamiento del nombre/dominio de tu sitio en un solo lugar
Los dos sitios LJWorld.com y Lawrence.com, tienen la funcionalidad de alertas por correo electrónico, que les permite a los lectores registrarse para obtener notificaciones. Es bastante básico: un lector se registra en un formulario web, e inmediatamente obtiene un correo electrónico que dice "Gracias por su suscripción".
Sería ineficiente y redundante implementar el código del procesamiento
de registros dos veces, así que los sitios usan el mismo código detrás
de escena. Pero la noticia "Gracias por su suscripción" debe ser
distinta para cada sitio. Empleando objetos Site
, podemos abstraer
el agradecimiento para usar los valores del nombre y dominio del
sitio, variables name
(ej. LJWorld.com
) y domain
(ej.
www.ljworld.com
).
El framework sites te proporciona un lugar para que puedas almacenar
el nombre (name
) y el dominio (domain
) de cada sitio de tu
proyecto, lo que significa que puedes reutilizar estos valores de
manera genérica.
14.2.3. Modo de uso del framework sites
Sites más que un framework, es una serie de convenciones. Toda la cosa se basa en dos conceptos simples:
- el modelo
Site
, que se halla endjango.contrib.sites
, tiene los camposdomain
yname
. - la opción de configuración
SITE_ID
especifica el ID de la base de datos del objetoSite
asociado con este archivo de configuración en particular.
La manera en que uses estos dos conceptos queda a tu criterio, pero Django los usa de varios modos de manera automática, siguiendo convenciones simples.
Para instalar la aplicación sites, sigue estos pasos:
- Agrega
'django.contrib.sites'
a tuINSTALLED_APPS
. - Ejecuta el comando
manage.py syncdb
para instalar la tabladjango_site
en tu base de datos. - Agrega uno o más objetos
Site
, por medio del sitio de administración de Django, o por medio de la API de Python. Crea un objetoSite
para cada sitio/dominio que esté respaldado por este proyecto Django. - Define la opción de configuración
SITE_ID
en cada uno de tus archivos de configuración (settings). Este valor debería ser el ID de base de datos del objetoSite
para el sitio respaldado por el archivo de configuración.
14.2.4. Las capacidades del framework Sites
Las siguientes secciones describen las cosas que puedes hacer con este framework.
14.2.4.1. Reuso de los datos en múltiples sitios
Para reusar los datos en múltiples sitios, como explicamos en el
primer escenario, simplemente debes agregarle un campo
muchos-a-muchos, ManyToManyField
hacia Site
en tus
modelos. Por ejemplo:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(maxlength=200)
# ...
sites = models.ManyToManyField(Site)
Esa es toda la infraestructura necesaria para asociar artículos con
múltiples sitios en tu base de datos. Con eso en su lugar, puedes
reusar el mismo código de vista para múltiples sitios. Continuando con
el modelo Article
del ejemplo, aquí mostramos cómo luciría una
vista article_detail
:
from django.conf import settings
def article_detail(request, article_id):
try:
a = Article.objects.get(id=article_id, sites__id=settings.SITE_ID)
except Article.DoesNotExist:
raise Http404
# ...
esta función de vista es reusable porque chequea el sitio del artículo
dinámicamente, según cuál sea el valor de la opción SITE_ID
.
Por ejemplo, digamos que el archivo de configuración de LJWorld.com
tiene un SITE_ID
asignado a 1
, y que el de Lawrence.com lo
tiene asignado a 2
. Si esta vista es llamada cuando el archivo de
configuración de LJWorld.com está activado, entonces la búsqueda de
artículos se limita a aquellos en que la lista de sitios incluye
LJWorld.com.
14.2.4.2. Asociación de contenido con un solo sitio
De manera similar, puedes asociar un modelo con el modelo Site
en una
relación muchos-a-uno, usando ForeignKey
.
Por ejemplo, si un artículo sólo se permite en un sitio, puedes usar un modelo como este:
from django.db import models
from django.contrib.sites.models import Site
class Article(models.Model):
headline = models.CharField(maxlength=200)
# ...
site = models.ForeignKey(Site)
Este tiene los mismos beneficios, como se describe en la última sección.
14.2.4.3. Obtención del sitio actual desde las vistas
A un nivel más bajo, puedes usar el framework sites en tus vistas de Django para hacer cosas particulares según el sitio en el cual la vista sea llamada. Por ejemplo:
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
else:
# Do something else.
Por supuesto, es horrible meter en el código el ID del sitio de esa manera. Una forma levemente más limpia de lograr lo mismo, es chequear el dominio actual del sitio:
from django.conf import settings
from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get(id=settings.SITE_ID)
if current_site.domain == 'foo.com':
# Do something
else:
# Do something else.
Este fragmento de código usado para obtener el objeto Site
según
el valor de settings.SITE_ID
es tan usado, que el administrador de
modelos de Site
(Site.objects
) tiene un método
get_current()
. El siguiente ejemplo es equivalente al anterior:
from django.contrib.sites.models import Site
def my_view(request):
current_site = Site.objects.get_current()
if current_site.domain == 'foo.com':
# Do something
else:
# Do something else.
Nota En este último ejemplo, no hay necesidad de importar django.conf.settings
.
14.2.4.4. Obtención del dominio actual para ser mostrado
Una forma DRY (acrónimo del inglés Don't Repeat Yourself, "no te repitas") de
guardar el nombre del sitio y del dominio, como explicamos en "Escenario 2:
alojamiento del nombre/dominio de tu sitio en un solo lugar", se logra
simplemente haciendo referencia a name
y a domain
del objeto Site
actual. Por ejemplo:
from django.contrib.sites.models import Site
from django.core.mail import send_mail
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
current_site = Site.objects.get_current()
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])
# ...
Continuando con nuestro ejemplo de LJWorld.com y Lawrence.com, en Lawrence.com el correo electrónico tiene como sujeto la línea "Gracias por suscribirse a las alertas de lawrence.com". En LJWorld.com, en cambio, el sujeto es "Gracias por suscribirse a las alertas de LJWorld.com". Este comportamiento específico para cada sitio, también se aplica al cuerpo del correo electrónico.
Una forma aún más flexible (aunque un poco más pesada) de hacer lo
mismo, es usando el sistema de plantillas de Django. Asumiendo que
Lawrence.com y LJWorld.com tienen distintos directorios de plantillas
(TEMPLATE_DIRS
), puedes simplemente delegarlo al sistema de
plantillas así:
from django.core.mail import send_mail
from django.template import loader, Context
def register_for_newsletter(request):
# Check form values, etc., and subscribe the user.
# ...
subject = loader.get_template('alerts/subject.txt').render(Context({}))
message = loader.get_template('alerts/message.txt').render(Context({}))
send_mail(subject, message, '[email protected]', [user_email])
# ...
En este caso, debes crear las plantillas subject.txt
y
message.txt
en ambos directorios de plantillas, el de LJWorld.com
y el de Lawrence.com . Como mencionamos anteriormente, eso te da más
flexibilidad, pero también es más complejo.
Una buena idea es explotar los objetos Site
lo más posible, para
que no haya una complejidad y una redundancia innecesarias.
14.2.4.5. Obtención del dominio actual para las URLs completas
La convención de Django de usar get_absolute_url()
para obtener
las URLs de los objetos sin el dominio, está muy bien. Pero en en
algunos casos puedes querer mostrar la URL completa — con http://
y el dominio, y todo — para un objeto. Para hacerlo, puedes usar el
framework sites. Este es 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'
>>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
'http://example.com/mymodel/objects/3/'
14.2.5. CurrentSiteManager
Si los Site
juegan roles importantes en tu aplicación, considera el
uso del útil CurrentSiteManager
en tu modelo (o modelos). Es un
administrador de modelos (consulta el Apéndice B) que filtra
automáticamente sus consultas para incluir sólo los objetos asociados
al Site
actual.
Usa CurrentSiteManager
agregándolo a tu modelo explícitamente. Por
ejemplo:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to='/home/photos')
photographer_name = models.CharField(maxlength=100)
pub_date = models.DateField()
site = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager()
Con este modelo, Photo.objects.all()
retorna todos los objetos Photo
de
la base de datos, pero Photo.on_site.all()
retorna sólo los objetos
Photo
asociados con el sitio actual, de acuerdo a la opción de configuración
SITE_ID
.
En otras palabras, estas dos sentencias son equivalentes:
Photo.objects.filter(site=settings.SITE_ID) Photo.on_site.all()
¿Cómo supo CurrentSiteManager
cuál campo de Photo
era el Site
? Por
defecto busca un campo llamado site
. Si tu modelo tiene un campo
ForeignKey
o un campo ManyToManyField
llamado de otra forma que
site
, debes pasarlo explícitamente como el parámetro para
CurrentSiteManager
. El modelo a continuación, que tiene un campo llamado
publish_on
, lo demuestra:
from django.db import models
from django.contrib.sites.models import Site
from django.contrib.sites.managers import CurrentSiteManager
class Photo(models.Model):
photo = models.FileField(upload_to='/home/photos')
photographer_name = models.CharField(maxlength=100)
pub_date = models.DateField()
publish_on = models.ForeignKey(Site)
objects = models.Manager()
on_site = CurrentSiteManager('publish_on')
Si intentas usar CurrentSiteManager
y pasarle un nombre de campo que no
existe, Django lanzará un ValueError
.
Nota Probablemente querrás tener un Manager
normal (no específico al sitio)
en tu modelo, incluso si usas CurrentSiteManager
. Como se explica en el
Apéndice B, si defines un manager manualmente, Django no creará
automáticamente el manager objects = models.Manager()
.
Además, algunas partes de Django — el sitio de administración y las vistas
genéricas — usan el manager que haya sido definido primero en el
modelo. Así que si quieres que el sitio de administración tenga acceso a
todos los objetos (no sólo a los específicos al sitio actual), pon un
objects = models.Manager()
en tu modelo, antes de definir
CurrentSiteManager
.
14.2.6. El uso que hace Django del framework Sites
Si bien no es necesario que uses el framework sites, es extremadamente
recomendado, porque Django toma ventaja de ello en algunos lugares. Incluso si
tu instalación de Django está alimentando a un solo sitio, deberías tomarte unos
segundos para crear el objeto site con tu domain
y name
, y apuntar su
ID en tu opción de configuración SITE_ID
.
Este es el uso que hace Django del framework sites:
- En el framework redirects (consulta la sección "Redirects" más
adelante), cada objeto redirect está asociado con un sitio en
particular. Cuando Django busca un redirect, toma en cuenta el
SITE_ID
actual. - En el framework comments, cada comentario está asociado con un sitio
en particular. Cuando un comentario es posteado, su
site
es asignado alSITE_ID
actual, y cuando los comentarios son listados con la etiqueta de plantillas apropiada, sólo los comentarios del sitio actual son mostrados. - En el framework flatpages (consulta la sección "Flatpages" más
adelante), cada página es asociada con un sitio en particular. Cuando una
página es creada, tú especificas su
site
, y el middleware de flatpage chequea elSITE_ID
actual cuando se traen páginas para ser mostradas. - En el framework syndication (consulta el Capítulo 11), las plantillas
para
title
ydescription
tienen acceso automático a la variable{{ site }}
, que es el objetoSite
que representa al sitio actual. Además, la conexión para proporcionar las URLs de los elementos usan eldomain
dede el objetoSite
actual si no especificas un fully qualified domain. - En el framework authentication (consulta el Capítulo 12), la vista
django.contrib.auth.views.login
le pasa el nombre delSite
actual a la plantilla como{{ site_name }}
.