From 75855172b4187f20db9f0550dd54bf9fbc6a68e1 Mon Sep 17 00:00:00 2001 From: Carolyn Wu <87150472+cw118@users.noreply.github.com> Date: Thu, 23 Dec 2021 02:44:46 -0500 Subject: Revise fr "Django Tutorial Part 9" (#3405) * Revise fr "Django Tutorial Part 9" * Fix injected errors with macros * Minor typofixes Co-authored-by: julieng --- files/fr/learn/server-side/django/forms/index.md | 667 ++++++++++++----------- 1 file changed, 348 insertions(+), 319 deletions(-) (limited to 'files/fr') diff --git a/files/fr/learn/server-side/django/forms/index.md b/files/fr/learn/server-side/django/forms/index.md index c2a244d739..eb023de660 100644 --- a/files/fr/learn/server-side/django/forms/index.md +++ b/files/fr/learn/server-side/django/forms/index.md @@ -1,42 +1,24 @@ --- -title: 'Django Tutorial Part 9: Working with forms' +title: 'Django didactique - Section 9 : Travailler avec des formulaires' slug: Learn/Server-side/Django/Forms -tags: - - Beginner - - CodingScripting - - DjangoForms - - Forms - - HTML forms - - Learn - - Tutorial - - django - - server side translation_of: Learn/Server-side/Django/Forms --- {{LearnSidebar}}{{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}} -Dans cette formation nous allons vous montrer comment travailler avec les formulaires HTML sous Django afin de créer, modifier et supprimer des instances de modèle. Pour illustrer le raisonnement, nous allons étendre le site web [LocalLibrary](/fr/docs/Learn/Server-side/Django/Tutorial_local_library_website) pour permettre aux bibliothécaires d'utiliser nos formulaires (plutôt que l'application d'administration par défaut) pour prolonger la durée de prêt des livres, et également pour ajouter, mettre à jour et supprimer des auteurs. +Dans cette formation, nous allons vous montrer comment travailler avec les formulaires HTML sous Django afin de créer, modifier et supprimer des instances de modèle. Pour illustrer le raisonnement, nous allons étendre le site web [LocalLibrary](/fr/docs/Learn/Server-side/Django/Tutorial_local_library_website) pour permettre aux bibliothécaires d'utiliser nos formulaires (plutôt que l'application d'administration par défaut) pour prolonger la durée de prêt des livres, et également pour ajouter, mettre à jour et supprimer des auteurs. - + - + @@ -44,368 +26,393 @@ Dans cette formation nous allons vous montrer comment travailler avec les formu ## Vue d'ensemble -Un [formulaire HTML](/fr/docs/Web/Guide/HTML/Forms) regroupe au moins un champ remplissable et des composants élémentaires d'interface web.  Il peut être utilisé pour réunir des saisies de la part des utilisateurs avant envoi vers un serveur. Les formulaires sont souples: ils s'adaptent à plusieurs modes de saisie. En effet, Il existe des composants élementaires d'interfaces graphique pour des  modes de saisie non contrainte avec une zone de saisie de texte, ou resteinte au type date avec un date picker,  la saisie d'un variable optionnelle via une boîte à cocher,  d'un choix à faire parmi plusieurs valeurs possibles avec les boutons radio etc... . Les formulaires permettent de partager des informations avec le serveur de manière relativement sécurisée  car ils permettent d'envoyer des requêtes de  type `POST` protégeant de la falsification des requêtes inter-site. +Un [formulaire HTML](/fr/docs/Learn/Forms) regroupe au moins un champ remplissable et des composants élémentaires d'interface web. Il peut être utilisé pour réunir des saisies de la part des utilisateurs avant envoi vers un serveur. Les formulaires sont souples : ils s'adaptent à plusieurs modes de saisie. En effet, il existe des composants élémentaires d'interface graphique pour des modes de saisie non contrainte avec une zone de saisie de texte, ou restreinte au type `date` avec un sélecteur de date (date picker), la saisie d'un variable optionnelle via une boîte à cocher, d'un choix à faire parmi plusieurs valeurs possibles avec les boutons radio, etc. Les formulaires permettent de partager des informations avec le serveur de manière relativement sécurisée, car ils permettent d'envoyer des requêtes de type `POST` avec une protection contre la falsification des requêtes inter-site. -Bien que nous n'ayons pas encore créé de formulaire au cours de cette formation, nous en avons déjà rencontré sur l'interface d'administration Django Admin — par exemple la capture d'écran ci-dessous montre un formulaire d'édition  de l'un de nos modèle de [Book](/fr/docs/Learn/Server-side/Django/Models)  (livre), comprenant des composants élémentaires d'interface graphique de chois de valeur parmi une liste proposée,  et des zones des saisie de texte. +Bien que nous n'ayons pas encore créé de formulaire au cours de cette formation, nous en avons déjà rencontré sur l'interface d'administration Django Admin — par exemple, la capture d'écran ci-dessous montre un formulaire d'édition de l'un de nos modèles de [Book](/fr/docs/Learn/Server-side/Django/Models) (livre), comprenant des composants élémentaires d'interface graphique de choix de valeur parmi une liste proposée, et des zones de saisie de texte. -![Admin Site - Book Add](admin_book_add.png) +![Écran d'administration du site - Ajout d'un livre](admin_book_add.png) -Travailler avec des formulaires peut s'avérer compliqué ! Les développeurs doivent non seulement écrire le code  HTML pour le formulaire, mais aussi vérifier et corriger sur le serveur les données saisies (et éventuellement aussi dans le navigateur), renvoyer le formulaire avec des messages d'erreur pour informer les usagers de tout champ invalide, prendre en charge les données quand elles passent l'étape de vérification, et finalement renvoyer une information à l'utilisateur d'une manière ou d'une autre pour indiquer ce succès. Les formulaires sous Django enlèvent beaucoup de travail à chacune de ces étapes, grâce à un cadriciel qui permet de déclarer des formulaires et leurs champs à travers un langage de programmation, puis d'utiliser ces objets non seulement pour générer le code HTML, mais aussi une grosse partie de la vérification des données et du retour d'information à l'utilisateur. +Travailler avec des formulaires peut s'avérer compliqué ! Les développeuses et développeurs doivent non seulement écrire le code HTML pour le formulaire, mais aussi vérifier et corriger sur le serveur les données saisies (et éventuellement aussi dans le navigateur), renvoyer le formulaire avec des messages d'erreur pour informer les usagers de tout champ invalide, prendre en charge les données quand elles passent l'étape de vérification, et finalement renvoyer une information à l'utilisateur d'une manière ou d'une autre pour indiquer ce succès. Les formulaires sous Django enlèvent beaucoup de travail à chacune de ces étapes, grâce à un cadriciel qui permet de déclarer des formulaires et leurs champs à travers un langage de programmation, puis d'utiliser ces objets non seulement pour générer le code HTML, mais aussi une grosse partie de la vérification des données et du retour d'information à l'utilisateur. -Dans cette formation, nous allons vous montrer quelque-unes des manièrs de créer et de travailler avec les formulaires, et en particulier, comment les vues sur les formulaires génériques d'édition peuvent réduire significativement la quantité de travail à fournir pour créer les formulaires de manipulation de vos modèles. En chemin nous allons étendre notre application *LocalLibrary* en ajoutant un formulaire permettant aux bibliothécaires de prolonger le prêt de libres, et nous allons créer des pages pour créer, modifier et supprimer des livres et des auteurs (reproduisant une version basique du formulaire ci-dessus pour éditer des livres. ) +Dans cette formation, nous allons vous montrer quelques-unes des manières de créer et de travailler avec les formulaires, et en particulier, comment les vues sur les formulaires génériques d'édition peuvent réduire significativement la quantité de travail à fournir pour créer les formulaires de manipulation de vos modèles. En chemin, nous allons étendre notre application *LocalLibrary* en ajoutant un formulaire permettant aux bibliothécaires de prolonger le prêt de livres, et nous allons créer des pages pour créer, modifier et supprimer des livres et des auteurs (reproduisant une version basique du formulaire ci-dessus pour éditer des livres). ## Formulaires HTML -D'abord, un premier aperçu des formulaires HTML ([HTML Forms](/fr/docs/Learn/HTML/Forms)). Soit un formulaire HTML simple, composé d'un unique champ de saisie texte , présent pour y entrer le nom d'une "équipe" quelconque, et son sa description dans l'étiquette associée : +D'abord, un premier aperçu des [formulaires HTML](/fr/docs/Learn/Forms). Soit un formulaire HTML simple, composé d'un unique champ de saisie texte, présent pour y entrer le nom d'une « équipe » quelconque et sa description dans l'étiquette associée : -![Simple name field example in HTML form](form_example_name_field.png) +![Champ textuel simple d'un formulaire HTML pour saisir un nom](form_example_name_field.png) -Le formulaire est défini en HTML comme une collection d'éléments enfermés entre deux balises \ ... \ contenant au moins une balise \ dont la valeur d'attribut 'type' doit valoir "submit": +Le formulaire est défini en HTML comme une collection d'éléments enfermés entre deux balises `...` contenant au moins une balise `` dont la valeur d'attribut `type` doit valoir `submit` : ```html -    -    -    + + + ``` -Bien qu'ici nous n'ayons qu'un champ de saisie texte destiné à recevoir le nom d'équipe, une formulaire _pourrait_ avoir un nombre quelconque d'autres champs de saisie et leurs étiquettes de description associées. La valeur de l'attribut 'type' définit la sorte de composant élementaire d'interface graphique  affichée. Les attributs 'id' et 'name' permettent d'identifier le champ en JavaScript/CSS/HTML alors que l'attribut 'value' définit la valeur initiale du champ lorsqu'il est affiché pour la première fois. La description associée  est déclarée par la balise \
Prérequis:Prérequis : - Avoir terminé les formations précédentes, y compris - Django Tutorial Part 8: User authentication and permissions. + Avoir terminé les formations précédentes, y compris Django didactique - section 8 : Authentification de l'utilisateur et permissions.
Objectifs:Objectifs : - Comprendre comment écrire des formulaires pour récupérer des - informations de la part des utilisateurs et mettre à jour la base de - données. Comprendre commment il est possible de simplifier grandement la - création de formulaires si l 'on travaille avec un seul modèle en - utilisant les vues génériques de formulaires d'éditions s'appuyant sur - des classes.    + Comprendre comment écrire des formulaires pour récupérer des informations de la part des utilisateurs et mettre à jour la base de données. Comprendre comment il est possible de simplifier grandement la création de formulaires si l'on travaille avec un seul modèle en utilisant les vues génériques de formulaires d'éditions s'appuyant sur des classes.
- \{{ form }} - 
- - +{% block content %} +

Renew: \{{ book_instance.book.title }}

+

Borrower: \{{ book_instance.borrower }}

+

Due date: \{{ book_instance.due_back }}

+ +
+ {% csrf_token %} + + \{{ form.as_table }} +
+ +
{% endblock %} ``` -La majeure partie de ce code devrait vous être familière si vous avez suivi les tutoriels précédents. Nous étendons le template de base et ensuite redéfinissons le block "content". Nous sommes en mesure de référencer `\{{ book_instance }}` (et ses variables), puisqu'il a été passé dans l'objet context par la fonction `render()`, et nous utilisons tout cela pour lister le titre du livre, son emprunteur et la date originale de retour. +La majeure partie de ce code devrait vous être familière si vous avez suivi les tutoriels précédents. + +Nous étendons le template de base et ensuite redéfinissons le block "content". Nous sommes en mesure de référencer `\{{ book_instance }}` (et ses variables), puisqu'il a été passé dans l'objet contexte par la fonction `render()`, et nous utilisons tout cela pour lister le titre du livre, son emprunteur et la date originale de retour. -Le code du formulaire est relativement simple. Nous déclarons d'abord les tags `form`, en précisant où le formulaire doit être adressé (`action`) et la `method` utilisée pour soumettre les donées (ici un "HTTP POST"). Si vous vous rappelez ce qui a été dit en haut de cette page (aperçu sur les [HTML Forms](/fr/docs/Learn/Server-side/Django/Forms#HTML_forms)), une `action` vide comme ici signifie que les données de formulaire seront postées à nouveau à l'URL actuelle (ce qui est le comportement que nous voulons !). À l'intérieur des tags, nous définissons le bouton , sur lequel l'utilisateur peut appuyer pour envoyer les données. Le `{% csrf_token %}` ajouté juste à l'intérieur des tags "form" est un des éléments de protection utilisés par Django contre les "cross-site forgery". +Le code du formulaire est relativement simple. Nous déclarons d'abord les balises `form`, en précisant où le formulaire doit être adressé (`action`) et la `method` utilisée pour soumettre les données (ici un "HTTP `POST`"). Si vous vous rappelez ce qui a été dit en haut de cette page (aperçu sur les [Formulaires HTML](#formulaires_html)), une `action` vide comme ici signifie que les données de formulaire seront postées à nouveau à l'URL actuelle (ce qui est le comportement que nous voulons !). À l'intérieur des balises, nous définissons le bouton `submit` sur lequel l'utilisateur peut appuyer pour envoyer les données. Le `{% csrf_token %}` ajouté juste à l'intérieur des balises `form` est un des éléments de protection utilisés par Django contre les "_cross-site forgery_" (falsification de requête inter-site). -> **Note :** Ajoutez le `{% csrf_token %}` à tout template Django que vous créeez et qui utilise `POST` pour soumettre les données. Cela réduira les risques qu'un utilisateur mal intentionné pirate vos formulaires. +> **Note :** Ajoutez le `{% csrf_token %}` à tout template Django que vous créez et qui utilise `POST` pour soumettre les données. Cela réduira les risques qu'un utilisateur mal intentionné pirate vos formulaires. -Tout ce qui reste est la variable de template `\{{ form }}`, que nous avons passée au template dans le dictionnaire de contexte. Peut-être sans surprise, quand il est utilisé comme indiqué, il fournit le rendu par défaut de tous les champs de formulaire, y compris leurs labels, widgets et textes d'aide. Voici le rendu : +Tout ce qui reste est la variable de template `\{{ form }}`, que nous avons passée au template dans le dictionnaire de contexte. Peut-être sans surprise, quand il est utilisé comme indiqué, il fournit le rendu par défaut de tous les champs de formulaire, y compris leurs labels, widgets et textes d'aide. Voici le rendu : ```html -  -  - 
Enter date between now and 4 weeks (default 3 weeks). -  + + + +
+ Enter date between now and 4 weeks (default 3 weeks). + ``` > **Note :** Ce n'est peut-être pas évident, car nous n'avons qu'un seul champ, mais, par défaut, chaque champ est défini dans sa propre ligne de tableau. Ce même rendu est fourni si vous référencez la variable de template `\{{ form.as_table }}`. -Si vous aviez entré une date invalide, vous obtiendriez en plus sur la page une liste des erreurs (indiquées en gras ci-dessous). +Si vous aviez entré une date invalide, vous obtiendriez en plus sur la page une liste des erreurs (voir `errorlist` ci-dessous). ```html -  -  - 
Enter date between now and 4 weeks (default 3 weeks). - + + + + +
+ Enter date between now and 4 weeks (default 3 weeks). + ``` @@ -413,164 +420,168 @@ Si vous aviez entré une date invalide, vous obtiendriez en plus sur la page une Si vous utilisez `\{{ form.as_table }}` comme indiqué ci-dessus, chaque champ est rendu comme une ligne de tableau. Vous pouvez également rendre chaque champ comme un élément de liste (en utilisant `\{{ form.as_ul }}`) ou comme un paragraphe (en utilisant `\{{ form.as_p }}`). -Il est également possible d'avoir un contrôle complet sur le rendu de chaque partie du formulaire, en indexant ses propriétés grâce à la notation pointée. Ainsi, par exemple, nous pouvons accéder un certain nombre d'éléments distincts pour notre champ `renewal_date` : +Il est également possible d'avoir un contrôle complet sur le rendu de chaque partie du formulaire, en indexant ses propriétés grâce à la notation pointée. Ainsi, par exemple, nous pouvons accéder à un certain nombre d'éléments distincts pour notre champ `renewal_date` : -- `\{{form.renewal_date}}`  : Le champ complet. -- `\{{form.renewal_date.errors}}` : La liste des erreurs. -- `\{{form.renewal_date.id_for_label}}` : L'id du label. -- `\{{form.renewal_date.help_text}}` : Le texte d'aide du champ. -- etc ! +- `\{{ form.renewal_date }}` : Le champ complet. +- `\{{ form.renewal_date.errors }}` : La liste des erreurs. +- `\{{ form.renewal_date.id_for_label }}` : L'`id` du label. +- `\{{ form.renewal_date.help_text }}` : Le texte d'aide du champ. -Pour plus d'exemples sur la manière de rendre manuellement des formulaires dans des templates, et boucler de manière dynamique sur les champs du template, voyez [Working with forms > Rendering fields manually](https://docs.djangoproject.com/en/1.10/topics/forms/#rendering-fields-manually) (Django docs). +Pour plus d'exemples sur la manière de rendre manuellement des formulaires dans des templates, et boucler de manière dynamique sur les champs du template, voyez [Utiliser des formulaires > Affichage manuel des champs](https://docs.djangoproject.com/fr/3.1/topics/forms/#rendering-fields-manually) (Documentation de Django). ### Tester la page -Si vous avez accepté le "challenge" dans [Django Tutorial Part 8: User authentication and permissions](/fr/docs/Learn/Server-side/Django/authentication_and_sessions#Challenge_yourself), vous avez une liste de tous les livres empruntés dans la bibliothèque, ce qui n'est visible que pour le staff de la bibliothèque. Nous pouvons ajouter un lien vers notre page de renouvellement après chaque élément, en utilisant le code de template suivant. +Si vous avez accepté le "challenge" dans [Django didactique - section 8 : Authentification des utilisateurs et permissions](/fr/docs/Learn/Server-side/Django/Authentication#challenge_yourself), vous avez une liste de tous les livres empruntés dans la bibliothèque, ce qui n'est visible que pour le staff de la bibliothèque. Nous pouvons ajouter un lien vers notre page de renouvellement après chaque élément, en utilisant le code de template suivant. ```html -{% if perms.catalog.can_mark_returned %}- Renew {% endif %} +{% if perms.catalog.can_mark_returned %} Renew {% endif %} ``` -> **Note :** Souvenez-vous que votre login de test devra avoir la permission "`catalog.can_mark_returned`" pour pouvoir accéder la page de renouvellement de livre (utilisez peut-être votre compte superuser). +> **Note :** Souvenez-vous que votre login de test devra avoir la permission "`catalog.can_mark_returned`" pour pouvoir accéder à la page de renouvellement de livre (utilisez peut-être votre compte superuser). -Vous pouvez aussi construire manuellement une URL de test comme ceci : [http://127.0.0.1:8000/catalog/book/*\*/renew/](/renew/>) (un id de bookinstance valide peut être obtenu en navigant vers une page de détail de livre dans votre bibliothèque, et en copiant le champ `id`). +Vous pouvez aussi construire manuellement une URL de test comme ceci : `http://127.0.0.1:8000/catalog/book//renew/` (un `bookinstance_id` valide peut être obtenu en naviguant vers une page de détail de livre dans votre bibliothèque, et en copiant le champ `id`). -### À quoi cela ressemble-t-il ? +### À quoi cela ressemble-t-il ? -Si tout a bien marché, le formulaire par défaut ressemblera à ceci : +Si tout a bien marché, le formulaire par défaut ressemblera à ceci : ![](forms_example_renew_default.png) -Le formulaire avec valeur erronée ressemblera à ceci : +Le formulaire avec valeur erronée ressemblera à ceci : ![](forms_example_renew_invalid.png) -La liste de tous les livres avec les liens vers le renouvellement ressemblera à ceci : +La liste de tous les livres avec les liens vers le renouvellement ressemblera à ceci : ![](forms_example_renew_allbooks.png) ## ModelForms -Créer une classe  en utilisant l'approche décrite ci-dessus est très flexible et vous autorise à créer le type de page de formulaire que vous voulez, et à l'associer à tout type de modèle(s). +Créer une classe en utilisant l'approche décrite ci-dessus est très flexible et vous autorise à créer le type de page de formulaire que vous voulez, et à l'associer à tout type de modèle(s). -Cependant, si vous avez seulement besoin d'un formulaire qui répertorie les champs d'un modèle unique, alors votre modèle définira déjà la plupart des informations requises dans votre formulaire : champs, labels, texte d'aide etc. Plutôt que de créer à nouveau les définitions du modèle dans votre formulaire, il est plus facile d'utiliser la classe d'aide [ModelForm](https://docs.djangoproject.com/en/2.1/topics/forms/modelforms/) pour créer le formulaire d'après votre modèle. Ce `ModelForm` peut dès lors être utilisé à l'intérieur de vos vues exactement de la même manière qu'un `Form` ordinaire. +Cependant, si vous avez seulement besoin d'un formulaire qui répertorie les champs d'un modèle _unique_, alors votre modèle définira déjà la plupart des informations requises dans votre formulaire : champs, labels, texte d'aide, etc. Plutôt que de créer à nouveau les définitions du modèle dans votre formulaire, il est plus facile d'utiliser la classe d'aide [ModelForm](https://docs.djangoproject.com/fr/3.1/topics/forms/modelforms/) pour créer le formulaire d'après votre modèle. Ce `ModelForm` peut dès lors être utilisé à l'intérieur de vos vues exactement de la même manière qu'un `Form` ordinaire. -Un `ModelForm` basique, contenant le même champ que notre `RenewBookForm` d'origine, est montré ci-dessous. Tout ce que vous avez à faire pour créer le formulaire, c'est ajouter `class Meta` avec le `model` (`BookInstance`) associé, et une liste des `fields` du modèle à inclure dans le formulaire (vous pouvez inclure tous les champs en utilisant `fields = '__all__'`, ou bien utiliser `exclude` (au lieu de `fields`) pour préciser les champs à ne _pas_ importer du modèle). +Un `ModelForm` basique, contenant le même champ que notre `RenewBookForm` d'origine, est montré ci-dessous. Tout ce que vous avez à faire pour créer le formulaire, c'est ajouter `class Meta` avec le `model` (`BookInstance`) associé, et une liste des `fields` du modèle à inclure dans le formulaire. ```python from django.forms import ModelForm -from .models import BookInstance + +from catalog.models import BookInstance class RenewBookModelForm(ModelForm): - class Meta: - model = BookInstance - fields = ['due_back',] + class Meta: + model = BookInstance + fields = ['due_back'] ``` -> **Note :** Cela peut ne pas sembler beaucoup plus simple que d'utiliser un simple `Form`, et ça ne l'est effectivement pas dans ce cas, parce que nous n'avons qu'un seul champ. Cependant, si vous avez beaucoup de champs, cela peut réduire notablement la quantité de code ! +> **Note :** Vous pouvez inclure tous les champs en utilisant `fields = '__all__'`, ou bien utiliser `exclude` (au lieu de `fields`) pour préciser les champs à ne _pas_ importer du modèle. +> +> Aucune approche n'est recommandée, car tout nouveau champ ajouté au modèle est automatiquement inclus dans le formulaire (sans considération du développeur de répercussions sécuritaires éventuelles). + +> **Note :** Cela peut ne pas sembler beaucoup plus simple que d'utiliser un simple `Form`, et ça ne l'est effectivement pas dans ce cas, parce que nous n'avons qu'un seul champ. Cependant, si vous avez beaucoup de champs, cela peut réduire notablement la quantité de code ! -Le reste de l'information vient des définitions de champ données par le modèle (par ex. les labels, les widgets, le texte d'aide, les messages d'erreur). S'ils ne sont pas suffisamment satisfaisants, nous pouvons les réécrire dans notre `class Meta`, en précisant un dictionnaire contenant le champ à modifier et sa nouvelle valeur. Par exemple, dans ce formulaire, nous pourrions souhaiter, pour notre champ, un label tel que "_Renewal date_" (plutôt que celui par défaut, basé sur le nom du champ : _Due Back_), et nous voulons aussi que notre texte d'aide soit spécifique à ce cas d'utilisation. La classe `Meta` ci-dessous vous montre comment réécrire ces champs, et vous pouvez pareillement définir `widgets` et `error_messages` si les valeurs par défaut ne sont pas suffisantes. +Le reste de l'information vient des définitions de champ données par le modèle (par exemple, les labels, les widgets, le texte d'aide, les messages d'erreur). S'ils ne sont pas suffisamment satisfaisants, nous pouvons les réécrire dans notre `class Meta`, en précisant un dictionnaire contenant le champ à modifier et sa nouvelle valeur. Par exemple, dans ce formulaire, nous pourrions souhaiter, pour notre champ, un label tel que "_Renewal date_" (plutôt que celui par défaut, basé sur le nom du champ : _Due Back_), et nous voulons aussi que notre texte d'aide soit spécifique à ce cas d'utilisation. La classe `Meta` ci-dessous vous montre comment réécrire ces champs, et vous pouvez pareillement définir `widgets` et `error_messages` si les valeurs par défaut ne sont pas suffisantes. ```python class Meta: - model = BookInstance - fields = ['due_back',] - labels = { 'due_back': _('Renewal date'), } - help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } + model = BookInstance + fields = ['due_back'] + labels = {'due_back': _('New renewal date')} + help_texts = {'due_back': _('Enter a date between now and 4 weeks (default 3).')} ``` -Pour ajouter une validation, vous pouvez utiliser la même approche que pour un `Form` normal : vous définissez une fonction appelée `clean_field_name()`, et vous levez des exceptions de type `ValidationError` pour les valeurs non valides. La seule différence par rapport à notre formulaire original, c'est que le champ de modèle est appelé `due_back` et non "`renewal_date`". Ce changement est nécessaire, dans la mesure où le champ correspondant dans `BookInstance` est appelé `due_back`. +Pour ajouter une validation, vous pouvez utiliser la même approche que pour un `Form` normal : vous définissez une fonction appelée `clean_field_name()`, et vous levez des exceptions de type `ValidationError` pour les valeurs non valides. La seule différence par rapport à notre formulaire original, c'est que le champ de modèle est appelé `due_back` et non "`renewal_date`". Ce changement est nécessaire, dans la mesure où le champ correspondant dans `BookInstance` est appelé `due_back`. ```python from django.forms import ModelForm -from .models import BookInstance + +from catalog.models import BookInstance class RenewBookModelForm(ModelForm): -    def clean_due_back(self): -       data = self.cleaned_data['due_back'] + def clean_due_back(self): + data = self.cleaned_data['due_back'] -  #Check date is not in past. -       if data < datetime.date.today(): -           raise ValidationError(_('Invalid date - renewal in past')) + # Vérifier que la date ne se situe pas dans le passé. + if data < datetime.date.today(): + raise ValidationError(_('Invalid date - renewal in past')) -       #Check date is in range librarian allowed to change (+4 weeks) -       if data > datetime.date.today() + datetime.timedelta(weeks=4): -           raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) + # Vérifier que la date tombe dans le bon intervalle (entre maintenant et dans 4 semaines). + if data > datetime.date.today() + datetime.timedelta(weeks=4): + raise ValidationError(_('Invalid date - renewal more than 4 weeks ahead')) -       # Remember to always return the cleaned data. -       return data + # N'oubliez pas de toujours renvoyer les données nettoyées. + return data - class Meta: - model = BookInstance - fields = ['due_back',] - labels = { 'due_back': _('Renewal date'), } - help_texts = { 'due_back': _('Enter a date between now and 4 weeks (default 3).'), } + class Meta: + model = BookInstance + fields = ['due_back'] + labels = {'due_back': _('Renewal date')} + help_texts = {'due_back': _('Enter a date between now and 4 weeks (default 3).')} ``` -La classe `RenewBookModelForm` ci-dessus est maintenant fonctionnellement équivalente à notre `RenewBookForm` d'origine. Vous pourriez l'importer et l'utiliser partout où vous utilisez `RenewBookForm`, du moment que vous changez aussi de `renewal_date` en `due_back` le nom de variable du formulaire correspondant, comme dans la deuxième déclaration du formulaire : `RenewBookModelForm(initial={'due_back': proposed_renewal_date}`. +La classe `RenewBookModelForm` ci-dessus est maintenant fonctionnellement équivalente à notre `RenewBookForm` d'origine. Vous pourriez l'importer et l'utiliser partout où vous utilisez `RenewBookForm`, du moment que vous changez aussi de `renewal_date` en `due_back` le nom de variable du formulaire correspondant, comme dans la deuxième déclaration du formulaire : `RenewBookModelForm(initial={'due_back': proposed_renewal_date}`. ## Vues génériques d'édition -L'algorithme de gestion des formulaires que nous avons utilisé ci-dessus dans notre exemple de vue basée sur une fonction, représente un processus extrêmement commun dans vues destinées à éditer un formulaire. Django abstrait pour vous la plus grande partie de ce processus répétitif ("boilerplate") en proposant des [generic editing views](https://docs.djangoproject.com/en/2.1/ref/class-based-views/generic-editing/) pour les vues de création, éditition et suppression basées sur des modèles. Ces vues génériques non seulement assument le comportement d'une vue, mais elles créent automatiquement la classe de formulaire (un `ModelForm`) pour vous à partir du modèle. +L'algorithme de gestion des formulaires que nous avons utilisé ci-dessus, dans notre exemple de vue fonctionnelle, représente un processus extrêmement commun dans les vues destinées à éditer un formulaire. Django abstrait pour vous la plus grande partie de ce processus répétitif (boilerplate) en proposant des [vues génériques d'édition](https://docs.djangoproject.com/fr/3.1/ref/class-based-views/generic-editing/) pour les vues de création, édition et suppression basées sur des modèles. Ces vues génériques non seulement assument le comportement d'une vue, mais elles créent automatiquement la classe de formulaire (un `ModelForm`) pour vous à partir du modèle. -> **Note :** En plus des vues d'édition décrites ici, il existe aussi une classe [FormView](https://docs.djangoproject.com/en/2.1/ref/class-based-views/generic-editing/#formview), qui se tient, en termes de rapport "flexibilité"/"effort codage", à mi-chemin entre notre vue basée sur une fonction et les autres vues génériques. En utilisant `FormView`, vous avez encore besoin de créer votre `Form`, mais vous n'avez pas besoin d'implémenter tous les éléments d'une gestion standard de formulaire. À la place, vous n'avez qu'à fournir une implémentation de la fonction qui sera appelée une fois que les données envoyées sont reconnues valides. +> **Note :** En plus des vues d'édition décrites ici, il existe aussi une classe [FormView](https://docs.djangoproject.com/fr/3.1/ref/class-based-views/generic-editing/#formview), qui se tient, en termes de rapport "flexibilité"/"effort codage", à mi-chemin entre notre vue basée sur une fonction et les autres vues génériques. En utilisant `FormView`, vous avez encore besoin de créer votre `Form`, mais vous n'avez pas besoin d'implémenter tous les éléments d'une gestion standard de formulaire. À la place, vous n'avez qu'à fournir une implémentation de la fonction qui sera appelée une fois que les données envoyées sont reconnues valides. -Dans cette section, nous allons utiliser des vues génériques d'édition pour créer des pages afin de pouvoir ajouter les fonctionnalités de création, d'édition et de suppression des enregistrements de type `Author` de notre bibliothèque, en fournissant efficacement une réimplémentation basique de certaines parties du site Admin (cela peut être intéressant si vous avez besoin d'offrir une fonctionnalité admin d'une manière plus flexible que ce qui peut être présenté par le site admin). +Dans cette section, nous allons utiliser des vues génériques d'édition pour créer des pages afin de pouvoir ajouter les fonctionnalités de création, d'édition et de suppression des enregistrements de type `Author` de notre bibliothèque, fournissant efficacement une réimplémentation basique de certaines parties du site Admin (cela peut être intéressant si vous avez besoin d'offrir une fonctionnalité admin d'une manière plus flexible que ce qui peut être présenté par le site admin). ### Vues -Ouvrez le fichier vue (**locallibrary/catalog/views.py**) et ajoutez le bloc de code suivant à la fin : +Ouvrez le fichier vue (**locallibrary/catalog/views.py**) et ajoutez le bloc de code suivant à la fin : ```python from django.views.generic.edit import CreateView, UpdateView, DeleteView from django.urls import reverse_lazy -from .models import Author + +from catalog.models import Author class AuthorCreate(CreateView): - model = Author - fields = '__all__' - initial={'date_of_death':'12/10/2016',} + model = Author + fields = ['first_name', 'last_name', 'date_of_birth', 'date_of_death'] + initial = {'date_of_death': '11/06/2020'} class AuthorUpdate(UpdateView): - model = Author - fields = ['first_name','last_name','date_of_birth','date_of_death'] + model = Author + fields = '__all__' # Non recommandé (problème potentiel de sécurité si on ajoute d'autres champs) class AuthorDelete(DeleteView): - model = Author - success_url = reverse_lazy('authors') + model = Author + success_url = reverse_lazy('authors') ``` Comme vous pouvez le voir, pour les vues "créer", "modifier" et "supprimer", vous avez besoin de dériver respectivement des vues génériques `CreateView`, `UpdateView`, et `DeleteView`, et de définir ensuite le modèle associé. -Pour les cas "créer" et "modifier", vous devez aussi préciser les champs à afficher dans le formulaire (en utilisant la même syntaxe que pour la classe `ModelForm`). Dans ce cas, nous montrons à la fois la syntaxe pour afficher "tous" les champs, et comment vous pouvez les lister un par un. Vous pouvez aussi spécifier les valeurs initiales pour chacun des champs, en utilisant un dictionnaire de paires _nom_du_champ/valeur_ (ici nous définissons arbitrairement la date de mort, uniquement dans un but de démonstration - sans doute voudrez-vous l'enlever !). Par défaut, ces vues vont rediriger en cas de succès vers une page affichant l'élément nouvellement créé ou modifié, ce qui, dans notre cas, sera la vue "détail" d'un auteur, créée dans un précédent tutoriel. Vous pouvez spécifier un autre lieu de redirection en déclarant explicitement le paramètre `success_url` (comme indiqué dans la classe `AuthorDelete`). +Pour les cas "créer" et "modifier", vous devez aussi préciser les champs à afficher dans le formulaire (en utilisant la même syntaxe que pour la classe `ModelForm`). Dans ce cas, nous montrons à la fois la syntaxe pour afficher "tous" les champs, et comment vous pouvez les lister un par un. Vous pouvez aussi spécifier les valeurs initiales pour chacun des champs en utilisant un dictionnaire de paires _nom_du_champ/valeur_ (ici nous définissons arbitrairement la date de mort, uniquement dans un but de démonstration — sans doute voudrez-vous l'enlever !). Par défaut, ces vues vont rediriger en cas de succès vers une page affichant l'élément nouvellement créé ou modifié, ce qui, dans notre cas, sera la vue "détail" d'un auteur, créée dans un précédent tutoriel. Vous pouvez spécifier un autre lieu de redirection en déclarant explicitement le paramètre `success_url` (comme indiqué dans la classe `AuthorDelete`). -La classe  ne requiert pas l'affichage d'aucun champ, aussi n'ont-ils pas besoin d'être précisés. Par contre il vous faut bien spécifier la `success_url`, car Django n'a pas de valeur par défaut pour cela. Dans ce cas, nous utilisons la fonction  pour rediriger vers notre liste d'auteurs après qu'un auteur ait été supprimé. `reverse_lazy()` est une version de `reverse()` exécutée mollement ("lazily"), que nous utilisons ici parce que nous fournissons une URL à un attribut de vue basée sur classe. +La classe `AuthorDelete` ne requiert pas l'affichage d'aucun champ, aussi n'ont-ils pas besoin d'être précisés. Par contre, il vous faut bien spécifier la `success_url`, car Django n'a pas de valeur par défaut pour cela. Dans ce cas, nous utilisons la fonction [`reverse_lazy()`](https://docs.djangoproject.com/fr/3.1/ref/urlresolvers/#reverse-lazy) pour rediriger vers notre liste d'auteurs après qu'un auteur a été supprimé. `reverse_lazy()` est une version de `reverse()` exécutée mollement ("_lazily_"), que nous utilisons ici parce que nous fournissons une URL à un attribut de vue basée sur une classe. ### Templates -Les vues "créer" et "modifier" utilisent le même template par défaut, lequel sera nommé d'après votre modèle : \*model_name**\*\_form.html** (vous pouvez changer le suffixe en autre chose que **\_form** en utilisant le champ `template_name_suffix` dans votre vue, par exemple `template_name_suffix = '_other_suffix'`). +Les vues "créer" et "modifier" utilisent le même template par défaut, lequel sera nommé d'après votre modèle : *model_name*\_**form.html** (vous pouvez changer le suffixe en autre chose que **\_form** en utilisant le champ `template_name_suffix` dans votre vue, par exemple, `template_name_suffix = '_other_suffix'`). -Créez le fichier de template **locallibrary/catalog/templates/catalog/author_form.html**, et copiez-y le texte suivant. +Créez le fichier de template **locallibrary/catalog/templates/catalog/author_form.html** et copiez-y le texte suivant. ```html {% extends "base_generic.html" %} {% block content %} - -
- {% csrf_token %} - - \{{ form.as_table }} -
- - -
+
+ {% csrf_token %} + + \{{ form.as_table }} +
+ +
{% endblock %} ``` -Ce formulaire est semblable à nos formulaires précédents et affiche les champs en utilisant un tableau. Notez aussi comment nous déclarons à nouveau le `{% csrf_token %}` pour nous assurer que nos formulaires résisteront à d'éventuelles attaques par CSRF (Cross Site Request Forgery). +Ce formulaire est semblable à nos formulaires précédents et affiche les champs en utilisant un tableau. Notez aussi comment nous déclarons à nouveau le `{% csrf_token %}` pour nous assurer que nos formulaires résisteront à d'éventuelles attaques par CSRF (_Cross-Site Request Forgery_). -La vue "supprimer" s'attend à trouver un template avec un nom au format \*model_name**\*\_confirm_delete.html** (de nouveau, vous pouvez changer le suffixe en utilisant `template_name_suffix` dans votre vue). Créez le fichier de template **locallibrary/catalog/templates/catalog/author_confirm_delete\*\***.html\*\*, et copiez-y le texte suivant. +La vue "supprimer" s'attend à trouver un template avec un nom au format *model_name*\_**confirm_delete.html** (de nouveau, vous pouvez changer le suffixe en utilisant `template_name_suffix` dans votre vue). Créez le fichier de template **locallibrary/catalog/templates/catalog/author_confirm_delete.html** et copiez-y le texte suivant. ```html {% extends "base_generic.html" %} @@ -582,8 +593,8 @@ La vue "supprimer" s'attend à trouver un template avec un nom au format \*mode

Are you sure you want to delete the author: \{{ author }}?

- {% csrf_token %} - + {% csrf_token %} +
{% endblock %} @@ -591,59 +602,77 @@ La vue "supprimer" s'attend à trouver un template avec un nom au format \*mode ### Configurations d'URL -Ouvrez votre fichier de configuration d'URL (**locallibrary/catalog/urls.py**) et ajoutez-y à la fin la configuration suivante : +Ouvrez votre fichier de configuration d'URL (**locallibrary/catalog/urls.py**) et ajoutez-y à la fin la configuration suivante : ```python urlpatterns += [ - url(r'^author/create/$', views.AuthorCreate.as_view(), name='author_create'), - url(r'^author/(?P\d+)/update/$', views.AuthorUpdate.as_view(), name='author_update'), - url(r'^author/(?P\d+)/delete/$', views.AuthorDelete.as_view(), name='author_delete'), + path('author/create/', views.AuthorCreate.as_view(), name='author-create'), + path('author//update/', views.AuthorUpdate.as_view(), name='author-update'), + path('author//delete/', views.AuthorDelete.as_view(), name='author-delete'), ] ``` -Il n'y a rien de particulièrement nouveau ici ! Vous pouvez voir que les vues sont des classes, et doivent dès lors être appelée via `.as_view()`, et vous devriez être capable de reconnaître les patterns d'URL dans chaque cas. Nous devons utiliser `pk` comme nom pour la valeur de nos clés primaires capturées, car c'est le nom de paramètre attendu par les classes de vue. +Il n'y a rien de particulièrement nouveau ici ! Vous pouvez voir que les vues sont des classes, et doivent dès lors être appelée via `.as_view()`, et vous devriez être capable de reconnaître les motifs d'URL dans chaque cas. Nous devons utiliser `pk` comme nom pour la valeur de nos clés primaires capturées, car c'est le nom de paramètre attendu par les classes de vue. Les pages de création, modification et suppression d'auteur sont maintenant prêtes à être testées (nous ne nous mettons pas en peine pour cette fois, bien que vous puissiez le faire si vous le souhaiter, de les accrocher dans la barre latérale du site). -> **Note :** Les utilisateurs observateurs auront remarqué que nous n'avons rien fait pour empêcher les utilisateurs non autorisés d'accéder ces pages ! Nous laissons cela comme exercice pour vous (suggestion : vous pourriez utiliser le `PermissionRequiredMixin`, et soit créer une nouvelle permission, soit réutiliser notre permission`can_mark_returned` ). +> **Note :** Les utilisateurs observateurs auront remarqué que nous n'avons rien fait pour empêcher les utilisateurs non autorisés d'accéder à ces pages ! Nous laissons cela comme exercice pour vous (suggestion : vous pourriez utiliser le `PermissionRequiredMixin`, et soit créer une nouvelle permission, soit réutiliser notre permission `can_mark_returned`). ### Test de la page Tout d'abord, connectez-vous au site avec un compte ayant les permissions que vous avez définies comme nécessaires pour accéder aux pages d'édition d'auteur. -Ensuite naviguez à la page de création d'auteur : , ce qui devrait ressembler à la capture d'écran ci-dessous. +Ensuite, naviguez à la page de création d'auteur, __, ce qui devrait ressembler à la capture d'écran ci-dessous. -![Form Example: Create Author](forms_example_create_author.png) +![Exemple de formulaire : création d'un auteur](forms_example_create_author.png) Entrez des valeurs pour les champs et ensuite cliquez sur **Submit** pour sauvegarder l'enregistrement de cet auteur. Vous devriez maintenant être conduit à une vue "détail" pour votre nouvel auteur, avec une URL du genre _http\://127.0.0.1:8000/catalog/author/10_. -Vous pouvez tester l'édition d'un enregistrement en ajoutant */update/* à la fin de l'URL "détail" (par exemple _http\://127.0.0.1:8000/catalog/author/10/update/_). Nous ne mettons pas de capture d'écran, car c'est à peu près la même chose que la page "create". +Vous pouvez tester l'édition d'un enregistrement en ajoutant */update/* à la fin de l'URL "détail" (par exemple, _http\://127.0.0.1:8000/catalog/author/10/update/_). Nous ne mettons pas de capture d'écran, car c'est à peu près la même chose que la page "create". -Enfin, nous pouvons effacer l'enregistrement en ajoutant "delete" à la fin de l'URL de détail (par exemple _http\://127.0.0.1:8000/catalog/author/10/delete/_). Django devrait vous afficher la page de suppression montrée ci-dessous. Cliquez sur "**Yes, delete**" pour supprimer l'enregistrement et être reconduit à la liste des auteurs. +Enfin, nous pouvons effacer l'enregistrement en ajoutant "delete" à la fin de l'URL de détail (par exemple, _http\://127.0.0.1:8000/catalog/author/10/delete/_). Django devrait vous afficher la page de suppression montrée ci-dessous. Cliquez sur "**Yes, delete**" pour supprimer l'enregistrement et être reconduit à la liste des auteurs. ![](forms_example_delete_author.png) ## Mettez-vous au défi -Créez des formulaires pour créer, modifier et effacer des enregistrements de type `Book`. Vous pouvez utiliser exactement la même structure que pour les `Authors`. Si votre template **book_form.html** est simplement copié-renommé à partir du template **author_form.html**, alors la nouvelle page "create book" va ressembler à quelque chose comme ceci : +Créez des formulaires pour créer, modifier et effacer des enregistrements de type `Book`. Vous pouvez utiliser exactement la même structure que pour les `Authors`. Si votre template **book_form.html** est simplement copié-renommé à partir du template **author_form.html**, alors la nouvelle page "create book" va ressembler à quelque chose comme ceci : ![](forms_example_create_book.png) ## Résumé -Créer et gérer des formulaires peut être un processus compliqué ! Django le rend bien plus aisé en fournissant des mécanismes de programmation pour déclarer, rendre et valider des formulaires. Django fournit de plus des vues génériques d'édition de formulaires, qui peuvent faire presque tout le travail si vous voulez définir des pages pour créer, modifier et supprimer des enregistrements associés à une instance d'un modèle unique. +Créer et gérer des formulaires peut être un processus compliqué ! Django le rend bien plus aisé en fournissant des mécanismes de programmation pour déclarer, rendre et valider des formulaires. Django fournit de plus des vues génériques d'édition de formulaires, qui peuvent faire _presque tout_ le travail si vous voulez définir des pages pour créer, modifier et supprimer des enregistrements associés à une instance d'un modèle unique. -Il y a bien d'autres choses qui peuvent être faites avec les formulaires (regardez notre liste [See also](/fr/docs/Learn/Server-side/Django/Forms#See_also) ci-dessous), mais vous devez être maintenant en mesure de comprendre comment ajouter des formulaires basiques et un code de gestion de formulaire à vos propres sites web. +Il y a bien d'autres choses qui peuvent être faites avec les formulaires (regardez notre liste [Voir aussi](#voir_aussi) ci-dessous), mais vous devez être maintenant en mesure de comprendre comment ajouter des formulaires basiques et un code de gestion de formulaire à vos propres sites web. -## See also +## Voir aussi -- [Working with forms](https://docs.djangoproject.com/en/1.10/topics/forms/) (Django docs) -- [Writing your first Django app, part 4 > Writing a simple form](https://docs.djangoproject.com/en/1.10/intro/tutorial04/#write-a-simple-form) (Django docs) -- [The Forms API](https://docs.djangoproject.com/en/1.10/ref/forms/api/) (Django docs) -- [Form fields](https://docs.djangoproject.com/en/1.10/ref/forms/fields/) (Django docs) -- [Form and field validation](https://docs.djangoproject.com/en/1.10/ref/forms/validation/) (Django docs) -- [Form handling with class-based views](https://docs.djangoproject.com/en/1.10/topics/class-based-views/generic-editing/) (Django docs) -- [Creating forms from models](https://docs.djangoproject.com/en/1.10/topics/forms/modelforms/) (Django docs) -- [Generic editing views](https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-editing/) (Django docs) +- [Utilisation des formulaires](https://docs.djangoproject.com/fr/3.1/topics/forms/) (Documentation de Django) +- [Écriture de votre première application Django, 4e partie](https://docs.djangoproject.com/fr/3.1/intro/tutorial04/#write-a-simple-form) (Documentation de Django) +- [L'API des formulaires](https://docs.djangoproject.com/fr/3.1/ref/forms/api/) (Documentation de Django) +- [Champs de formulaires](https://docs.djangoproject.com/fr/3.1/ref/forms/fields/) (Documentation de Django) +- [Les formulaires et la validation des champs](https://docs.djangoproject.com/fr/3.1/ref/forms/validation/) (Documentation de Django) +- [Gestion de formulaires avec les vues fondées sur les classes](https://docs.djangoproject.com/fr/3.1/topics/class-based-views/generic-editing/) (Documentation de Django) +- [Création de formulaires à partir de modèles](https://docs.djangoproject.com/fr/3.1/topics/forms/modelforms/) (Documentation de Django) +- [Vues génériques d'édition](https://docs.djangoproject.com/fr/3.1/ref/class-based-views/generic-editing/) (Documentation de Django) {{PreviousMenuNext("Learn/Server-side/Django/authentication_and_sessions", "Learn/Server-side/Django/Testing", "Learn/Server-side/Django")}} + +## Dans ce module + +- [Introduction à Django](/fr/docs/Learn/Server-side/Django/Introduction) +- [Mettre en place un environnement de développement Django](/fr/docs/Learn/Server-side/Django/development_environment) +- [Django didactique : Site web "Bibliothèque locale"](/fr/docs/Learn/Server-side/Django/Tutorial_local_library_website) +- [Django didactique Section 2 : Créer le squelette du site web](/fr/docs/Learn/Server-side/Django/skeleton_website) +- [Django didactique Section 3 : Utilisation des modèles de données](/fr/docs/Learn/Server-side/Django/Models) +- [Django didactique Section 4 : Site d'administration de Django](/fr/docs/Learn/Server-side/Django/Admin_site) +- [Django didactique Section 5 : Créer la page d'accueil](/fr/docs/Learn/Server-side/Django/Home_page) +- [Django didactique Section 6 : Vues génériques pour les listes et les détails](/fr/docs/Learn/Server-side/Django/Generic_views) +- [Django didactique Section 7 : Framework pour les sessions](/fr/docs/Learn/Server-side/Django/Sessions) +- [Django didactique Section 8 : Authentification des utilisateurs et permission](/fr/docs/Learn/Server-side/Django/Authentication) +- [Django didactique Section 9 : Travailler avec des formulaires](/fr/docs/Learn/Server-side/Django/Forms) +- [Django didactique Section 10 : Tester une application web Django](/fr/docs/Learn/Server-side/Django/Testing) +- [Django didactique Section 11 : Déployer une application Django en production](/fr/docs/Learn/Server-side/Django/Deployment) +- [La sécurité des applications web Django](/fr/docs/Learn/Server-side/Django/web_application_security) +- [Mise en pratique : construisez votre mini blog avec Django](/fr/docs/Learn/Server-side/Django/django_assessment_blog) \ No newline at end of file -- cgit v1.2.3-54-g00ecf