# Documentation Django 5.2.8

# 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` et `name` 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

It’s fragile to hardcode the site IDs like that, in case they change. The cleaner way of accomplishing the same thing is to check the current site’s domain:

    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 :

1. Ajoutez `'django.contrib.sites'` à votre réglage `INSTALLED_APPS`.

2. Définissez un réglage `SITE_ID`:

       SITE_ID = 1

3. 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 le `Site`, et l’intergiciel `FlatpageFallbackMiddleware` vérifie le site actuel lorsqu’il recherche des pages statiques à afficher.

- Dans l’`infrastructure de syndication`, les gabarits pour `title` et `description` ont automatiquement accès à une variable `{{ site }}`, qui est l’objet `Site` représentant le site actuel. De même, le point d’entrée pour fournir les URL des éléments utilisent l’attribut `domain` de l’objet `Site` actuel si vous n’indiquez pas un domaine pleinement qualifié.

- Dans l’`infrastructure d'authentification`, la vue `django.contrib.auth.views.LoginView` passe le nom de l’objet `Site` actuel au gabarit sous forme de `{{ site_name }}`.

- La vue de raccourci (`django.contrib.contenttypes.views.shortcut`) utilise le domaine de l’objet `Site` 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 attributs `domain` et `name`), mais qui reçoit ses données à partir d’un objet `HttpRequest` de Django plutôt qu’à partir de la base de données.

  __init__(request)

    Définit les attributs `name` et `domain` selon les valeurs obtenues de `get_host()`.

A `RequestSite` object has a similar interface to a normal `Site` object, except its `__init__()` method takes an `HttpRequest` object. It’s able to deduce the `domain` and `name` by looking at the request’s domain. It has `save()` and `delete()` methods to match the interface of `Site`, but the methods raise `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’objet `Site` en cours, soit un objet `RequestSite` défini à partir de la requête. Il détermine le site actuel sur la base de `request.get_host()` si le réglage `SITE_ID` n’est pas défini.

  `request.get_host()` peut renvoyer à la fois un domaine et un port lorsque l’en-tête `Host` contient un numéro de port explicite, par exemple `example.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 pas `RequestSite` qui utilise toujours l’hôte non modifié.