L’infrastructure des « sites »¶
Django est livré avec un système facultatif de « sites ». C’est un point d’extension pour associer des objets et des fonctionnalités à certains sites web, et c’est un lieu de regroupement pour les noms de domaine et les noms « explicites » de vos sites Django.
Utilisez-le si une seule installation de Django est utilisée pour plus d’un site Web et que vous avez besoin de différencier ces sites sur certains points.
L’infrastructure de sites est basée principalement sur ce modèle :
-
class
models.
Site
¶ Un modèle pour le stockage des attributs
domain
etname
d’un site web.-
domain
¶ Le nom de domaine pleinement qualifié associé au site web. Par exemple,
www.example.com
.
-
name
¶ Un nom lisible « explicite » pour le site web.
-
Le réglage SITE_ID
indique l’identifiant de base de données de l’objet Site
associé à ce fichier de réglages particulier. Si ce réglage est absent, la fonction get_current_site()
essaie d’obtenir le site actuel en comparant le domain
au nom d’hôte provenant de la méthode request.get_host()
.
La manière de l’utiliser est à votre convenance, mais Django l’utilise automatiquement dans un certain nombre de situations à l’aide de quelques conventions.
Exemple d’utilisation¶
Pourquoi auriez-vous besoin du système de sites ? Des exemples offrent la meilleure des explications.
Association de contenu à plusieurs sites différents¶
Les sites LJWorld.com et Lawrence.com étaient exploités par la même agence de presse – le journal Lawrence Journal-World à Lawrence, Kansas. LJWorld.com se concentrait sur l’actualité, tandis que Lawrence.com mettait l’accent sur le divertissement local. Mais parfois, les éditeurs veulaient publier un article sur les deux sites.
La manière naïve de résoudre ce problème serait d’exiger de chaque rédacteur de site de publier la même histoire deux fois : une fois pour LJWorld.com et une autre fois pour Lawrence.com. Mais c’est vraiment inefficace pour les producteurs des sites, sans compter qu’il est redondant de stocker plusieurs copies de la même histoire dans la base de données.
Une meilleure solution évite la duplication de contenu : les deux sites utilisent le même article dans la base de données et chaque article est associé à un ou plusieurs sites. Dans la terminologie des modèles Django, ceci est représenté par un champ ManyToManyField
dans le modèle 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)
Ceci effectue plusieurs choses de manière élégante :
Les producteurs de sites peuvent éditer tous les contenus, pour les deux sites, dans une même interface (l’administration de Django).
La même histoire n’a pas besoin d’être publiée deux fois dans la base de données ; elle constitue un seul enregistrement de base de données.
Les développeurs des sites peuvent utiliser le même code de vue Django pour les deux sites. Le code de vue qui affiche une histoire donnée vérifie que l’histoire demandée se trouve bien sur le site actuel. Cela donne quelque chose comme :
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") # ...
Association de contenu à un seul site¶
De même, vous pouvez associer un modèle au modèle Site
par une relation plusieurs-à-un, avec une clé ForeignKey
.
Par exemple, si un article ne doit apparaître que sur un seul site, vous pourriez utiliser un modèle tel que celui-ci :
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)
Les bénéfices sont les mêmes que ceux de la section précédente.
Interception du site actuel dans les vues¶
Vous pouvez exploiter l’infrastructure des sites dans les vues Django pour effectuer des opérations particulières sur la base du site pour lequel la vue est appelée. Par exemple :
from django.conf import settings
def my_view(request):
if settings.SITE_ID == 3:
# Do something.
pass
else:
# Do something else.
pass
Il est fragile de figer des identifiants de site de cette manière car ils peuvent changer. La façon propre de faire la même chose est de se baser sur le nom de domaine du site actuel :
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
Cela présente aussi l’avantage de vérifier si l’infrastructure des sites est installée et de renvoyer une instance RequestSite
quand ce n’est pas le cas.
Si vous n’avez pas accès à l’objet de requête, vous pouvez utiliser la méthode get_current()
du gestionnaire du modèle Site
. Vous devez ensuite vous assurer que le fichier des réglages contient bien le réglage SITE_ID
. Cet exemple est équivalent au précédent :
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
Accès au domaine actuel pour l’affichage¶
LJWorld.com et Lawrence.com ont tous deux une fonction d’alerte par courriel, ce qui permet aux lecteurs de s’inscrire pour recevoir des notifications lorsque des actualités apparaissent. C’est assez simple : un lecteur s’inscrit avec un formulaire web et reçoit immédiatement un courriel disant « Merci pour votre inscription ».
Il serait inefficace et redondant d’implémenter deux fois le code de ce processus d’inscription, ce qui fait que les sites utilisent le même code en arrière-plan. Mais le message « merci pour votre inscription » doit être différent pour chaque site. En utilisant des objets Site
, il est possible d’abstraire le message « merci » afin d’utiliser les valeurs name
et domain
correspondant au site actuel.
Voici un exemple qui pourrait constituer le code de gestion du formulaire dans la vue :
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],
)
# ...
Pour Lawrence.com, l’objet du courriel correspond à « Thanks for subscribing to lawrence.com alerts. ». Pour LJWorld.com, l’objet du courriel correspond à « Thanks for subscribing to LJWorld.com alerts. ». De même pour les corps des courriels.
Notez qu’une manière encore plus souple (mais plus lourde) de faire cela serait d’utiliser le système des gabarits de Django. En supposant que les sites Lawrence.com et LJWorld.com ont des répertoires de gabarits différents (DIRS
), il suffirait de confier la personnalisation des messages au système des gabarits, comme ceci :
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])
# ...
Dans ce cas, il faudrait encore créer les fichiers de gabarit subject.txt
et message.txt
dans les deux répertoires de gabarits LJWorld.com et Lawrence.com. Cela apporte un maximum de flexibilité, mais est aussi plus complexe.
Il est recommandé d’exploiter au maximum les objets Site
afin de supprimer de la complexité inutile et de la redondance.
Accès au domaine actuel pour des URL complètes¶
La convention get_absolute_url()
de Django est pratique pour obtenir les URL des objets sans le nom de domaine, mais dans certains cas, il peut être souhaitable d’afficher une URL complète d’un objet, avec https://
et le domaine. Pour cela, vous pouvez utiliser l’infrastructure des sites. Un exemple :
>>> 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/'
Activation de l’infrastructure des sites¶
Pour activer l’infrastructure des sites, suivez ces étapes :
Ajoutez
'django.contrib.sites'
à votre réglageINSTALLED_APPS
.Définissez un réglage
SITE_ID
:SITE_ID = 1
Exécutez
migrate
.
django.contrib.sites
inscrit un gestionnaire de signal post_migrate
qui crée un site par défaut nommé example.com
avec le domaine example.com
. Ce site est aussi créé à la suite de la création de la base de données de test par Django. Pour définir le nom et le domaine corrects pour votre projet, vous pouvez utiliser une migration de données.
Afin de servir différents sites en production, il s’agit de créer un fichier de réglages différent pour chacun avec un SITE_ID
spécifique (en important peut-être d’un fichier de réglages commun pour éviter de dupliquer les réglages partagés), puis de définir la valeur DJANGO_SETTINGS_MODULE
appropriée pour chaque site.
Mise en cache de l’objet Site
actuel¶
Comme le site actuel est stocké en base de données, chaque appel à Site.objects.get_current()
pourrait aboutir à une requête de base de données. Mais Django est un peu plus malin que cela : lors de la première requête, le site actuel est mis en cache et tout appel subséquent à cette méthode renvoie les données en cache plutôt que de recourir à la base de données.
Si pour une raison quelconque vous souhaitez forcer une nouvelle requête à la base de données, vous pouvez demander à Django d’effacer le contenu en cache en utilisant 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()
Le gestionnaire CurrentSiteManager
¶
-
class
managers.
CurrentSiteManager
¶
Si Site
joue un rôle majeur dans votre application, considérer l’utilisation du gestionnaire CurrentSiteManager
dans vos modèles. Il s’agit d’un gestionnaire de modèle qui filtre automatiquement ses requêtes pour n’inclure que les objets associés au Site
actuel.
SITE_ID
obligatoire
CurrentSiteManager
n’est utilisable qu’à la condition que le réglage SITE_ID
soit défini dans vos réglages.
Utilisez CurrentSiteManager
en l’ajoutant explicitement à vos modèles. Par exemple :
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()
Avec ce modèle, Photo.objects.all()
renvoie tous les objets Photo
de la base de données, mais Photo.on_site.all()
ne renvoie que les objets Photo
associés au site actuel, en fonction du réglage SITE_ID
.
Autrement dit, ces deux instructions sont équivalentes :
Photo.objects.filter(site=settings.SITE_ID)
Photo.on_site.all()
Comment CurrentSiteManager
sait-il quel champ de Photo
indique le Site
? Par défaut, CurrentSiteManager
cherche à filtrer soit selon une clé ForeignKey
nommée site
, soit selon un champ ManyToManyField
nommé sites
. Si vous utilisez un champ nommé différemment de``site`` ou sites
pour désigner les objets Site
auxquels vos objets sont reliés, il est alors nécessaire de passer explicitement le nom de champ personnalisé comme paramètre à CurrentSiteManager
dans le modèle. Le modèle suivant, qui possède un champ nommé publish_on
, démontre cela :
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 vous essayez d’utiliser CurrentSiteManager
et que vous lui indiquez un nom de champ qui n’existe pas, Django génère une exception ValueError
.
Finalement, notez qu’il est recommandé de conserver un Manager
normal (non lié aux sites) pour vos modèles, même si vous utilisez CurrentSiteManager
. Comme expliqué dans la documentation des gestionnaires, si vous définissez manuellement un gestionnaire, Django ne crée pas le gestionnaire automatique objects = models.Manager()
à votre place. Sachez également que certaines parties de Django, particulièrement le site d’administration et les vues génériques, utilisent le premier gestionnaire défini pour un modèle, donc si vous voulez que le site d’administration puisse accéder à tous les objets (et pas seulement ceux d’un site particulier), ajoutez objects = models.Manager()
dans vos modèles avant de définir CurrentSiteManager
.
Intergiciel de site¶
Si vous utilisez fréquemment ce motif :
from django.contrib.sites.models import Site
def my_view(request):
site = Site.objects.get_current()
...
Pour éviter les répétitions, ajoutez django.contrib.sites.middleware.CurrentSiteMiddleware
à MIDDLEWARE
. Cet intergiciel définit l’attribut site
pour chaque objet de requête, ce qui permet d’appeler request.site
pour accéder au site actuel.
Utilisation de l’infrastructure des sites par Django¶
Même s’il n’est pas obligatoire d’utiliser l’infrastructure des sites, elle est fortement encouragée car Django en tire profit à plusieurs endroits. Même si votre installation de Django n’est utilisée que par un seul site, prenez les quelques secondes nécessaires à la définition de l’objet site avec les bonnes valeurs domain
et name
, puis faites pointer le réglage SITE_ID
vers l’identifiant de cet objet.
Voici comment Django utilise l’infrastructure des sites :
- Dans l’
application de redirection
, chaque objet de redirection est associé à un site particulier. Lorsque Django recherche une redirection, il prend en compte le site actuel. - Dans l’
application des pages statiques
, chaque page statique est associée à un site particulier. Lorsqu’une page statique est créée, il faut indiquer leSite
, et l’intergicielFlatpageFallbackMiddleware
vérifie le site actuel lorsqu’il recherche des pages statiques à afficher. - Dans l’
infrastructure de syndication
, les gabarits pourtitle
etdescription
ont automatiquement accès à une variable{{ site }}
, qui est l’objetSite
représentant le site actuel. De même, le point d’entrée pour fournir les URL des éléments utilisent l’attributdomain
de l’objetSite
actuel si vous n’indiquez pas un domaine pleinement qualifié. - Dans l’
infrastructure d'authentification
, la vuedjango.contrib.auth.views.LoginView
passe le nom de l’objetSite
actuel au gabarit sous forme de{{ site_name }}
. - La vue de raccourci (
django.contrib.contenttypes.views.shortcut
) utilise le domaine de l’objetSite
actuel lors de la construction de l’URL d’un objet. - Dans le site d’administration, le lien « voir sur le site » utilise l’objet
Site
actuel pour connaître le domaine du site vers lequel la redirection s’opère.
Les objets RequestSite
¶
Certaines applications django.contrib s’appuient sur l’infrastructure des sites mais sont conçues pour ne pas dépendre de l’installation de l’application sites dans la base de données. (Certaines personnes ne souhaitent pas ou n’ont simplement pas la possibilité d’installer la table de base de données supplémentaire que demande l’infrastructure des sites.) Pour ces situations, l’application fournit une classe django.contrib.sites.requests.RequestSite
qui peut être utilisée comme solution de repli lorsque le complément en base de données de l’infrastructure des sites n’est pas disponible.
-
class
requests.
RequestSite
¶ Une classe qui partage l’interface principale de
Site
(c’est-à-dire qu’elle possède les attributsdomain
etname
), mais qui reçoit ses données à partir d’un objetHttpRequest
de Django plutôt qu’à partir de la base de données.-
__init__
(request)¶ Définit les attributs
name
etdomain
selon les valeurs obtenues deget_host()
.
-
Un objet RequestSite
possède une interface similaire à un objet Site
normal, sauf que sa méthode __init__()
accepte un objet HttpRequest
. Il est capable d’en déduire le domaine et le nom en examinant le domaine de la requête. Il possède des méthodes save()
et delete()
pour refléter l’interface de Site
, mais ces méthodes générent l’exception NotImplementedError
.
Le raccourci get_current_site
¶
Finalement, pour éviter du code redondant, le système fournit une fonction django.contrib.sites.shortcuts.get_current_site()
.
-
shortcuts.
get_current_site
(request)¶ Une fonction qui vérifie si
django.contrib.sites
est installé et qui renvoie soit l’objetSite
en cours, soit un objetRequestSite
défini à partir de la requête. Il détermine le site actuel sur la base derequest.get_host()
si le réglageSITE_ID
n’est pas défini.request.get_host()
peut renvoyer à la fois un domaine et un port lorsque l’en-têteHost
contient un numéro de port explicite, par exempleexample.com:80
. Dans de tels cas, si la recherche échoue parce que l’hôte ne correspond pas à un enregistrement dans la base de données, le port est enlevé et la recherche est relancée uniquement avec la partie du domaine. Ceci ne concerne pasRequestSite
qui utilise toujours l’hôte non modifié.