Créez plusieurs modèles avec un seul formulaire
Maintenant que nous avons donné un moyen aux utilisateurs de téléverser des photos, donnons-leur la possibilité de créer aussi des billets de blog.
Parfois, en créant un formulaire, vous voudrez instancier plus d’un modèle à la fois. Dans notre cas, il faut que les utilisateurs puissent créer et une Photo
, et un post de Blog
en même temps. Découvrez comment en vidéo ci-dessous.
Étape 1 : Créez les formulaires
Pour permettre aux utilisateurs d’instancier plusieurs modèles simultanément, il vous faut un formulaire pour chacun des modèles Photo
et Blog
.
Vous avez créé un ModelForm
pour la Photo
au chapitre précédent. Réutilisons-le ! Et maintenant, créons-en un pour le modèle Blog
.
# blog/forms.py
from django import forms
from . import models
class PhotoForm(forms.ModelForm):
...
class BlogForm(forms.ModelForm):
class Meta:
model = models.Blog
fields = ['title', 'content']
Étape 2 : Incluez les formulaires dans le gabarit
Voyons à présent comment inclure ces deux formulaires dans le gabarit. La vue n’a pas encore été créée, mais nommons déjà les formulaires photo_form
et blog_form
.
Nous voulons que les deux formulaires puissent être envoyés avec un clic unique sur un bouton, pour instancier les deux modèles simultanément. Pour ce faire, incluez ces deux formulaires individuellement sous la même balise <form>
:
# blog/templates/blog/create_blog_post.html
{% extends 'base.html' %}
{% block content %}
<h2>Écrire un billet</h2>
<form method="post" enctype="multipart/form-data">
{{ blog_form.as_p }}
{{ photo_form.as_p }}
{% csrf_token %}
<button type="submit" >Publier</button>
</form>
{% endblock content %}
Maintenant que les deux formulaires sont sous la même balise <form>
, un clic sur le bouton « envoyer » enverra les données POST
des deux formulaires en même temps. Voyons comment gérer cela dans la vue.
Étape 3 : Écrivez la vue
Avant d’apprendre à gérer l’aspect POST
de la requête, définissons la vue pour inclure à la fois le photo_form
et le blog_form
dans le context
et retourner le rendu du gabarit dans la réponse HTTP.
# blog/views.py
@login_required
def blog_and_photo_upload(request):
blog_form = forms.BlogForm()
photo_form = forms.PhotoForm()
if request.method == 'POST':
# handle the POST request here
context = {
'blog_form': blog_form,
'photo_form': photo_form,
}
return render(request, 'blog/create_blog_post.html', context=context)
Désormais, vous pouvez fournir les données POST
aux formulaires, puis utiliser la méthode save()
pour instancier les modèles.
# blog/forms.py
@login_required
def blog_and_photo_upload(request):
blog_form = forms.BlogForm()
photo_form = forms.PhotoForm()
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()]):
blog_form.save()
photo_form.save()
context = {
'blog_form': blog_form,
'photo_form': photo_form,
}
return render(request, 'blog/create_blog_post.html', context=context)
La vue va maintenant recevoir la requête POST
et créer une instance des modèles Blog
et Photo
, si les deux formulaires sont valides.
Pourquoi ne pas simplement utiliser if blog_form.is_valid() and photo_form.is_valid()
?
L’exécution de la méthode is_valid()
sur un formulaire ne se contente pas de vérifier sa validité. Elle génère aussi des messages d’erreur pour tous les champs qui auraient des entrées non valides. Ces messages sont affichés en tant que feedback sur le front-end.
Lorsque vous exécutez une instruction if condition_a and condition_b
, Python exige que les deux conditions soient « Truthy » (évaluées à True). Si l’instruction conclut que condition_a
est « Falsy » (évaluée à False), elle cessera immédiatement d’exécuter la ligne et sautera directement au prochain bloc de code.
Cela signifie que si vous avez l’instruction if blog_form.is_valid() and photo_form.is_valid()
, et que blog_form.is_valid()
renvoie False
, alors photo_form.is_valid()
ne sera jamais exécuté, et ne générera pas de message d’erreur.
L’approche actuelle marche très bien si vous voulez uniquement instancier ces modèles. Néanmoins, nous devrons lier les deux objets à l’utilisateur qui les a créés, et remplir le champ photo
pour l’objet Blog
. Ajustons la vue pour prendre ces éléments en compte.
@login_required
def blog_and_photo_upload(request):
blog_form = forms.BlogForm()
photo_form = forms.PhotoForm()
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.author = request.user
blog.photo = photo
blog.save()
return redirect('home')
context = {
'blog_form': blog_form,
'photo_form': photo_form,
}
return render(request, 'blog/create_blog_post.html', context=context)
La vue met désormais correctement à jour tous les champs avant de les enregistrer effectivement dans la base de données. La photo
et le blog
seront liés l’un à l’autre, ainsi qu’à l’utilisateur qui les a créés.
Étape 4 : Ajoutez le modèle d’URL
Ajoutons maintenant le nouveau modèle d’URL :
# fotoblog/urls.py
urlpatterns = [
…
path('blog/create', blog.views.blog_and_photo_upload, name='blog_create'),
]
Étape 5 : Ajoutez « Écrire un billet » à la barre latérale
Pour finir, ajoutez le lien « Écrire un billet » au gabarit de base.
# templates/base.html
...
<p><a href="{% url 'home' %}">Accueil</a></p>
<p><a href="{% url 'blog_create' %}">Écrire un billet</a></p>
<p><a href="{% url 'photo_upload' %}">Télécharger une photo</a></p>
...
Puis, naviguez sur la nouvelle page et voyez de quoi elle a l’air.
Super ! Vous avez maintenant un moyen de créer des publications blog et photo simultanément. En revanche, il n’y a pas de moyen de les visualiser une fois qu’elles sont créées. Ajoutons maintenant cette possibilité.
Ajoutez une page de visualisation d’un billet de blog
Nous allons inclure des posts de blog sur la page d’accueil, mais nous ne voulons tout de même pas voir tout le texte de chaque billet du blog. Ajoutons donc une autre page, où l’utilisateur peut voir un billet complet.
Étape 1 : Ajoutez la vue pour la page de visualisation d’un billet
Maintenant, ajoutons la vue pour voir des billets individuels.
# blog/views.py
from django.shortcuts import get_object_or_404
@login_required
def view_blog(request, blog_id):
blog = get_object_or_404(models.Blog, id=blog_id)
return render(request, 'blog/view_blog.html', {'blog': blog})
Étape 2 : Ajoutez le gabarit pour la page de visualisation d’un billet
Et maintenant, ajoutez le gabarit pour la page de visualisation d’un billet.
# blog/templates/blog/view_blog.html
{% extends 'base.html' %}
{% block content %}
<h2>{{ blog.title }}</h2>
<img src="{{ blog.photo.image.url }}">
<p>{{ blog.photo.caption }}</p>
<p>{{ blog.content }}</p>
{% endblock content %}
Étape 3 : Ajoutez le modèle d’URL pour la page de visualisation d’un billet
Et maintenant, ajoutez le modèle d’URL pour la page de visualisation d’un billet.
# fotoblog/urls.py
urlpatterns = [
…
path('blog/<int:blog_id>', blog.views.view_blog, name='view_blog'),
]
Étape 4 : Récupérez les instances de Blog
dans la vue home
Maintenant que la page de visualisation d’un billet est construite, récupérons les instances de Blog
pour la page d’accueil.
# blog/views.py
@login_required
def home(request):
photos = models.Photo.objects.all()
blogs = models.Blog.objects.all()
return render(request, 'blog/home.html', context={'photos': photos, 'blogs': blogs})
Étape 5 : Mettez à jour le gabarit home.html
Et ensuite, affichez-les dans le gabarit, avec un lien vers la page du billet de blog.
# blog/templates/blog/home.html
{% extends 'base.html' %}
{% block content %}
...
<h2>Blog</h2>
<div class="grid-container">
{% for blog in blogs %}
<div class="post">
<a href="{% url 'view_blog' blog.id %}">
<h4>Billet : {{ blog.title }}</h4>
<img src="{{ blog.photo.image.url }}">
</a>
</div>
{% endfor %}
</div>
{% endblock content %}
Étape 6 : Testez le service vous-même
Bravo ! Vous avez ajouté beaucoup de fonctionnalités ! Prenez le temps de créer quelques billets sur le blog, visualisez-les depuis la page d’accueil, puis cliquez dedans pour voir leur contenu.
Une fois que vos changements vous conviendront, vous serez prêt à passer à l'étape suivante.
En résumé
Vous pouvez inclure plusieurs formulaires dans une vue, pour instancier plusieurs modèles simultanément.
Si vous faites le rendu de ces formulaires au sein des mêmes balises
<form>
dans le gabarit, ils seront soumis dans la même requêtePOST
.Vous pouvez remplir ces deux formulaires avec l’objet
request.POST
dans la vue, et instancier plusieurs modèles depuis la même requête.
Maintenant que vous pouvez instancier plusieurs modèles en envoyant un seul formulaire, vous pouvez vous plonger dans le prochain chapitre. Vous allez y apprendre à gérer plusieurs formulaires sur une seule page.