• 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 19/01/2024

Créez des objets de modèle avec un ModelForm

Ajoutez un modèle d'URL, une vue et un gabarit pour la création d'un groupe

Si nous devons avoir un nouveau formulaire, nous avons besoin d'un endroit pour le mettre. Réfléchissons à ce que pourrait être le chemin URL vers notre nouveau formulaire.

Jusqu'à présent, nos chemins liés à nos groupes sont les suivants :

  • « bands/ » pour la liste des groupes

  • « bands/1/ » pour le groupe 1, « bands/2/ » pour le groupe 2, etc.

Nous savons que dans la suite de cette partie, nous allons ajouter des vues pour créer, mettre à jour et supprimer des groupes. Ce serait bien si nous pouvions d'une manière ou d'une autre les placer tous sous le chemin « bands/ ».

Donc pour créer un Band, que diriez-vous d'utiliser le chemin bands/add/? Ajoutons-le en dessous de nos autres modèles d'URL liés aux groupes :

# merchex/urls.py

urlpatterns = [
  ...
  path('bands/', views.band_list, name='band-list'),
  path('bands/<int:id>/', views.band_detail, name='band-detail'),
  path('bands/add/', views.band_create, name='band-create'),
...

Nous savons maintenant que donner un name à nos modèles d'URL est utile pour générer des liens dans les gabarits, donc nous incluons name='band-create'.

Pourquoi « bands/add/ » ? Pourquoi pas « bandes/creation/ » ?

En réalité, vous pouvez utiliser n'importe quel mot, à condition qu'il ait un sens pour vos utilisateurs ! J'utilise ici un verbe différent, notamment pour montrer que vous êtes libre de construire vos URL de la manière la plus appropriée à votre application.

Je vous laisse créer la vue band_create et le modèle band_create.html par vous-même. Le modèle peut contenir un simple <h1> pour le moment. Nous ajouterons le formulaire dans la section suivante.

Laissez votre modèle définir la forme

Maintenant que nous savons comment créer des formulaires dans Django, la prochaine étape est de créer un formulaire qui permettra aux utilisateurs de créer un nouvel objet Band.

On pourrait penser qu’on commencerait par définir une autre classe de formulaire et, pour chaque champ de notre modèle, définir un champ de formulaire correspondant, comme ceci :

class BandForm(forms.Form):
   name = forms.CharField(max_length=100)
   biography = forms.CharField(max_length=1000)
   year_formed = forms.IntegerField(min_value=1900, max_value=2021)
   official_homepage = forms.URLField(required=False)

... et vous pourriez le faire ! Mais comparez-la au modèle lui-même (réduit pour la comparaison) :

class Band(models.Model):
…
   name = models.fields.CharField(max_length=100)
...
   biography = models.fields.CharField(max_length=1000)
   year_formed = models.fields.IntegerField(
    validators=[MinValueValidator(1900),
           MaxValueValidator(2021)]
)
…
official_homepage = models.fields.URLField(null=True, blank=True)

Même si nous construisons deux choses distinctes, un modèle et un formulaire, il semble y avoir beaucoup d'informations dupliquées entre les deux. Nous avons déjà défini chaque champ dans le modèle, en spécifiant les types de données (string, integer) et les règles (par exemple max_length ). Pourquoi devrions-nous les définir également dans le formulaire ?

Ne serait-il pas formidable de pouvoir laisser le modèle définir le formulaire ? C'est exactement ce à quoi servent les ModelForm.

Générez automatiquement un formulaire à partir d'un modèle avec un ModelForm

Dans listings/forms.py, nous allons définir une nouvelle classe qui hérite de django.forms.ModelForm :

from django import forms

from listings.models import Band
...

class BandForm(forms.ModelForm):
   class Meta:
     model = Band
     fields = '__all__'

La nouvelle classe contient une classe imbriquée, Meta, qui spécifie le modèle pour lequel ce formulaire sera utilisé, et les champs de ce modèle à inclure dans ce formulaire (dans ce cas, tous).

Maintenant, utilisons notre BandForm dans la vue band_create, en le passant au modèle :

# listings/views.py

from listings.forms import BandForm, ContactUsForm
...

def band_create(request):
   form = BandForm()
   return render(request,
            'listings/band_create.html',
            {'form': form})

Et ensuite nous générons le formulaire dans le modèle :

# listings/templates/listings/band_create.html

{% extends 'listings/base.html' %}

{% block content %}

 <h1>Créer un nouveau groupe</h1>

<form action="" method="post" novalidate>
  {% csrf_token %}
  {{ form.as_p }}
  <input type="submit" value="Envoyer">
</form>

{% endblock %}

Jetez un coup d'œil à notre nouvelle page dans le navigateur à l'adresse http://127.0.0.1:8000/bands/add/ :

La page s'intitule « Créer un nouveau groupe ».
Notre page « Créer un nouveau groupe ».

Nous venons de générer un formulaire pour notre modèle, sans avoir à définir aucun champ de formulaire, nous avons laissé le modèle le faire pour nous ! Et voici la meilleure partie : si nous modifions les champs du modèle, le formulaire est automatiquement mis à jour.

Mais nous n'avons pas encore fini : nous devons mettre à jour notre vue pour qu'elle crée un objet Band à partir des valeurs que nous soumettons dans le formulaire.

Mettez à jour la logique de la vue pour créer un nouvel objet

Nous allons suivre le même schéma que pour le formulaire de « Contact ». Récapitulons le cycle de vie de ce modèle de vue, en style « pseudocode » :

  • La demande est transmise à la fonction de vue.

    • S'il s'agit d'une demande GET :

      • Il doit s'agir de la première requête sur cette page et l'utilisateur s'attend à voir un formulaire vierge, prêt à être rempli. Nous créons donc une nouvelle instance vide du formulaire et nous la stockons dans une variable « form ».

    • S'il s'agit d'une demande POST :

      • Il ne s'agit pas de la première demande, mais d'une demande ultérieure à cette page, accompagnée de certaines valeurs de formulaire que l'utilisateur aura soumises. Nous créons donc une instance du formulaire et nous la remplissons avec les valeurs envoyées dans la requête POST. C’est stocké dans une variable « form ».

      • Si la validation du formulaire se déroule avec succès :

        • nous effectuons l'action, qu'il s'agisse d'envoyer un e-mail, de créer un objet ou de toute autre action.

        • Nous redirigeons l'utilisateur vers une autre vue, peut-être une page de confirmation ou une autre page qui indique que l'action a réussi. Nous arrêtons d'exécuter cette fonction de vue à ce stade. Cependant...

      • Si la validation du formulaire n'a PAS réussi :

        • Le processus de validation a maintenant ajouté des messages d'erreur dans les champs concernés.

        • Nous autorisons l'exécution de cette vue pour continuer à l'étape suivante ci-dessous. 

  • Nous avons maintenant un « formulaire » qui est soit a) nouveau et vide, soit b) rempli avec des valeurs précédemment soumises et des messages d'erreur pertinents. Dans les deux cas, nous passons « form » au modèle pour qu'il puisse être affiché dans le navigateur.

Voici à quoi cela ressemble en Python, dans notre vue band_create. Notez que nous traitons d'abord le cas POST dans l'instruction if et ensuite le cas GET dans l'instruction else :

# listings/views.py

def band_create(request):
    if request.method == 'POST':
        form = BandForm(request.POST)
        if form.is_valid():
            # créer une nouvelle « Band » et la sauvegarder dans la db
            band = form.save()
            # redirige vers la page de détail du groupe que nous venons de créer
            # nous pouvons fournir les arguments du motif url comme arguments à la fonction de redirection
            return redirect('band-detail', band.id))

    else:
        form = BandForm()

    return render(request,
            'listings/band_create.html',
            {'form': form})

Notez que la méthode form.save() ne fait pas qu'enregistrer le nouvel objet dans la base de données : elle renvoie également cet objet, ce qui signifie que nous pouvons l'utiliser à l'étape suivante, où nous redirigeons immédiatement vers le groupe nouvellement créé.

Maintenant nous pouvons remplir le formulaire :

Les champs du formulaire sont remplis.
Notre formulaire « Créer un nouveau groupe » est prêt.

... et nous sommes alors redirigés vers la page détaillée du groupe que nous venons de créer :

La page s'intitule « A Tribe Called Quest » et présente le genre du groupe, son année de création, son statut actif et sa page d'accueil.
La page de détail.

Notre nouvelle page fonctionne, mais elle ne sert à rien si nos utilisateurs ne peuvent pas la trouver. Nous devons créer un lien vers elle quelque part.

Et si on établissait un lien à partir de la vue en liste de Band?

# listings/templates/listings/band_list.html

...
  <h1>Groupes</h1>

  <a href="{% url 'band-create' %}">Créer un nouveau groupe</a>
...

Les ModelForms sont un outil puissant pour créer des modèles rapidement et facilement. Si vous avez besoin de vérifier l'une des étapes du chapitre précédent, regardez le screencast.

Excluez des champs d'un ModelForm

C'est formidable que Django puisse générer un formulaire pour nous, mais que faire si le formulaire par défaut ne répond pas tout à fait à nos besoins ?

Vous pouvez créer un formulaire à partir de zéro, comme nous l'avons fait avec le ContactUsForm et l'utiliser. Mais il est également possible de personnaliser un ModelForm.

Disons que nous voulons que les utilisateurs puissent créer un nouveau groupe, mais nous ne voulons pas qu'ils puissent modifier les champs active et official_homepage, nous voulons que seuls les administrateurs puissent modifier ces champs (ce qu'ils peuvent faire dans le site d'administration). Nous pouvons les exclure du formulaire, comme ceci :

class BandForm(forms.ModelForm):
   class Meta:
      model = Band
      # fields = '__all__' # supprimez cette ligne
      exclude = ('active', 'official_homepage')  # ajoutez cette ligne

Et alors ces champs n'apparaîtront pas dans le formulaire :

La page contient 4 champs : Nom, Genre, Biographie, et Année de création.
Les champs actif et official_homepage sont exclus.

Le formulaire fonctionnera toujours et nous pourrons créer un nouvel objet Band sans aucune erreur, même si nous avons laissé ces champs de côté, parce que active a une valeur par défaut de True et official_homepage autorise les valeurs NULL avec null=True.

Activez la validation côté client

Pour les deux formulaires que nous avons créés jusqu'à présent, nous avons ajouté novalidate à leur HTML :

<form action="" method="post" novalidate>

Cela a désactivé la validation côté client dans votre navigateur. Réactivons-la en enlevant novalidate du gabarit band_create :

# listings/templates/listings/band_create.html

<form action="" method="post">

Essayez maintenant de soumettre ce formulaire vide :

Sous le champ Genre apparaît le message « Sélectionnez un élément dans la liste ».
La validation côté client.

La validation côté client offre une expérience utilisateur améliorée. Il valide les données du formulaire avant qu’elles ne soient envoyées au serveur. S'il y a des données non valides, le formulaire affiche un message d'erreur et ne soumet pas les données tant qu'elles ne sont pas valides. Cela évite à l'utilisateur de faire un « aller-retour » vers le serveur pour s'apercevoir qu'il a soumis des données non valides.

Alors pourquoi avons-nous désactivé la validation côté client au début ?...

Si le formulaire ne se soumet pas tant que toutes les données ne sont pas valides, nous ne pouvons pas voir ce qui se passe lorsque nous envoyons des données invalides au serveur, et il n'est donc pas possible de démontrer que la validation côté serveur fonctionne.

Mais avons-nous vraiment besoin de la validation côté serveur si nous avons la validation côté client ?

Oui, absolument. Et c'est une règle importante pour apprendre le développement web :

« Ne jamais faire confiance au client. »

Pourquoi pas ?... Nous avons écrit le HTML pour le côté client, ne pouvons-nous pas lui faire confiance ?

Le problème est que, bien que nous ayons effectivement construit le client, n'importe quel utilisateur peut modifier le HTML en frontal, supprimer la validation et envoyer ce qu'il veut au serveur ! En fait, vous pouvez le faire dès maintenant. Ouvrez l'inspecteur de votre navigateur (cliquez avec le bouton droit de la souris et « Inspecter » si vous êtes sous Chrome). Vous pouvez facilement augmenter l'attribut maxlength de cette entrée à 10 000 ou même 100 000 !

Dans le code HTML de la page, l'élément indiquant maxlength=100000 est entouré.
Maxlength="100000"

Si nous n'avions pas de validation côté serveur, une biographie de 100 000 caractères pourrait être enregistrée directement dans la base de données. Cela ne ferait probablement pas planter le site et serait plus ennuyeux qu'autre chose. Mais vous pouvez imaginer qu'un pirate informatique pourrait essayer de trouver d'autres moyens d'exploiter cette absence de validation côté serveur.

La sécurité des applications web est un sujet important que tout développeur devrait connaître, mais nous ne pouvons pas tout couvrir dans ce cours. Cela dit, Django intègre de nombreuses bonnes pratiques en matière de sécurité, ce qui vous permet de prendre un bon départ rien qu'en utilisant le framework.

Mais pour l'instant, souvenez-vous :

  • La validation côté serveur est ce sur quoi vous vous appuyez pour vous assurer que vous n'acceptez que des données valides dans votre base de données.

  • La validation côté client est fournie afin d'améliorer l'expérience de l'utilisateur et, bien qu'utile, elle ne doit pas être utilisée de manière exclusive !

Puisque nous parlons de sécurité, c'est le moment d'expliquer ce qu'est le {% csrf_token %} dans tous nos formulaires. CSRF est l'abréviation de Cross Site Request Forgery. Il s'agit d'une méthode visant à inciter les utilisateurs à effectuer des opérations en créant (ou en « forgeant ») des requêtes. Le csrf_token empêche cela, en générant un jeton (token) aléatoire pour chaque requête. Si une requête POST ultérieure n'inclut pas ce jeton exact, Django sait que la requête est sans doute une contrefaçon et lève une erreur pour arrêter l'exécution.

Est-ce qu'il nous arrive de créer manuellement un formulaire pour un modèle, ou est-ce que nous utilisons toujours un ModelForm?

Vous devriez très rarement avoir à créer manuellement un formulaire pour un modèle à partir de zéro, bien que rien ne vous empêche de le faire. Il faudrait alors que le modèle et le formulaire soient synchronisés l'un avec l'autre. L'utilisation d'un ModelForm dans le premier cas est logique, car vous gardez les choses DRY et il y a moins de code à maintenir.

C'est à vous ! Ajoutez une page « Créer » pour le modèle listing

Voici votre liste de tâches pour ce chapitre :

  • ajouter une page « Create new Listing » (modèle d'URL, vue et gabarit) ;

  • ajouter un ListingForm et l’utiliser dans la nouvelle page pour créer des objets Listing ;

  • lier la liste des annonces à la page de création d'une annonce ;

  • réactiver la validation côté client pour tous vos formulaires.

En résumé

  • Lorsque nous construisons un formulaire pour créer de nouveaux objets à partir d'un modèle, nous utilisons un ModelForm, cela permet au modèle de définir automatiquement les champs du formulaire.

  • Nous utilisons un ModelForm de la même manière qu'un Form : nous créons une instance du formulaire dans notre vue et nous la passons au gabarit. Nous traitons ensuite les demandes GET et POST dans la vue.

  • Nous pouvons personnaliser un ModelForm en excluant certains des champs du modèle, en n'affichant qu'un sous-ensemble de champs dans le formulaire.

Maintenant que nous avons créé un formulaire qui peut créer des objets de modèle, voyons comment nous pouvons réutiliser ce formulaire pour l'opération d'écriture suivante, le « U » de « CRUD » pour Update : la mise à jour. 

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