logo oujood
🔍

ModelForm Django en profondeur

ModelForm génère automatiquement un formulaire à partir d'un modèle Django. Ce chapitre va plus loin que les bases : personnalisation des widgets, labels et help_text, save(commit=False) pour modifier l'objet avant enregistrement, et gestion des clés étrangères.

OUJOOD.COM

Rappel — ce que ModelForm fait automatiquement

Quand vous déclarez un ModelForm, Django inspecte la classe Meta.model et génère un champ de formulaire pour chaque colonne du modèle. Un CharField du modèle devient un CharField dans le formulaire, un IntegerField un IntegerField, une ForeignKey un ModelChoiceField avec une liste déroulante. Les contraintes du modèle (max_length, blank, null) sont reprises automatiquement.

Personnaliser la classe Meta

La classe Meta contrôle tous les aspects du formulaire généré :

  📋 Copier le code

from django import forms
from .models import Membres

class MembreForm(forms.ModelForm):
    class Meta:
        model  = Membres
        fields = ['prenom', 'nom', 'mail']

        # Labels personnalisés
        labels = {
            'prenom': 'Prénom',
            'nom':    'Nom de famille',
            'mail':   'Adresse email',
        }

        # Textes d'aide affichés sous les champs
        help_texts = {
            'mail': 'Utilisé pour les notifications — jamais partagé.',
        }

        # Messages d'erreur personnalisés par champ et par type d'erreur
        error_messages = {
            'prenom': {
                'required': 'Le prénom est obligatoire.',
                'max_length': 'Le prénom ne peut pas dépasser 255 caractères.',
            },
            'mail': {
                'required': 'L\'adresse email est obligatoire.',
                'invalid':  'Entrez une adresse email valide.',
            },
        }

Personnaliser les widgets

Un widget contrôle comment un champ est rendu en HTML. Django choisit un widget par défaut selon le type de champ — TextInput pour un CharField, Select pour une ForeignKey. Vous pouvez le remplacer et y ajouter des attributs HTML.

  📋 Copier le code

class MembreForm(forms.ModelForm):
    class Meta:
        model  = Membres
        fields = ['prenom', 'nom', 'mail', 'bio']
        widgets = {
            # Ajouter des attributs HTML (class CSS, placeholder...)
            'prenom': forms.TextInput(attrs={
                'class':       'form-control',
                'placeholder': 'Votre prénom',
            }),
            'nom': forms.TextInput(attrs={
                'class': 'form-control',
            }),
            'mail': forms.EmailInput(attrs={
                'class': 'form-control',
            }),
            # Remplacer un CharField par un Textarea
            'bio': forms.Textarea(attrs={
                'class': 'form-control',
                'rows':  4,
            }),
        }

Widgets courants

TextInput — champ texte simple (<input type="text">).

Textarea — zone de texte multi-lignes.

PasswordInput — champ mot de passe (masque la saisie).

EmailInput — champ email avec validation HTML5.

NumberInput — champ numérique.

CheckboxInput — case à cocher.

Select — liste déroulante (utilisé automatiquement pour les ForeignKey).

DateInput — champ date avec attribut type="date".

Ajouter de la validation personnalisée

ModelForm accepte les mêmes méthodes de validation que Form : clean_nomchamp() pour un champ, clean() pour la validation croisée.

  📋 Copier le code

class MembreForm(forms.ModelForm):
    class Meta:
        model  = Membres
        fields = ['prenom', 'nom', 'mail']

    def clean_mail(self):
        mail = self.cleaned_data['mail']
        # Pour un formulaire de modification, exclure l'objet courant
        qs = Membres.objects.filter(mail=mail)
        if self.instance.pk:
            qs = qs.exclude(pk=self.instance.pk)
        if qs.exists():
            raise forms.ValidationError("Cette adresse email est déjà utilisée.")
        return mail

save(commit=False) — modifier l'objet avant enregistrement

save() crée ou met à jour l'enregistrement directement. save(commit=False) retourne un objet Python non encore enregistré en base — vous pouvez le modifier avant de l'enregistrer manuellement. Très utile pour ajouter des champs qui ne figurent pas dans le formulaire.

  📋 Copier le code

def ajouter_membre(request):
    if request.method == 'POST':
        form = MembreForm(request.POST)
        if form.is_valid():
            # Crée l'objet Python sans l'enregistrer
            membre = form.save(commit=False)
            # Ajoute des données non présentes dans le formulaire
            membre.ajoute_par = request.user
            membre.actif      = True
            # Enregistrement en base
            membre.save()
            return redirect('index')
    else:
        form = MembreForm()
    return render(request, 'ajout.html', {'form': form})

Gérer les relations ForeignKey dans un ModelForm

Quand un modèle a une ForeignKey, Django génère automatiquement un ModelChoiceField — une liste déroulante avec tous les objets liés. Vous pouvez filtrer cette liste en surchargeant __init__ :

  📋 Copier le code

from .models import Article, Categorie

class ArticleForm(forms.ModelForm):
    class Meta:
        model  = Article
        fields = ['titre', 'contenu', 'categorie']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # Limiter les catégories affichées aux catégories actives
        self.fields['categorie'].queryset = Categorie.objects.filter(active=True)
        # Personnaliser le label de l'option vide
        self.fields['categorie'].empty_label = "— Choisir une catégorie —"

Par carabde | Mis à jour le 06 mai 2026