• Aucun résultat trouvé

Des formulaires à partir de modèles

II. Premiers pas 32

9. Les formulaires 94

9.4. Des formulaires à partir de modèles

Dernière fonctionnalité que nous verrons à propos des dictionnaires : les Modelform. Il s’agit de formulaires générés automatiquement à partir d’un modèle, ce qui évite la plupart du temps de devoir écrire un formulaire pour chaque modèle créé. C’est un gain de temps non négligeable ! Ils reprennent la plupart des caractéristiques des formulaires classiques et s’utilisent comme eux.

Dans le chapitre sur les modèles, nous avons créé une classe Article. Pour rappel, la voici :

1 class Article(models.Model):

2 titre = models.CharField(max_length=100) 3 auteur = models.CharField(max_length=42) 4 slug = models.SlugField(max_length=100) 5 contenu = models.TextField(null=True)

6 date = models.DateTimeField(auto_now_add=True, auto_now=False, verbose_name="Date de parution")

7 categorie = models.ForeignKey(Categorie) 8

9 def __str__(self):

10 return self.titre

Pour faire un formulaire à partir de ce modèle, c’est très simple :

1 from django import forms 2 from models import Article

34 class ArticleForm(forms.ModelForm):

5 class Meta:

6 model = Article

Listing 51 – Extrait de blog/forms.py

Et c’est tout ! Notons que nous héritons maintenant de forms.ModelForm et non plus de forms.Form. Il y a également une sous-classe Meta (comme pour les modèles), qui permet de spécifier des informations supplémentaires. Dans l’exemple, nous avons juste indiqué sur quelle classe le ModelForm devait se baser (à savoir le modèle Article, bien entendu).

Le rendu HTML du formulaire est plutôt éloquent. Observez la figure suivante :

Figure 9.4. – Le choix de la catégorie apparaît dans le formulaire

En plus de convertir les champs de modèle vers des champs de formulaire adéquats, Django va même chercher toutes les catégories enregistrées dans la base de données et les propose comme choix pour la ForeignKey! Le framework va aussi utiliser certains paramètres des champs du

modèle pour les champs du formulaire. Par exemple, l’argument verbose_name du modèle sera utilisé comme l’argumentlabel des formulaires, help_text restehelp_text etblank devient required(blank est un argument des champs des modèles qui permet d’indiquer à l’administration et aux ModelForm si un champ peut être laissé vide ou non, il est par défaut à False).

Une fonctionnalité très pratique desModelForm est qu’il n’y a pas besoin d’extraire les données une à une pour créer ou mettre à jour un modèle. En effet, il fournit directement une méthode save qui va mettre à jour la base de données toute seul. Petit exemple dans le shell :

1 >>> from blog.models import Article, Categorie 2 >>> from blog.forms import ArticleForm

3 >>> donnees = {

4 ... 'titre':"Les crêpes c'est trop bon", 5 ... 'slug':"les-crepes-cest-trop-bon", 6 ... 'auteur':"Maxime",

7 ...

'contenu':"Vous saviez que les crêpes bretonnes c'est trop bon ? La pêche c'est nul à côté.", 8 ... 'categorie':Categorie.objects.all()[0].id # Nous prenons

l'identifiant de la première catégorie qui vient 9 ... }

10 >>> form = ArticleForm(donnees) 11 >>> Article.objects.all()

12 []

13 >>> form.save()

14 <Article: Les crêpes c'est trop bon>

15 >>> Article.objects.all()

16 [<Article: Les crêpes c'est trop bon>]

i

Tout objet d’un modèle sauvegardé possède un attribut id, c’est un identifiant propre à chaque entrée. Avec les ForeignKey, c’est lui que nous utilisons généralement comme clé étrangère.

Pratique, n’est-ce pas ? Nous avons ici simulé avec un dictionnaire le contenu d’un éventuel request.POST et l’avons passé au constructeur d’ArticleForm. Depuis la méthode save, le ModelForm va directement créer une entrée dans la base de données et retourner l’objet créé.

De la même façon, il est possible de mettre à jour une entrée très simplement. En donnant un objet du modèle sur lequel le ModelForm est basé, il peut directement remplir les champs du formulaire et mettre l’entrée à jour selon les modifications de l’utilisateur. Pour ce faire, dans une vue, il suffit d’appeler le formulaire ainsi :

1 form = ArticleForm(instance=article) # article est bien entendu un objet d'Article quelconque dans la base de données

Django se charge du reste, comme vous pouvez le voir sur la figure suivante !

Figure 9.5. – L’entrée se met automatiquement à jour !

Une fois les modifications du formulaire envoyées depuis une requêtePOST, il suffit de reconstruire un ArticleForm à partir de l’article et de la requête et d’enregistrer les changements si le formulaire est valide :

1 form = ArticleForm(request.POST, instance=article) 2 if form.is_valid():

3 form.save() L’entrée est désormais à jour.

Si vous souhaitez que certains champs ne soient pas éditables par vos utilisateurs, il est possible d’en sélectionner ou d’en exclure certains, toujours grâce à la sous-classe Meta :

1 class ArticleForm(forms.ModelForm):

2 class Meta:

3 model = Article

4 exclude = ('auteur','categorie','slug') # Exclura les champs nommés « auteur », « categorie » et « slug »

En ayant exclu ces trois champs, cela revient à sélectionner uniquement les champs titre et

contenu, comme ceci :

1 class ArticleForm(forms.ModelForm):

2 class Meta:

3 model = Article

4 fields = ('titre','contenu',)

i

Petite précision : l’attribut fieldspermet également de déterminer l’ordre des champs.

Le premier du tuple arriverait en première position dans le formulaire, le deuxième en deuxième position, etc.

Observez le résultat à la figure suivante.

Figure 9.6. – Seuls les champs « titre » et « contenu » sont éditables

Cependant, lors de la création d’une nouvelle entrée, si certains champs obligatoires du modèle (ceux qui n’ont pas null=True comme argument) ont été exclus, il ne faut pas oublier de les rajouter par la suite. Il ne faut donc pas appeler la méthodesave telle quelle sur unModelForm avec des champs exclus, sinon Django lèvera une exception. Un paramètre spécial de la méthode save a été prévu pour cette situation :

1 >>> from blog.models import Article, Categorie 2 >>> from blog.forms import ArticleForm

3 >>> donnees = {

4 ... 'titre':"Un super titre d'article !", 5 ... 'contenu':"Un super contenu ! (ou pas)"

6 ... }

7 >>> form = ArticleForm(donnees) # Pas besoin de spécifier les autres champs, ils ont été exclus

8 >>> article = form.save(commit=False) # Ne sauvegarde pas

directement l'article dans la base de données

9 >>> article.categorie = Categorie.objects.all()[0] # Nous ajoutons

les attributs manquants 10 >>> article.auteur = "Mathieu"

11 >>> article.save()

La chose importante dont il faut se souvenir ici est donc form.save(commit=False) qui permet de ne pas sauvegarder directement l’article dans la base de données, mais renvoie un objet avec les données du formulaire sur lequel nous pouvons continuer à travailler.

9.5. En résumé

— Un formulaire est décrit par une classe, héritant de django.forms.Form, où chaque attribut est un champ du formulaire défini par le type des données attendues.

— Chaque classe dedjango.formspermet d’affiner les données attendues : taille maximale du contenu du champ, champ obligatoire ou optionnel, valeur par défaut…

— Il est possible de récupérer un objet Form après la validation du formulaire et de vérifier si les données envoyées sont valides, via form.is_valid().

— La validation est personnalisable, grâce à la réécriture des méthodes clean_NOM_DU_CHAMP() etclean().

— Pour moins de redondances, la création de formulaires à partir de modèles existant se fait en héritant de la classe ModelForm, à partir de laquelle nous pouvons modifier les champs éditables et leurs comportements.

Autre point essentiel du web actuel : il est souvent utile d’envoyer des fichiers sur un site web afin que celui-ci puisse les réutiliser par la suite (avatar d’un membre, album photos, chanson…).

Nous couvrirons dans cette partie la gestion des fichiers côté serveur et les méthodes proposées par Django.