Le formulaire ( form en anglais) est un élément incontournable du web. Il permet d'envoyer au serveur des données qu'indique l'utilisateur dans des champs dynamiques. Un formulaire complet et intelligent assiste l'utilisateur sur la manière de remplir les champs mais vérifie surtout l'intégrité des informations, hors de question d'envoyer un integer si une adresse mail est attendue.
L'objet Form
Chaque formulaire dans Django est composé de l'objet Form. Celui possède deux concepts que vous devez maitriser: le field (champ) et le widget . Le field est déterminé en fonction de type de données et le widget est l'outil utilisé pour entrer une donnée.
Créer un formulaire de base
Nous allons créer un simple formulaire:
# coding: utf-8 from django.conf.urls import patterns, include, url from django.views.generic import * from django import forms class ContactForm(forms.Form): subject = forms.CharField() message = forms.CharField(widget=forms.Textarea) def send_email(self): print self.cleaned_data class ContactView(FormView): template_name = 'backoffice/templates/contact.html' form_class = ContactForm success_url = '/thanks/' def form_valid(self, form): form.send_email() return super(ContactView, self).form_valid(form) urlpatterns = patterns('', url(r'^contact/$', ContactView.as_view()), )
Créons notre template:
backoffice/templates/contact.html
<form action="" method="POST"> {% csrf_token %} {{form.as_ul}} <input type="submit" value="Envoyer"/> </form>
Regardons le résultat:
Envoyons le formulaire et regardons dans notre terminal la valeur retournée par print :
{'message': u'Connaissez-vous blablabla', 'subject': "demande d'information"}
Tadaa! En quelques lignes de code, nous avons crée un formulaire capable de nous retourner des informations.
Les méthodes de la classe Form
add_initial_prefix(self, field_name) | ajoute un prefix initial pour vérifier les données dynamiques initiales |
add_prefix(self, field_name) | retourne le nom du champ avec un préfix |
as_p(self) | retourne le rendu HTML du formulaire avec des balises p |
as_table(self) | retourne le rendu HTML du formulaire avec les balises tr (sans la balise table) |
as_ul(self) | retourne le rendu HTML du formulaire avec les balises li (sans la balise ul) |
clean(self) | hook pour faire un clean après Field.clean() Utilisé pour relever des erreurs non spécifique à un champ |
full_clean(self) | clean toutes les self.data et remplit self._errors et self.cleaned_data |
has_changed(self) | retourne True si les données ont changé du formulaire initial |
is_multipart(self) | retourne True si le formulaire est multipart-encoded c'est à dire qu'un FileInput est utilisé |
is_valid(self) | retourne True si le formulaire n'a aucune erreur. |
errors(self) | retourne les erreurs |
changed_data(self) | retourne les données modifiées |
non_field_errors(self) | retourne une liste d'erreurs qui ne sont pas associées à un champ particulier c'est à dire de Form.clean() |
visible_fields(self) | retourne les champs visibles |
hidden_fields(self) | retourne les champs invisibles |
Créer un formulaire de modèle
# coding: utf-8 from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * from django import forms class ProductForm(forms.ModelForm): class Meta: model = Product class ProductCreateView(CreateView): form_class = ProductForm model = Product success_url = "/product/new/" urlpatterns = patterns('', url(r'^product/new/$', ProductCreateView.as_view()), )
Nous travaillerons avec des vues-classes pour les exemples, je trouve ce type de vue plus lisible mais vous pouvez faire vos tests avec des vues-fonctions comme nous l'avons vu précédement.
Si vous exécutez ce code vous remarquerez que de base le paramètre form n'est pas indispensable. Nous avons d'ailleurs vu dans le chapitre sur les vues qu'il était possible de créer un formulaire complet sans que nous ayons intervenu dans quoi que ce soit. Associer une classe forms.ModelForm permet de personnaliser son formulaire.
Créer ses propres validations de formulaire
Vous pouvez relever une erreur générale comme ceci:
class ProductForm(forms.ModelForm): class Meta: model = Product def clean(self): cleaned_data = super(ProductForm, self).clean() price_ht = cleaned_data.get("price_ht") price_ttc = cleaned_data.get("price_ttc") if price_ht > price_ttc: raise forms.ValidationError("Erreur le prix HT ne peut être supérieur à TTC") return cleaned_data
Ou cibler l'erreur au niveau du champ:
class ProductForm(forms.ModelForm): class Meta: model = Product def clean(self): cleaned_data = super(ProductForm, self).clean() price_ht = cleaned_data.get("price_ht") price_ttc = cleaned_data.get("price_ttc") if price_ht > price_ttc: msg = "Le prix HT doit être plus élevé que le prix TT" self._errors['price_ht'] = self.error_class([msg]) return cleaned_data
Formset
Vous pouvez associer des formulaires de clés étrangères (comme nous l'avons vu dans le chapitre sur la contrib admin ) comme ceci:
# coding: utf-8 from django.conf.urls import patterns, include, url from django.views.generic import * from backoffice.models import * from django import forms from django.forms.models import inlineformset_factory from django.http import HttpResponseRedirect class ProductForm(forms.ModelForm): class Meta: model = Product ProductItemFormSet = inlineformset_factory(Product, ProductItem) class ProductCreateView(CreateView): form_class = ProductForm model = Product success_url = "/product/new/" def get(self, request, *args, **kwargs): self.object = None form_class = self.get_form_class() form = self.get_form(form_class) product_item_form = ProductItemFormSet() return self.render_to_response(self.get_context_data(form=form, product_item_form=product_item_form)) def post(self, request, *args, **kwargs): self.object = None form_class = self.get_form_class() form = self.get_form(form_class) product_item_form = ProductItemFormSet(self.request.POST) if form.is_valid() and product_item_form.is_valid(): return self.form_valid(form, product_item_form) else: return self.form_invalid(form, product_item_form) def form_valid(self, form, product_item_form): self.object = form.save() product_item_form.instance = self.object product_item_form.save() return HttpResponseRedirect(self.get_success_url()) def form_invalid(self, form, product_item_form): return self.render_to_response(self.get_context_data(form=form, product_item_form=product_item_form)) urlpatterns = patterns('', url(r'^product/new/$', ProductCreateView.as_view()), )
Notre template:
<form action="" method="POST"> {% csrf_token %} {{form.as_ul}} {{ product_item_form.management_form }} {{ product_item_form.non_form_errors }} {% for form in product_item_form %} <br /> {{form.as_table}} {% endfor %} <input type="submit" value="Envoyer"/> </form>
Résultat: