
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.

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é.

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
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 :
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... OKEnsuite, voyons comment utiliser des formulaires pour créer des relations plusieurs-à-plusieurs au sein d’une vue.
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']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.
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 %}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')
]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 :

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.
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 :
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 .
ManyToManyField pour utiliser la table intermédiaireAjoutez 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 blogCela 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... OKEnsuite, 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 !
ForeignKey à un ManyToManyField en utilisant une migration personnaliséeMaintenant 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 :
Créez une migration vide dans l’applicationblog.
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.
Exécutez la migration. Si le résultat vous convient, supprimez le champauthorde Blog.
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.
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.