• 12 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 22/03/2024

Créez des relations plusieurs-à-plusieurs

Liez des utilisateurs avec des champs plusieurs-à-plusieurs

Jusqu’à présent, nous avons utilisé des relationsForeignKey  pour lier différents types de modèles les uns aux autres, ce qui crée des relations un-à-plusieurs (ou one-to-many). Par exemple, le modèlePhoto  a une relationForeignKeyàUser  par le champuploader. Vous ne pouvez lier chaque instance dePhoto  qu’à une instance deUser, mais vous pouvez lier une instance deUser  à de nombreuses instances différentes de   Photo

Relation un-à-plusieurs
Relation un-à-plusieurs

Vous pouvez également utiliser le  OneToOneField  pour créer des relations un-à-un (one-to-one). Dans ce cas, vous ne pouvez lier chaque instance du modèle A qu’à une instance du modèle B, et vice-versa.

Un cas d’usage courant pour les relations un-à-un : l’utilisation des modèles de profil pour étendre le modèle  User  . Dans notre cas, comme nous avons deux types d’utilisateurs — les créateurs et les abonnés — nous voudrions peut-être créer un modèle  Creator   et un modèle  Subscriber  , chacun ayant un  OneToOneField  relatif au  User  . Ceux-ci contiendront les informations nécessaires pour chaque utilisateur, mais qui ne sont significatives que pour les types créateur et abonné.

Relation un-à-un
Relation un-à-un

Enfin, vous avez également l’option du  ManyToManyField  , que vous pouvez utiliser pour créer des relations plusieurs-à-plusieurs. Imaginons que le modèle A a un  ManyToManyField  qui le lie au modèle B. Cela signifie qu’il peut être lié à plusieurs instances différentes du modèle B, et aussi qu’une instance du modèle B peut être liée à plusieurs instances différentes du modèle A.

Sur notre site, nous allons utiliser des relations plusieurs-à-plusieurs pour permettre aux utilisateurs de suivre d’autres utilisateurs. Nous voulons qu’un utilisateur puisse s’abonner à de nombreux créateurs différents, et que de nombreux autres utilisateurs puissent aussi s’abonner à lui. La relation plusieurs-à-plusieurs est donc parfaitement adaptée.

Relation plusieurs-à-plusieurs
Relation plusieurs-à-plusieurs

Relation plusieurs-à-plusieurs

Implémentons maintenant ce schéma dans nos modèles. Découvrez en vidéo comment créer une relation plusieurs à plusieurs-à-plusieurs avec Django :

Étape 1 : Mettez à jour les modèles

Pour établir une relation plusieurs-à-plusieurs entre les utilisateurs, vous devez spécifier un  ManyToManyField  sur le modèle  User  , qui lie à un autre  User. Appelons le nôtre  follows (suit)  .

# authentication/models.py

    class User(AbstractUser):
    ...
    follows = models.ManyToManyField(
        'self',
        limit_choices_to={'role': CREATOR},
        symmetrical=False,
        verbose_name='suit',
    )

Le premier argument dans le  ManyToManyField  est le modèle avec lequel vous nouez une relation. Dans notre cas, il s’agit du même modèleUser, auquel nous référons avec'self'.

Vous pouvez limiter quels utilisateurs peuvent être suivis en utilisant le mot-clé optionnel limit_choices_to. Nous voulons que seuls les utilisateurs avec le rôle  CREATOR  puissent être suivis.

Dans ce cas particulier, où les deux modèles dans la relation plusieurs-à-plusieurs sont les mêmes, vous devez également préciser si la relation est symétrique. Les relations symétriques sont celles où il n’y a aucune différence entre les deux acteurs de la relation, comme quand on lie deux amis. Un utilisateur en suit un autre, donc vous précisez  symmetrical=False  . L’argument  symmetrical  n’est pas  requis si vous liez à un autre modèle que celui dans lequel le  ManyToManyField  est déclaré.

Maintenant, générez et exécutez les migrations :

(ENV) ~/fotoblog (master)
→ python manage.py makemigrations
Migrations for 'authentication':
    authentication/migrations/0003_user_follows.py
    - Add field follows to user

(ENV) ~/fotoblog (master)
→ python manage.py migrate
Operations to perform:
    Apply all migrations: admin, auth, authentication, blog, contenttypes, sessions
Running migrations:
    Applying authentication.0004_user_follows... OK

Ensuite, voyons comment utiliser des formulaires pour créer des relations plusieurs-à-plusieurs au sein d’une vue.

Étape 2 : Créez des relations plusieurs-à-plusieurs dans un formulaire

Pour créer la relation plusieurs-à-plusieurs dans la vue, vous devez d’abord définir le formulaire approprié. Comme les champs sont dans le modèleUser, vous pouvez utiliser unmodelForm.

Créez un formulaire qui permette à l’utilisateur de choisir d’autres utilisateurs qu’il veut suivre.

# blog/forms.py
from django.contrib.auth import get_user_model

User = get_user_model()


class FollowUsersForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['follows']

Étape 3 : Créez la vue

Gérez maintenant le formulaire dans une vue :

# blog/views.py

@login_required
def follow_users(request):
    form = forms.FollowUsersForm(instance=request.user)
    if request.method == 'POST':
        form = forms.FollowUsersForm(request.POST, instance=request.user)
        if form.is_valid():
            form.save()
            return redirect('home')
    return render(request, 'blog/follow_users_form.html', context={'form': form})

Avec le  ManyToManyField  , vous pouvez utiliser la fonction   ModelForm.save()  pour créer la relation plusieurs-à-plusieurs.

Étape 4 : Créez le gabarit

Ajoutez ensuite le gabarit, comme d’habitude.

# blog/templates/blog/follow_users_form.html
{% extends 'base.html' %}
{% block content %}
    <h2>Suivre des utilisateurs</h2>
    <form method="post">
        {{ form.as_p }}
        {% csrf_token %}
        <button type="submit" >Confirmer</button>
    </form>
{% endblock content %}

Étape 5 : Ajoutez le modèle d’URL

Ajoutez maintenant le modèle d’URL pour la nouvelle page.

# fotoblog/urls.py
urlpatterns = [
    …
    path('follow-users/', blog.views.follow_users, name='follow_users')
    
]

Étape 6 : Ajoutez un lien vers la nouvelle page

Ajoutez un lien dans la barre latérale.

# templates/base.html
…
{% if perms.blog.add_photo %}
    <p><a href="{% url 'photo_upload' %}">Télécharger une photo</a></p>
    <p><a href="{% url 'create_multiple_photos' %}">Télécharger plusieurs photos</a></p>
{% endif %}
<p><a href="{% url 'follow_users' %}">Suivre des utilisateurs</a></p>
<p><a href="{% url 'upload_profile_photo' %}">Changer la photo de profil</a></p>
...

Voyons à quoi ressemble ce formulaire :

Capture d'écran du formulaire
Capture d'écran du formulaire

Nous avons réussi ! Le formulaire contient tous les créateurs, et permet à l’utilisateur de les sélectionner et de les suivre. Il vous permet aussi de sélectionner plusieurs utilisateurs en maintenant le bouton CTRL/Commande appuyé. Le formulaire les stocke alors dans un champ plusieurs-à-plusieurs.

Voyons ensuite comment stocker des informations sur les relations plusieurs-à-plusieurs.

Stockez des données supplémentaires sur les relations plusieurs-à-plusieurs avec des tables intermédiaires

Vous aurez parfois besoin de stocker des données supplémentaires sur la relation entre deux instances du modèle liées par une relation plusieurs-à-plusieurs.

Disons que vous voulez autoriser un modèle  Blog  à avoir plusieurs contributeurs, et stocker un log de ce que chaque auteur a ajouté.

Pour cela, créez une table intermédiaire afin de stocker des données sur la relation. Lorsque vous utilisez un  ManyToManyField, Django produit lui-même cette table. Ici, vous la créez simplement manuellement à la place. Découvrez la procédure en vidéo :

Étape 1 : Créez la table intermédiaire

La table intermédiaire nécessite deux relations  ForeignKey  aux deux modèles impliqués dans la relation plusieurs-à-plusieurs. Pour notre application, c’est donc une relation vers le  User  , et une vers le  Blog  .

Nous allons également ajouter le champ  contribution  pour stocker des informations sur les contributions spécifiques de chaque auteur.

Voyons de quoi ça a l’air :

# blog/models.py
class BlogContributor(models.Model):
    contributor = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
    contribution = models.CharField(max_length=255, blank=True)
    
    class Meta:
        unique_together = ('contributor', 'blog')

 Nous avons défini l’attribut   unique_together  dans la classe   Meta  pour garantir qu’il n’y a qu’une seule instance de  BlogContributor  pour chaque paire   contributor  -   blog  .

Étape 2 : Mettez à jour le ManyToManyField pour utiliser la table intermédiaire

Ajoutez maintenant un  ManyToManyField  à  Blog  et dites-lui d’utiliser notre table intermédiaire.

Pour accomplir cela, spécifiez l’argument mot-clé  through  en déclarant le champ. Vous devez simplement lui montrer la direction du nouveau modèle. Pour l’instant, laissons l’ancien champ  author  en place, mais nous allons le rendre optionnel en l’autorisant à être  null  .

class Blog(models.Model):
    ...
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
    contributors = models.ManyToManyField(settings.AUTH_USER_MODEL, through='BlogContributor')
...

Générez maintenant les migrations.

→ python manage.py makemigrations
SystemCheckError: System check identified some issues:

ERRORS:
blog.Blog.author: (fields.E304) Reverse accessor for 'blog.Blog.author' clashes with reverse accessor for 'blog.Blog.contributors'.
    HINT: Add or change a related_name argument to the definition for 'blog.Blog.author' or 'blog.Blog.contributors'.
blog.Blog.contributors: (fields.E304) Reverse accessor for 'blog.Blog.contributors' clashes with reverse accessor for 'blog.Blog.author'.
    HINT: Add or change a related_name argument to the definition for 'blog.Blog.contributors' or 'blog.Blog.author'.

 Oh non ! Django n’a pas l’air d’aimer nos changements ! Vous voyez ceci en raison d’un conflit dans notre accesseur inversé.

Avant d’ajouter ce champ, vous auriez pu utiliser   user.blog_set  pour accéder à un QuerySet des instances deBlog  de cet auteur précis.

Mais maintenant, si nous voulons faire la même chose pour le champ  contributors  — en récupérant toutes les instances de   Blog  pour unUserdans lesquelles l’utilisateur est présent dans les   contributors  de l’instance deBlog  — à nouveau, la recherche inversée serait   user.blog_set  .

Pour contourner ceci, donnez au champ un accesseur inversé personnalisé en spécifiant l’argument   related_name  . Si vous le définissez comme   contributions  , vous pouvez alors accéder à toutes les instances de   Blog  ayant le   User  comme contributeur, en utilisant   user.contributions  à la place.

Mettez à jour le modèle en tenant compte de tout cela.

# blog/models.py
class Blog(models.Model):
    ...
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, null=True)
    contributors = models.ManyToManyField(
settings.AUTH_USER_MODEL, through='BlogContributor', related_name='contributions')
...

Et réessayez de générer les migrations.

(ENV) ~/fotoblog (master)
→ python manage.py makemigrations
Migrations for 'blog':
    blog/migrations/0003_auto_20210427_2325.py
        - Create model BlogContributor
        - Add field contributors to blog

Cela a fonctionné ! Vous pouvez les appliquer :

(ENV) ~/fotoblog (master)
→ python manage.py migrate
Operations to perform:
    Apply all migrations: admin, auth, authentication, blog, contenttypes, sessions
Running migrations:
    Applying blog.0003_auto_20210427_2325... OK

Étape 3 : Mettez à jour la vue pour gérer une relation plusieurs-à-plusieurs

Ensuite, mettez à jour la vue  blog_and_photo_upload  pour refléter ces changements.

Voyons comment la vue gère actuellement une requête  POST  :

def blog_and_photo_upload(request):
    ...
    if request.method == 'POST':
        blog_form = forms.BlogForm(request.POST)
        photo_form = forms.PhotoForm(request.POST, request.FILES)
        if all([blog_form.is_valid(), photo_form.is_valid()]):
            photo = photo_form.save(commit=False)
            photo.uploader = request.user
            photo.save()
            blog = blog_form.save(commit=False)
            blog.photo = photo
            blog.author = request.user
            blog.save()
...

Vous pouvez uniquement stocker les relations dans le  ManyToManyField  une fois que le modèle a été sauvegardé dans la base de données.

Dans notre cas, nous voulons ajouter l’utilisateur connecté en tant que contributeur, une fois que l’instance de  blog  est sauvegardée.

Pour faire cela, utilisez la méthode  add  pour créer la relation plusieurs-à-plusieurs. Vous pouvez aussi préciser le contenu des champs supplémentaires avec l’argument nommé  through_defaults.

Mettez à jour la vue pour utiliser le nouveau  ManyToManyField  :

def blog_and_photo_upload(request):
    ...
    if request.method == 'POST':
        blog_form = forms.BlogForm(request.POST)
        photo_form = forms.PhotoForm(request.POST, request.FILES)
        if all([blog_form.is_valid(), photo_form.is_valid()]):
            photo = photo_form.save(commit=False)
            photo.uploader = request.user
            photo.save()
            blog = blog_form.save(commit=False)
            blog.photo = photo
            blog.save()
            blog.contributors.add(request.user, through_defaults={'contribution': 'Auteur principal'})
            return redirect('home')

    ...

La relation de contributeur a maintenant été sauvegardée comme une relation plusieurs-à-plusieurs.

Nous avons ajouté la capacité d’ajouter des contributeurs multiples pour un billet de blog, mais nous devons encore migrer les anciennes données stockées dans le champ  author   vers le nouveau modèle de données. C’est ce que vous allez faire maintenant !

C'est à vous ! Migrez d’une ForeignKey à un ManyToManyField en utilisant une migration personnalisée

Maintenant que notre nouvelle relation est configurée, vous pouvez migrer les données de l’ancienne relation un-à-plusieurs vers une relation plusieurs-à-plusieurs.

Voici ce que vous devez faire :

  1. Créez une migration vide dans l’applicationblog.

  2. Dans la migration, itérez dans chaque instance du modèleBlog  . Ajoutez la valeur   authorauxcontributors, avec une contribution par défaut de   'Auteur principal'  pour chacun d’entre eux.

  3. Exécutez la migration. Si le résultat vous convient, supprimez le champauthorde   Blog.

  4. Créez et exécutez les nouvelles migrations. Vous devriez avoir migré avec succès toutes les données existantes vers le nouveau modèle de données !

Une fois que vous avez écrit votre migration personnalisée, comparez-la à la solution dans le dépôt GitHub.

En résumé 

  • Utilisez le  ManyToManyField  pour stocker des relations plusieurs-à-plusieurs.

  • Les relations plusieurs-à-plusieurs sont stockées dans des tables intermédiaires.

  • Pour stocker des données sur une relation, créez une table intermédiaire enrichie de champs additionnels et spécifiez-la avec le mot-cléthrough  .

Vous connaissez maintenant les relations plusieurs-à-plusieurs! Vérifions vos compétences sur les modèles et les formulaires, avant de passer à l’ORM Django et aux gabarits.

Exemple de certificat de réussite
Exemple de certificat de réussite