---
title: "Création de formulaires à partir de modèles"
version: 5.2
locale: fr
source: https://docs.djangoproject.com/fr/5.2/topics/forms/modelforms/
canonical: https://djangodocs.dev/fr/5.2/topics/forms/modelforms/
---
# Création de formulaires à partir de modèles

## `ModelForm`

#### `class ModelForm`

Si vous construisez une application basée sur une base de données, il y a des chances pour que vos formulaires correspondent étroitement avec les modèles Django. Par exemple, vous pourriez avoir un modèle `BlogComment` et vouloir créer un formulaire permettant d’envoyer des commentaires. Dans ce cas, il serait redondant de devoir définir les types de champs du formulaire, car vous avez déjà défini des champs au niveau du modèle.

C’est pour cette raison que Django fournit une classe utilitaire permettant de créer une classe de formulaire `Form` à partir d’un modèle Django.

Par exemple :

```pycon
>>> from django.forms import ModelForm
>>> from myapp.models import Article

# Create the form class.
>>> class ArticleForm(ModelForm):
...     class Meta:
...         model = Article
...         fields = ["pub_date", "headline", "content", "reporter"]
...

# Creating a form to add an article.
>>> form = ArticleForm()

# Creating a form to change an existing article.
>>> article = Article.objects.get(pk=1)
>>> form = ArticleForm(instance=article)
```

### Types de champs

La classe de formulaire générée contiendra un champ de formulaire pour chaque champ de modèle inclus, dans l’ordre indiqué par l’attribut `fields`.

Chaque champ de modèle possède un champ de formulaire par défaut. Par exemple, le champ `CharField` d’un modèle est représenté par un champ de formulaire `CharField`. Un champ `ManyToManyField` d’un modèle est représenté par un champ de formulaire `MultipleChoiceField`. Voici la liste complète des correspondances :

| Champ de modèle | Champ de formulaire |
| --- | --- |
| [`AutoField`](/fr/5.2/ref/models/fields/#django.db.models.AutoField) | Non représenté dans le formulaire |
| [`BigAutoField`](/fr/5.2/ref/models/fields/#django.db.models.BigAutoField) | Non représenté dans le formulaire |
| [`BigIntegerField`](/fr/5.2/ref/models/fields/#django.db.models.BigIntegerField) | [`IntegerField`](/fr/5.2/ref/forms/fields/#django.forms.IntegerField) avec `min_value` à -9223372036854775808 et `max_value` à 9223372036854775807. |
| [`BinaryField`](/fr/5.2/ref/models/fields/#django.db.models.BinaryField) | [`CharField`](/fr/5.2/ref/forms/fields/#django.forms.CharField), si [`editable`](/fr/5.2/ref/models/fields/#django.db.models.Field.editable) est défini à `True` sur le champ de modèle, sinon il n’est pas représenté dans le formulaire. |
| [`BooleanField`](/fr/5.2/ref/models/fields/#django.db.models.BooleanField) | [`BooleanField`](/fr/5.2/ref/forms/fields/#django.forms.BooleanField), ou [`NullBooleanField`](/fr/5.2/ref/forms/fields/#django.forms.NullBooleanField) si `null=True`. |
| [`CharField`](/fr/5.2/ref/models/fields/#django.db.models.CharField) | [`CharField`](/fr/5.2/ref/forms/fields/#django.forms.CharField) avec `max_length` définie à la même valeur que `max_length` du champ de modèle et [`empty_value`](/fr/5.2/ref/forms/fields/#django.forms.CharField.empty_value) définie à `None` si `null=True`. |
| [`DateField`](/fr/5.2/ref/models/fields/#django.db.models.DateField) | [`DateField`](/fr/5.2/ref/forms/fields/#django.forms.DateField) |
| [`DateTimeField`](/fr/5.2/ref/models/fields/#django.db.models.DateTimeField) | [`DateTimeField`](/fr/5.2/ref/forms/fields/#django.forms.DateTimeField) |
| [`DecimalField`](/fr/5.2/ref/models/fields/#django.db.models.DecimalField) | [`DecimalField`](/fr/5.2/ref/forms/fields/#django.forms.DecimalField) |
| [`DurationField`](/fr/5.2/ref/models/fields/#django.db.models.DurationField) | [`DurationField`](/fr/5.2/ref/forms/fields/#django.forms.DurationField) |
| [`EmailField`](/fr/5.2/ref/models/fields/#django.db.models.EmailField) | [`EmailField`](/fr/5.2/ref/forms/fields/#django.forms.EmailField) |
| [`FileField`](/fr/5.2/ref/models/fields/#django.db.models.FileField) | [`FileField`](/fr/5.2/ref/forms/fields/#django.forms.FileField) |
| [`FilePathField`](/fr/5.2/ref/models/fields/#django.db.models.FilePathField) | [`FilePathField`](/fr/5.2/ref/forms/fields/#django.forms.FilePathField) |
| [`FloatField`](/fr/5.2/ref/models/fields/#django.db.models.FloatField) | [`FloatField`](/fr/5.2/ref/forms/fields/#django.forms.FloatField) |
| [`ForeignKey`](/fr/5.2/ref/models/fields/#django.db.models.ForeignKey) | [`ModelChoiceField`](/fr/5.2/ref/forms/fields/#django.forms.ModelChoiceField) (voir ci-dessous) |
| [`ImageField`](/fr/5.2/ref/models/fields/#django.db.models.ImageField) | [`ImageField`](/fr/5.2/ref/forms/fields/#django.forms.ImageField) |
| [`IntegerField`](/fr/5.2/ref/models/fields/#django.db.models.IntegerField) | [`IntegerField`](/fr/5.2/ref/forms/fields/#django.forms.IntegerField) |
| `IPAddressField` | `IPAddressField` |
| [`GenericIPAddressField`](/fr/5.2/ref/models/fields/#django.db.models.GenericIPAddressField) | [`GenericIPAddressField`](/fr/5.2/ref/forms/fields/#django.forms.GenericIPAddressField) |
| [`JSONField`](/fr/5.2/ref/models/fields/#django.db.models.JSONField) | [`JSONField`](/fr/5.2/ref/forms/fields/#django.forms.JSONField) |
| [`ManyToManyField`](/fr/5.2/ref/models/fields/#django.db.models.ManyToManyField) | [`ModelMultipleChoiceField`](/fr/5.2/ref/forms/fields/#django.forms.ModelMultipleChoiceField) (voir ci-dessous) |
| [`PositiveBigIntegerField`](/fr/5.2/ref/models/fields/#django.db.models.PositiveBigIntegerField) | [`IntegerField`](/fr/5.2/ref/forms/fields/#django.forms.IntegerField) |
| [`PositiveIntegerField`](/fr/5.2/ref/models/fields/#django.db.models.PositiveIntegerField) | [`IntegerField`](/fr/5.2/ref/forms/fields/#django.forms.IntegerField) |
| [`PositiveSmallIntegerField`](/fr/5.2/ref/models/fields/#django.db.models.PositiveSmallIntegerField) | [`IntegerField`](/fr/5.2/ref/forms/fields/#django.forms.IntegerField) |
| [`SlugField`](/fr/5.2/ref/models/fields/#django.db.models.SlugField) | [`SlugField`](/fr/5.2/ref/forms/fields/#django.forms.SlugField) |
| [`SmallAutoField`](/fr/5.2/ref/models/fields/#django.db.models.SmallAutoField) | Non représenté dans le formulaire |
| [`SmallIntegerField`](/fr/5.2/ref/models/fields/#django.db.models.SmallIntegerField) | [`IntegerField`](/fr/5.2/ref/forms/fields/#django.forms.IntegerField) |
| [`TextField`](/fr/5.2/ref/models/fields/#django.db.models.TextField) | [`CharField`](/fr/5.2/ref/forms/fields/#django.forms.CharField) avec `widget=forms.Textarea` |
| [`TimeField`](/fr/5.2/ref/models/fields/#django.db.models.TimeField) | [`TimeField`](/fr/5.2/ref/forms/fields/#django.forms.TimeField) |
| [`URLField`](/fr/5.2/ref/models/fields/#django.db.models.URLField) | [`URLField`](/fr/5.2/ref/forms/fields/#django.forms.URLField) |
| [`UUIDField`](/fr/5.2/ref/models/fields/#django.db.models.UUIDField) | [`UUIDField`](/fr/5.2/ref/forms/fields/#django.forms.UUIDField) |

Comme l’on peut s’y attendre, les champs de modèle de type `ForeignKey` et `ManyToManyField` sont des cas spéciaux :

- `ForeignKey` est représenté par `django.forms.ModelChoiceField`, qui est un champ `ChoiceField`  dont les choix possibles sont définis par le `QuerySet` d’un modèle.
- `ManyToManyField` est représenté par un `django.forms.ModelMultipleChoiceField`, qui est un champ `MultipleChoiceField` dont les choix possibles sont définis par le `QuerySet` d’un modèle.

De plus, chaque champ de formulaire généré possède les attributs comme suit :

- Si le champ de modèle a `blank=True`, `required` est alors défini à `False` dans le champ de formulaire. Sinon, `required=True`.
- L’étiquette (`label`) du champ de formulaire est défini à la valeur `verbose_name` du champ de modèle, avec le premier caractère en majuscules.
- La valeur `help_text` du champ de formulaire est définie à la valeur `help_text` du champ de modèle.
- Si la valeur `choices` d’un champ de modèle est définie, le composant ( `widget`) du champ de formulaire sera de type `Select` dont les choix proviendront de la valeur `choices` du champ de modèle. Ces choix incluent normalement le choix vierge qui est sélectionné par défaut. Si le champ est obligatoire, cela force l’utilisateur à faire un choix. Le choix vierge n’est pas inclus si le champ de modèle a `blank=False` ainsi qu’une valeur par défaut (`default`) explicite (c’est la valeur `default` qui sera initialement sélectionnée).

Pour terminer, notez que vous pouvez surcharger le champ de formulaire utilisé pour un champ de modèle donné. Consultez [Surcharge des champs par défaut](#overriding-the-default-fields) ci-dessous.

### Un exemple complet

Considérez cet ensemble de modèles :

```
from django.db import models
from django.forms import ModelForm

TITLE_CHOICES = {
    "MR": "Mr.",
    "MRS": "Mrs.",
    "MS": "Ms.",
}

class Author(models.Model):
    name = models.CharField(max_length=100)
    title = models.CharField(max_length=3, choices=TITLE_CHOICES)
    birth_date = models.DateField(blank=True, null=True)

    def __str__(self):
        return self.name

class Book(models.Model):
    name = models.CharField(max_length=100)
    authors = models.ManyToManyField(Author)

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ["name", "title", "birth_date"]

class BookForm(ModelForm):
    class Meta:
        model = Book
        fields = ["name", "authors"]
```

Avec ces modèles, les sous-classes `ModelForm` ci-dessus seraient à peu près équivalentes à ceci (avec comme seule différence la méthode `save()` que nous aborderons dans un moment) :

```
from django import forms

class AuthorForm(forms.Form):
    name = forms.CharField(max_length=100)
    title = forms.CharField(
        max_length=3,
        widget=forms.Select(choices=TITLE_CHOICES),
    )
    birth_date = forms.DateField(required=False)

class BookForm(forms.Form):
    name = forms.CharField(max_length=100)
    authors = forms.ModelMultipleChoiceField(queryset=Author.objects.all())
```

### Validation d’un `ModelForm`

La validation d’un `ModelForm` se distingue en deux étapes importantes :

1. La [validation du formulaire](/fr/5.2/ref/forms/validation/)
2. La [validation de l’instance de modèle](/fr/5.2/ref/models/instances/#validating-objects)

Comme pour la validation de formulaire normale, la validation des formulaires de modèle est déclenchée implicitement lors de l’appel à [`is_valid()`](/fr/5.2/ref/forms/api/#django.forms.Form.is_valid) ou par l’accession à l’attribut [`errors`](/fr/5.2/ref/forms/api/#django.forms.Form.errors), ou explicitement en appelant `full_clean()`, même si en pratique cette dernière méthode est rarement utilisée.

La validation de `Model` ([`Model.full_clean()`](/fr/5.2/ref/models/instances/#django.db.models.Model.full_clean)) est déclenchée à l’intérieur de l’étape de validation de formulaire, juste après l’appel à la méthode `clean()` du formulaire.

> **Warning**
>
> Le processus de nettoyage modifie l’instance de modèle transmise au constructeur de `ModelForm` de plusieurs manières. Par exemple, toute valeur de champ date du modèle est convertie en vrai objet date. Une validation qui échoue peut laisser l’instance de modèle sous-jacente dans un état non cohérent et il n’est donc pas conseillé de la réutiliser.

#### Surcharge de la méthode `clean()`

Vous pouvez surcharger la méthode `clean()` d’un formulaire de modèle pour procéder à des validations supplémentaires de la même manière que pour un formulaire normal.

Une instance de formulaire de modèle liée à un objet modèle contient un attribut `instance` qui donne à ses méthodes l’accès à cette instance de modèle spécifique.

> **Warning**
>
> La méthode `ModelForm.clean()` définit un drapeau qui fait que l’étape de [validation de modèle](/fr/5.2/ref/models/instances/#validating-objects) valide l’unicité des champs de modèle marqués comme `unique`, `unique_together` ou `unique_for_date|month|year`.
>
> Si vous souhaitez surcharger la méthode `clean()` et maintenir cette validation, vous devez appeler la méthode `clean()` de la classe parente.

#### Interactions avec la validation des modèles

Dans le cadre du processus de validation, `ModelForm` appelle la méthode `clean()` de chaque champ du modèle possédant un champ de formulaire correspondant. Si vous avez exclu des champs de modèle, la validation ne sera pas effectuée pour ces champs. Consultez la documentation de la [validation des formulaires](/fr/5.2/ref/forms/validation/) pour en savoir plus sur le fonctionnement du nettoyage et de la validation des champs.

La méthode `clean()` du modèle est appelée avant les contrôles d’unicité. Consultez la [validation des objets](/fr/5.2/ref/models/instances/#validating-objects) pour plus d’informations sur la méthode `clean()` des modèles.

#### Considérations sur les messages d’erreur des modèles

Les messages d’erreur définis au niveau des [`champs de formulaire`](/fr/5.2/ref/forms/fields/#django.forms.Field.error_messages) ou au niveau de la [classe Meta de formulaire](#modelforms-overriding-default-fields) ont toujours la priorité sur les messages d’erreur définis au niveau des [`champs de modèle`](/fr/5.2/ref/models/fields/#django.db.models.Field.error_messages).

Les messages d’erreur définis au niveau des [`champs de modèle`](/fr/5.2/ref/models/fields/#django.db.models.Field.error_messages) ne sont utilisés que lorsqu’une erreur `ValidationError` est générée à l’étape de [validation du modèle](/fr/5.2/ref/models/instances/#validating-objects) et qu’aucun message d’erreur correspondant n’est défini au niveau du formulaire.

Vous pouvez surcharger les messages d’erreur issus de `NON_FIELD_ERRORS` et générés par la validation des modèles en ajoutant la clé [`NON_FIELD_ERRORS`](/fr/5.2/ref/exceptions/#django.core.exceptions.NON_FIELD_ERRORS) au dictionnaire `error_messages` de la classe `Meta` interne des classes `ModelForm`:

```
from django.core.exceptions import NON_FIELD_ERRORS
from django.forms import ModelForm

class ArticleForm(ModelForm):
    class Meta:
        error_messages = {
            NON_FIELD_ERRORS: {
                "unique_together": "%(model_name)s's %(field_labels)s are not unique.",
            }
        }
```

### La méthode `save()`

Chaque `ModelForm` possède aussi une méthode `save()`. Celle-ci crée et enregistre un objet en base de données à partir des données saisies dans le formulaire. Une sous-classe de `ModelForm` peut accepter une instance existante de modèle par le paramètre nommé `instance`. Si celui-ci est présent, `save()` met à jour cette instance. Dans le cas contraire, `save()` crée une nouvelle instance du modèle concerné :

```pycon
>>> from myapp.models import Article
>>> from myapp.forms import ArticleForm

# Create a form instance from POST data.
>>> f = ArticleForm(request.POST)

# Save a new Article object from the form's data.
>>> new_article = f.save()

# Create a form to edit an existing Article, but use
# POST data to populate the form.
>>> a = Article.objects.get(pk=1)
>>> f = ArticleForm(request.POST, instance=a)
>>> f.save()
```

Notez que si le formulaire [n’a pas été validé](#validation-on-modelform), l’appel à `save()` s’en occupera en contrôlant `form.errors`. Une erreur `ValueError` est générée si les données du formulaire ne sont pas valides, par exemple si `form.errors` est évalué à `True`.

Si un champ facultatif ne figure pas dans les données de formulaire, l’instance de modèle résultante utilise comme valeur de champ le contenu [`default`](/fr/5.2/ref/models/fields/#django.db.models.Field.default) du champ de modèle, si elle est définie. Ce comportement ne s’applique pas aux champs qui utilisent [`CheckboxInput`](/fr/5.2/ref/forms/widgets/#django.forms.CheckboxInput), [`CheckboxSelectMultiple`](/fr/5.2/ref/forms/widgets/#django.forms.CheckboxSelectMultiple) ou [`SelectMultiple`](/fr/5.2/ref/forms/widgets/#django.forms.SelectMultiple) (ou tout autre composant personnalisé dont la méthode  [`value_omitted_from_data()`](/fr/5.2/ref/forms/widgets/#django.forms.Widget.value_omitted_from_data) renvoie toujours `False`) dans la mesure où une case à cocher non cochée ou des `<select multiple>` non sélectionnés ne sont pas inclus dans l’envoi des données d’un formulaire HTML. Utilisez un champ ou un composant de formulaire personnalisé si vous concevez une API et que vous souhaitez obtenir le comportement de valeur par défaut de base pour un champ qui utilise l’un de ces composants.

Cette méthode `save()` accepte un paramètre nommé facultatif `commit`, qui accepte les valeurs `True` ou `False`. Si vous appelez `save()` avec `commit=False`, elle renvoie un objet qui n’aura pas encore été enregistré dans la base de données. Dans ce cas, c’est à vous d’appeler `save()` sur l’instance de modèle obtenue. C’est utile lorsque vous souhaitez effectuer un traitement particulier sur l’objet avant qu’il soit enregistré, ou si vous voulez utiliser l’une des [options d’enregistrement de modèle](/fr/5.2/ref/models/instances/#ref-models-force-insert) spécialisées. `commit` vaut `True` par défaut.

Un autre effet de bord de l’utilisation de `commit=False` se voit lorsque le modèle possède une relation plusieurs-à-plusieurs vers un autre modèle. Dans ce cas, Django ne peut pas enregistrer immédiatement les données du formulaire de la relation plusieurs-à-plusieurs. La raison est qu’il n’est pas possible d’enregistrer des données plusieurs-à-plusieurs propres à une instance si celle-ci n’existe pas encore dans la base de données.

Pour contourner ce problème, chaque fois que vous enregistrez un formulaire avec `commit=False`, Django ajoute une méthode `save_m2m()` à la sous-classe de votre `ModelForm`. Après avoir manuellement enregistré l’instance dérivée du formulaire, vous pouvez appeler `save_m2m()` pour enregistrer les données de formulaire de type plusieurs-à-plusieurs. Par exemple :

```pycon
# Create a form instance with POST data.
>>> f = AuthorForm(request.POST)

# Create, but don't save the new author instance.
>>> new_author = f.save(commit=False)

# Modify the author in some way.
>>> new_author.some_field = "some_value"

# Save the new instance.
>>> new_author.save()

# Now, save the many-to-many data for the form.
>>> f.save_m2m()
```

Il n’est utile d’appeler `save_m2m()` que si vous utilisez `save(commit=False)`. Lorsque vous appelez la méthode `save()` d’un formulaire, toutes les données, y compris les données plusieurs-à-plusieurs, sont enregistrées sans avoir besoin de faire quoi que ce soit d’autre. Par exemple :

```pycon
# Create a form instance with POST data.
>>> a = Author()
>>> f = AuthorForm(request.POST, instance=a)

# Create and save the new author instance. There's no need to do anything else.
>>> new_author = f.save()
```

À part les méthodes `save()` et `save_m2m()`, un `ModelForm` fonctionne exactement de la même manière que tout autre formulaire `forms`. Par exemple, la méthode `is_valid()` est utilisée pour vérifier la validité, la méthode `is_multipart()` est utilisée pour déterminer si un formulaire nécessite un envoi de fichier `multipart` (et donc que `request.FILES` doit être transmis au formulaire), etc. Voir [Liaison de fichiers téléversés avec un formulaire](/fr/5.2/ref/forms/api/#binding-uploaded-files) pour plus d’informations.

### Sélection des champs à utiliser

Il est fortement recommandé de définir explicitement tous les champs qui doivent être présents dans le formulaire en utilisant l’attribut `fields`. Si vous ne le faites pas, cela peut facilement devenir source de problèmes de sécurité lorsqu’un formulaire permet à un utilisateur de définir certains champs de manière non prévue, particulièrement lorsqu’on ajoute de nouveaux champs à un modèle. En fonction de la façon dont le formulaire est affiché, il est même possible que le problème ne soit pas visible sur la page Web.

Une autre approche est d’inclure automatiquement tous les champs ou d’en exclure certains. Mais à la base, cette approche est notoirement moins sécurisée et a déjà abouti à des failles sévères sur des sites Web célèbres (par ex. GitHub \_).

Il existe toutefois deux raccourcis disponibles pour les cas où vous pouvez garantir que ces questions de sécurité ne s’appliquent pas à votre situation :

1. Définir l’attribut `fields` à la valeur spéciale `'__all__'` pour indiquer que tous les champs du modèle doivent être utilisés. Par exemple :

   ```
   from django.forms import ModelForm

   class AuthorForm(ModelForm):
       class Meta:
           model = Author
           fields = "__all__"
   ```
2. Définir l’attribut `exclude` de la classe interne `Meta` du `ModelForm` à une liste de champs à exclure du formulaire.

   Par exemple :

   ```
   class PartialAuthorForm(ModelForm):
       class Meta:
           model = Author
           exclude = ["title"]
   ```

   Comme le modèle `Author` possède les trois champs `name`, `title` et `birth_date`, nous nous retrouvons avec les champs `name` et `birth_date` affichés dans le formulaire.

Si l’une de ces techniques est employée, l’ordre d’apparition des champs dans le formulaire respecte l’ordre de définition des champs du modèle, avec les instances `ManyToManyField` apparaissant en dernier.

De plus, Django applique la règle suivante : si vous définissez `editable=False` au niveau du champ de modèle, *tout* formulaire créé à partir du modèle via `ModelForm` ne contiendra pas ce champ.

> **Note**
>
> Tout champ non inclus dans un formulaire par la logique ci-dessus ne sera pas touché par la méthode `save()` du formulaire. De même, si vous ajoutez manuellement dans un formulaire un champ initialement exclu, ce champ ne sera pas initialisé avec les données de l’instance de modèle.
>
> Django empêche toute tentative d’enregistrer un modèle incomplet. Si le modèle n’autorise pas les champs manquants à être vides et que ces champs n’ont pas de valeur par défaut, tout tentative d’appeler `save()` sur un `ModelForm` avec des champs manquants échouera. Pour éviter ce problème, vous devez créer une instance de modèle comportant des valeurs initiales pour les champs obligatoires manquants :
>
> ```
> author = Author(title="Mr")
> form = PartialAuthorForm(request.POST, instance=author)
> form.save()
> ```
>
> Une variante est d’utiliser `save(commit=False)` et de définir manuellement les champs obligatoires supplémentaires :
>
> ```
> form = PartialAuthorForm(request.POST)
> author = form.save(commit=False)
> author.title = "Mr"
> author.save()
> ```
>
> Consultez la [section sur l’enregistrement des formulaires](#the-save-method) pour plus de détails sur l’utilisation de `save(commit=False)`.

### Surcharge des champs par défaut

Les types de champs par défaut, tels que décrits dans le tableau ci-dessus [Types de champs](#field-types), sont des choix raisonnables. Si votre modèle contient un champ `DateField`, il est probable que vous vouliez représenter ce champ dans un formulaire par un champ de formulaire `DateField`. Mais `ModelForm` apporte la souplesse de pouvoir modifier le champ de formulaire pour un modèle défini.

Pour indiquer un composant personnalisé pour un champ, utilisez l’attribut `widgets` de la classe `Meta` interne. Ce doit être un dictionnaire faisant correspondre les noms de champs à des classes ou des instances de composants.

Par exemple, si vous voulez que le champ `CharField` de l’attribut `name` du modèle `Author` soit représenté par un composant `<textarea>` au lieu du composant `<input type="text">` par défaut, vous pouvez surcharger le composant du champ :

```
from django.forms import ModelForm, Textarea
from myapp.models import Author

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ["name", "title", "birth_date"]
        widgets = {
            "name": Textarea(attrs={"cols": 80, "rows": 20}),
        }
```

Le dictionnaire `widgets` accepte soit des instances de composants (par ex.  `Textarea(...)`) ou des classes (par ex. `Textarea`). Notez que le dictionnaire `widgets` est ignoré pour les champs de modèles ayant un attribut `choices` non vide. Dans ce cas, vous devez surcharger le champ de formulaire pour indiquer un composant `widget` différent.

De la même façon, vous pouvez indiquer les attributs `labels`, `help_texts` et `error_messages` de la classe interne `Meta` si vous avez besoin de personnaliser davantage un champ.

Par exemple, si vous souhaitez personnaliser la formulation de tous les textes visibles pour l’utilisateur concernant le champ `name`:

```
from django.utils.translation import gettext_lazy as _

class AuthorForm(ModelForm):
    class Meta:
        model = Author
        fields = ["name", "title", "birth_date"]
        labels = {
            "name": _("Writer"),
        }
        help_texts = {
            "name": _("Some useful help text."),
        }
        error_messages = {
            "name": {
                "max_length": _("This writer's name is too long."),
            },
        }
```

Vous pouvez aussi indiquer `field_classes` ou `formfield_callback` pour personnaliser le type des champs créés par le formulaire.

Par exemple, si vous souhaitez utiliser `MySlugFormField` pour le champ `slug`, vous pourriez procéder ainsi :

```
from django.forms import ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ["pub_date", "headline", "content", "reporter", "slug"]
        field_classes = {
            "slug": MySlugFormField,
        }
```

ou :

```
from django.forms import ModelForm
from myapp.models import Article

def formfield_for_dbfield(db_field, **kwargs):
    if db_field.name == "slug":
        return MySlugFormField()
    return db_field.formfield(**kwargs)

class ArticleForm(ModelForm):
    class Meta:
        model = Article
        fields = ["pub_date", "headline", "content", "reporter", "slug"]
        formfield_callback = formfield_for_dbfield
```

Finalement, si vous souhaitez contrôler complètement un champ, y compris son type, ses validateurs, son caractère obligatoire, etc., vous pouvez le faire en définissant les champs de manière déclarative comme on le ferait pour un formulaire `Form` normal.

Si vous souhaitez indiquer les validateurs d’un champ, vous pouvez le faire en définissant le champ de manière déclarative et en indiquant le paramètre `validators`:

```
from django.forms import CharField, ModelForm
from myapp.models import Article

class ArticleForm(ModelForm):
    slug = CharField(validators=[validate_slug])

    class Meta:
        model = Article
        fields = ["pub_date", "headline", "content", "reporter", "slug"]
```

> **Note**
>
> Lorsque vous créez une instance de formulaire explicitement comme ceci, il est important de comprendre comment les formulaires de type `ModelForm` et `Form` sont liés.
>
> `ModelForm` est comme un formulaire `Form` normal qui peut générer automatiquement certains champs. Les champs automatiquement générés dépendent du contenu de la classe `Meta` et des champs éventuellement définis de manière déclarative. Au fond, `ModelForm` va **uniquement** générer les champs qui sont **absents** du formulaire, ou en d’autres termes, les champs qui n’ont pas été définis déclarativement.
>
> Les champs définis déclarativement sont laissés tels quels, ce qui fait que toute personnalisation effectuée au niveau des attributs de `Meta`, tels que `widgets`, `labels`, `help_texts` ou `error_messages` est ignorée ; ces derniers ne s’appliquent qu’aux champs automatiquement générés.
>
> De même, les champs définis déclarativement ne dérivent pas leurs attributs comme `max_length` ou `required` du modèle correspondant. Si vous voulez conserver le comportement défini au niveau du modèle, vous devez définir les paramètres adéquats explicitement dans la déclaration du champ de formulaire.
>
> Par exemple, si le modèle `Article` ressemble à ceci :
>
> ```
> class Article(models.Model):
>     headline = models.CharField(
>         max_length=200,
>         null=True,
>         blank=True,
>         help_text="Use puns liberally",
>     )
>     content = models.TextField()
> ```
>
> et que vous vouliez effectuer une validation particulière pour `headline` tout en conservant les valeurs `blank` et `help_text` d’origine, voici comment vous pourriez définir `ArticleForm`:
>
> ```
> class ArticleForm(ModelForm):
>     headline = MyFormField(
>         max_length=200,
>         required=False,
>         help_text="Use puns liberally",
>     )
>
>     class Meta:
>         model = Article
>         fields = ["headline", "content"]
> ```
>
> Vous devez vous assurer que le type du champ de formulaire peut être utilisé pour définir le contenu du champ de modèle correspondant. S’ils ne sont pas compatibles, vous obtiendrez une erreur `ValueError` car aucune conversion implicite n’est effectuée.
>
> Consultez la [documentation des champs de formulaire](/fr/5.2/ref/forms/fields/) pour plus d’informations sur les champs et leurs paramètres.

### Activation de la régionalisation des champs

Par défaut, les champs d’un `ModelForm` ne régionalisent pas leurs données. Pour activer cette régionalisation, vous pouvez utiliser l’attribut `localized_fields` de la classe `Meta`.

```
>>> from django.forms import ModelForm
>>> from myapp.models import Author
>>> class AuthorForm(ModelForm):
...     class Meta:
...         model = Author
...         localized_fields = ['birth_date']
```

Si `localized_fields` est défini à la valeur spéciale `'__all__'`, les valeurs de tous les champs seront régionalisées.

### Héritage de formulaire

Comme pour les formulaires de base, il est possible d’étendre et de réutiliser les classes `ModelForm` par l’héritage. C’est utile lorsque vous devez déclarer des champs ou des méthodes supplémentaires dans une sous-classe pour les utiliser dans des formulaires dérivés de modèles. Par exemple, en utilisant la classe `ArticleForm` présentée précédemment :

```pycon
>>> class EnhancedArticleForm(ArticleForm):
...     def clean_pub_date(self): ...
...
```

Cela crée un formulaire qui se comporte comme `ArticleForm`, à l’exception de certaines validations et nettoyages supplémentaires pour le champ `pub_date`.

Il est aussi possible d’hériter de la classe `Meta` interne du parent si vous avez besoin de modifier les listes `Meta.fields` ou `Meta.exclude`:

```pycon
>>> class RestrictedArticleForm(EnhancedArticleForm):
...     class Meta(ArticleForm.Meta):
...         exclude = ["body"]
...
```

Nous conservons ici la méthode supplémentaire de `EnhancedArticleForm` et enlevons un champ de la classe `ArticleForm.Meta` originale.

Il faut cependant relever certains points.

- Les règles Python habituelles de la résolution de noms s’appliquent. Si vous avez plusieurs classes de base déclarant une classe interne `Meta`, seule la première sera considérée. C’est -à-dire la classe `Meta` de la sous-classe, s’il y en a une, sinon celle du premier parent, etc.
- Il est possible d’hériter à la fois de `Form` et de `ModelForm`. Cependant, il faut s’assurer que `ModelForm` apparaisse en premier dans la chaîne MRO. Ceci parce que ces classes se basent sur des métaclasses différentes et qu’une classe ne peut posséder qu’une seule métaclasse.
- Il est possible d’enlever de manière déclarative un champ `Field` hérité d’une classe parente en définissant son nom à `None` dans la sous-classe.

  Cette technique n’est utilisable que pour enlever des champs qui ont eux-mêmes été définis de manière déclarative dans la classe parente ; cela n’empêchera pas la métaclasse de `ModelForm` de générer un champ par défaut. Pour enlever des champs par défaut, voir [Sélection des champs à utiliser](#modelforms-selecting-fields).

### Indication de valeurs initiales

Comme avec les formulaires normaux, il est possible de spécifier les données initiales pour les formulaires en spécifiant un paramètre `initial` lors de l’instanciation du formulaire. Les valeurs initiales prévues de cette façon remplaceront la valeur initiale définie pour le champ et celle définie pour le modèle lié. Par exemple :

```pycon
>>> article = Article.objects.get(pk=1)
>>> article.headline
'My headline'
>>> form = ArticleForm(initial={"headline": "Initial headline"}, instance=article)
>>> form["headline"].value()
'Initial headline'
```

### Fonction de fabrique de ModelForm

Vous pouvez créer des formulaires à partir d’un modèle donné en utilisant la fonction autonome [`modelform_factory()`](/fr/5.2/ref/forms/models/#django.forms.models.modelform_factory) au lieu de passer par une définition de classe. Cela peut être plus pratique si vous n’avez pas à effectuer trop d’adaptations :

```pycon
>>> from django.forms import modelform_factory
>>> from myapp.models import Book
>>> BookForm = modelform_factory(Book, fields=["author", "title"])
```

Cela peut également être utilisé pour faire des modifications à des formulaires existants, par exemple pour indiquer les composants à utiliser pour un certain champ :

```pycon
>>> from django.forms import Textarea
>>> Form = modelform_factory(Book, form=BookForm, widgets={"title": Textarea()})
```

Les champs à inclure peuvent être spécifiés en utilisant les paramètres nommés `fields` et `exclude`, ou les attributs correspondants de la classe interne `Meta` du `ModelForm`. Veuillez consulter la documentation [Sélection des champs à utiliser](#modelforms-selecting-fields) des `ModelForm`.

… ou pour activer la régionalisation de certains champs :

```pycon
>>> Form = modelform_factory(Author, form=AuthorForm, localized_fields=["birth_date"])
```

## Formulaires groupés de modèles

#### `class models.BaseModelFormSet`

Comme pour les [formulaires groupés normaux](/fr/5.2/topics/forms/formsets/), Django fournit quelques classes de formulaires groupés améliorées qui facilitent le travail avec les modèles Django. Réutilisons le modèle `Author` défini plus haut :

```pycon
>>> from django.forms import modelformset_factory
>>> from myapp.models import Author
>>> AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
```

L’utilisation de `fields` indique aux formulaires groupés  les champs qu’ils doivent utiliser. Il est aussi possible d’utiliser une approche par exclusion, indiquant quels sont les champs à exclure :

```pycon
>>> AuthorFormSet = modelformset_factory(Author, exclude=["birth_date"])
```

Cela crée un jeu de formulaires groupés qui sont capables de gérer les données associées au modèle `Author`. Le fonctionnement est le même que pour des formulaires groupés normaux :

```pycon
>>> formset = AuthorFormSet()
>>> print(formset)
<input type="hidden" name="form-TOTAL_FORMS" value="1" id="id_form-TOTAL_FORMS"><input type="hidden" name="form-INITIAL_FORMS" value="0" id="id_form-INITIAL_FORMS"><input type="hidden" name="form-MIN_NUM_FORMS" value="0" id="id_form-MIN_NUM_FORMS"><input type="hidden" name="form-MAX_NUM_FORMS" value="1000" id="id_form-MAX_NUM_FORMS">
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" maxlength="100"></div>
<div><label for="id_form-0-title">Title:</label><select name="form-0-title" id="id_form-0-title">
<option value="" selected>---------</option>
<option value="MR">Mr.</option>
<option value="MRS">Mrs.</option>
<option value="MS">Ms.</option>
</select><input type="hidden" name="form-0-id" id="id_form-0-id"></div>
```

> **Note**
>
> [`modelformset_factory()`](/fr/5.2/ref/forms/models/#django.forms.models.modelformset_factory) utilise [`formset_factory()`](/fr/5.2/ref/forms/formsets/#django.forms.formsets.formset_factory) pour générer les formulaires groupés. Cela signifie qu’un formulaire groupé de modèle est une extension des formulaires groupés de base sachant interagir avec un modèle particulier.

> **Note**
>
> Dans les contextes d’[héritages multi-tables](/fr/5.2/topics/db/models/#multi-table-inheritance), les formulaires générés par une fabrique de jeux de formulaires contiennent un champ de lien parent (par défaut `<nom_modèle_parent>_ptr`) au lieu d’un champ `id`.

### Modification du jeu de requête

Par défaut, lorsque vous créez des formulaires groupés à partir d’un modèle, ceux-ci utilisent un jeu de requête qui contient tous les objets du modèle (par ex. `Author.objects.all()`). Vous pouvez surcharger ce comportement en utilisant le paramètre `queryset`:

```pycon
>>> formset = AuthorFormSet(queryset=Author.objects.filter(name__startswith="O"))
```

Comme variante, vous pouvez créer une sous-classe définissant `self.queryset` dans sa méthode `__init__`:

```
from django.forms import BaseModelFormSet
from myapp.models import Author

class BaseAuthorFormSet(BaseModelFormSet):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queryset = Author.objects.filter(name__startswith="O")
```

Puis, passez votre classe `BaseAuthorFormSet` à la fonction de fabrique :

```pycon
>>> AuthorFormSet = modelformset_factory(
...     Author, fields=["name", "title"], formset=BaseAuthorFormSet
... )
```

Si vous souhaitez renvoyer des formulaires groupés qui ne contiennent *aucune* instance préexistante du modèle, vous pouvez indiquer un objet `QuerySet` vide :

```pycon
>>> AuthorFormSet(queryset=Author.objects.none())
```

### Modification du formulaire

Par défaut, lorsque vous utilisez `modelformset_factory`, un formulaire de modèle est créé par [`modelform_factory()`](/fr/5.2/ref/forms/models/#django.forms.models.modelform_factory). Il peut souvent être utile d’indiquer un formulaire de modèle personnalisé. Par exemple, vous pouvez créer un formulaire de modèle personnalisé comportant une validation personnalisée :

```
class AuthorForm(forms.ModelForm):
    class Meta:
        model = Author
        fields = ["name", "title"]

    def clean_name(self):
        # custom validation for the name field
        ...
```

Puis, indiquez ce formulaire de modèle à la fonction de fabrication :

```
AuthorFormSet = modelformset_factory(Author, form=AuthorForm)
```

Il n’est pas toujours nécessaire de définir un formulaire de modèle personnalisé. La fonction `modelformset_factory` accepte plusieurs paramètres qui sont eux-mêmes transmis à `modelform_factory` et qui sont documentés plus bas.

### Indication des composants de formulaire à utiliser avec `widgets`

En exploitant le paramètre `widgets`, vous pouvez indiquer un dictionnaire de valeurs afin de personnaliser la classe de composant d’un champ particulier du `ModelForm`. C’est le même principe de fonctionnement que le dictionnaire `widgets` de la classe `Meta` interne d’un `ModelForm`:

```pycon
>>> AuthorFormSet = modelformset_factory(
...     Author,
...     fields=["name", "title"],
...     widgets={"name": Textarea(attrs={"cols": 80, "rows": 20})},
... )
```

### Activation de la régionalisation des champs avec `localized_fields`

À l’aide du paramètre `localized_fields`, vous pouvez activer la régionalisation des champs du formulaire.

```
>>> AuthorFormSet = modelformset_factory(
...     Author, fields=['name', 'title', 'birth_date'],
...     localized_fields=['birth_date'])
```

Si `localized_fields` est défini à la valeur spéciale `'__all__'`, les valeurs de tous les champs seront régionalisées.

### Indication de valeurs initiales

Comme pour les formulaires groupés normaux, il est possible [de fournir des données initiales](/fr/5.2/topics/forms/formsets/#formsets-initial-data) aux formulaires groupés en indiquant le paramètre `initial` au moment de la création de l’instance de classe de formulaires groupés de modèles renvoyée par [`modelformset_factory()`](/fr/5.2/ref/forms/models/#django.forms.models.modelformset_factory). Cependant, avec les formulaires groupés de modèles, les valeurs initiales ne s’appliquent qu’aux formulaires supplémentaires, ceux qui ne sont pas liés à des instances d’objets existants. Si la longueur de `initial` dépasse le nombre de formulaires supplémentaires, les données initiales en trop sont ignorées. Si les formulaires supplémentaires avec données initiales ne sont pas modifiés par l’utilisateur, ils ne seront ni validés, ni enregistrés.

### Enregistrement des objets des formulaires groupés

Comme pour un `ModelForm`, vous pouvez enregistrer les données sous forme d’objet de modèle. Cela se fait par la méthode `save()` des formulaires groupés :

```pycon
# Create a formset instance with POST data.
>>> formset = AuthorFormSet(request.POST)

# Assuming all is valid, save the data.
>>> instances = formset.save()
```

La méthode `save()` renvoie les instances qui ont été enregistrées dans la base de données. Si les données d’une instance précise n’ont pas été modifiées dans les données du formulaire, l’instance ne sera pas enregistrée dans la base de données et ne sera pas non plus incluse dans la valeur renvoyée (`instances`, dans l’exemple ci-dessus).

Lorsque des champs sont absents du formulaire (par exemple en raison de leur exclusion), ces champs ne sont pas modifiés par la méthode `save()`. Vous pouvez trouver plus d’informations sur cette restriction, qui est aussi valable pour les formulaires de modèles normaux, dans [Sélection des champs à utiliser](#selecting-the-fields-to-use).

Passez `commit=False` pour recevoir les instances de modèles non enregistrées :

```pycon
# don't save to the database
>>> instances = formset.save(commit=False)
>>> for instance in instances:
...     # do something with instance
...     instance.save()
...
```

Cela vous permet de définir des données complémentaires dans les instances avant de les enregistrer dans la base de données. Si vos formulaires groupés contiennent un champ `ManyToManyField`, vous devrez également appeler `formset.save_m2m()` pour vous assurer que les liens plusieurs-à-plusieurs soient correctement enregistrés.

Après l’appel à `save()`, les formulaires groupés du modèle posséderont trois nouveaux attributs contenant les modifications des formulaires :

#### `models.BaseModelFormSet.changed_objects`

#### `models.BaseModelFormSet.deleted_objects`

#### `models.BaseModelFormSet.new_objects`

### Restriction du nombre d’objets modifiables

Comme pour les formulaires groupés normaux, vous pouvez utiliser les paramètres `max_num` et `extra` de [`modelformset_factory()`](/fr/5.2/ref/forms/models/#django.forms.models.modelformset_factory) pour restreindre le nombre de formulaires supplémentaires affichés.

`max_num` n’empêche pas les objets existants d’être affichés :

```pycon
>>> Author.objects.order_by("name")
<QuerySet [<Author: Charles Baudelaire>, <Author: Paul Verlaine>, <Author: Walt Whitman>]>

>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=1)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> [x.name for x in formset.get_queryset()]
['Charles Baudelaire', 'Paul Verlaine', 'Walt Whitman']
```

De plus, `extra=0` n’empêche pas la création de nouvelles instances de modèles car il est possible [d’ajouter des formulaires supplémentaires par JavaScript](/fr/5.2/topics/forms/formsets/#understanding-the-managementform) ou d’envoyer des données POST supplémentaires. Consultez [Empêcher la création de nouveaux objets](#model-formsets-edit-only) pour voir la manière de faire cela.

Si la valeur de `max_num` est plus grande que le nombre d’objets liés existants, un maximum de  `extra` formulaires vierges supplémentaires seront ajoutés aux formulaires groupés, aussi longtemps que le nombre total de formulaires n’excède pas `max_num`:

```pycon
>>> AuthorFormSet = modelformset_factory(Author, fields=["name"], max_num=4, extra=2)
>>> formset = AuthorFormSet(queryset=Author.objects.order_by("name"))
>>> for form in formset:
...     print(form)
...
<div><label for="id_form-0-name">Name:</label><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100"><input type="hidden" name="form-0-id" value="1" id="id_form-0-id"></div>
<div><label for="id_form-1-name">Name:</label><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100"><input type="hidden" name="form-1-id" value="3" id="id_form-1-id"></div>
<div><label for="id_form-2-name">Name:</label><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100"><input type="hidden" name="form-2-id" value="2" id="id_form-2-id"></div>
<div><label for="id_form-3-name">Name:</label><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100"><input type="hidden" name="form-3-id" id="id_form-3-id"></div>
```

Une valeur `max_num` `None` (par défaut) place une limite élevée du nombre de formulaires affichés (1000). En pratique, cela équivaut à aucune limite.

### Empêcher la création de nouveaux objets

Avec le paramètre `edit_only`, il est possible d’empêcher la création de tout nouvel objet :

```pycon
>>> AuthorFormSet = modelformset_factory(
...     Author,
...     fields=["name", "title"],
...     edit_only=True,
... )
```

Ici, le jeu de formulaires ne fera que modifier des instances `Author` existantes. Aucun autre objet ne sera créé ou modifié.

### Utilisation de formulaires groupés de modèle dans une vue

Les formulaires groupés de modèle sont très semblables aux formulaires groupés normaux. Admettons que nous voulions afficher des formulaires groupés pour modifier les instances du modèle `Author`:

```
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
    if request.method == "POST":
        formset = AuthorFormSet(request.POST, request.FILES)
        if formset.is_valid():
            formset.save()
            # do something.
    else:
        formset = AuthorFormSet()
    return render(request, "manage_authors.html", {"formset": formset})
```

Comme vous pouvez le voir, la logique de la vue de formulaires groupés de modèle n’est pas fondamentalement différente de celle de formulaires groupés normaux. La seule différence est que nous appelons `formset.save()` pour enregistrer les données dans la base de données (comme cela a été décrit ci-dessus dans [Enregistrement des objets des formulaires groupés](#saving-objects-in-the-formset)).

### Surcharge de `clean()` d’un `ModelFormSet`

Tout comme pour un `ModelForm`, la méthode `clean()` de `ModelFormSet` valide par défaut qu’aucun des éléments dans les formulaires groupés ne viole les contraintes d’unicité de votre modèle (que ce soit `unique`, `unique_together` ou `unique_for_date|month|year`). Si vous souhaitez surcharger la méthode `clean()` d’un `ModelFormSet` et conserver cette validation, vous devez appeler la méthode `clean` de la classe parente :

```
from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...
```

Notez également qu’une fois que vous avez atteint cette étape, les instances de modèles individuelles pour chaque formulaire ont déjà été créées. Il ne suffit donc pas de modifier une valeur dans `form.cleaned_data` pour changer la valeur enregistrée. Si vous souhaitez modifier une valeur dans `ModelFormSet.clean()`, vous devez modifier `form.instance`:

```
from django.forms import BaseModelFormSet

class MyModelFormSet(BaseModelFormSet):
    def clean(self):
        super().clean()

        for form in self.forms:
            name = form.cleaned_data["name"].upper()
            form.cleaned_data["name"] = name
            # update the instance value.
            form.instance.name = name
```

### Utilisation d’un jeu de requête personnalisé

Comme indiqué précédemment, il est possible de surcharger le jeu de requête utilisé par défaut par les formulaires groupés de modèle :

```
from django.forms import modelformset_factory
from django.shortcuts import render
from myapp.models import Author

def manage_authors(request):
    AuthorFormSet = modelformset_factory(Author, fields=["name", "title"])
    queryset = Author.objects.filter(name__startswith="O")
    if request.method == "POST":
        formset = AuthorFormSet(
            request.POST,
            request.FILES,
            queryset=queryset,
        )
        if formset.is_valid():
            formset.save()
            # Do something.
    else:
        formset = AuthorFormSet(queryset=queryset)
    return render(request, "manage_authors.html", {"formset": formset})
```

Notez que nous transmettons le paramètre `queryset` à la fois aux requêtes `POST` et `GET` dans cet exemple.

### Utilisation des formulaires groupés dans un gabarit

Il y a trois façons d’afficher des formulaires groupés dans un gabarit Django.

Premièrement, vous pouvez déléguer aux formulaires groupés l’essentiel du travail :

```html+django
<form method="post">
    {{ formset }}
</form>
```

Deuxièmement, vous pouvez afficher les formulaires groupés manuellement, mais laisser le soin à chaque formulaire de s’afficher lui-même :

```html+django
<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form }}
    {% endfor %}
</form>
```

Lorsque vous vous occupez vous-même d’afficher les différents formulaires, prenez soin d’inclure également le formulaire de gestion comme le montre l’exemple ci-dessus. Voir la [documentation du formulaire de gestion](/fr/5.2/topics/forms/formsets/#understanding-the-managementform).

Troisièmement, vous pouvez afficher manuellement chaque champ :

```html+django
<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {% for field in form %}
            {{ field.label_tag }} {{ field }}
        {% endfor %}
    {% endfor %}
</form>
```

Si vous optez pour cette troisième méthode et que vous ne bouclez pas sur les champs avec un `{% for %}`, vous devez aussi inclure le champ de clé primaire. Par exemple, si vous affichez les champs `name` et `age` d’un modèle :

```html+django
<form method="post">
    {{ formset.management_form }}
    {% for form in formset %}
        {{ form.id }}
        <ul>
            <li>{{ form.name }}</li>
            <li>{{ form.age }}</li>
        </ul>
    {% endfor %}
</form>
```

Voyez comment nous devons explicitement afficher `{{ form.id }}`. Ceci pour s’assurer du bon fonctionnement des formulaires groupés de modèle, dans le cas de l’envoi `POST` (cet exemple suppose que la clé primaire s’appelle `id`. Si vous avez explicitement nommé la clé primaire su modèle autrement que `id`, prenez soin de l’afficher).

## Sous-formulaires groupés

#### `class models.BaseInlineFormSet`

Les sous-formulaires groupés sont une petite couche d’abstraction au-dessus des formulaires groupés de modèle. Ils simplifient la situation où l’on manipule des objets liés au travers d’une clé étrangère. Supposons que l’on dispose de ces deux modèles :

```
from django.db import models

class Author(models.Model):
    name = models.CharField(max_length=100)

class Book(models.Model):
    author = models.ForeignKey(Author, on_delete=models.CASCADE)
    title = models.CharField(max_length=100)
```

Si vous voulez créer des formulaires groupés permettant de modifier les livres appartenant à un auteur particulier, vous pourriez faire ainsi :

```pycon
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(Author, Book, fields=["title"])
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
```

Le [préfixe](/fr/5.2/topics/forms/formsets/#formset-prefix) de `BookFormSet` est `'book_set'` (`<nom_modèle>_set` ). Si la clé étrangère de `Book` vers `Author` possède l’attribut [`related_name`](/fr/5.2/ref/models/fields/#django.db.models.ForeignKey.related_name), c’est ce dernier qui est utilisé.

> **Note**
>
> [`inlineformset_factory()`](/fr/5.2/ref/forms/models/#django.forms.models.inlineformset_factory) utilise [`modelformset_factory()`](/fr/5.2/ref/forms/models/#django.forms.models.modelformset_factory) et définit `can_delete=True`.

> **See also**
>
> [can\_delete et can\_order affichés manuellement](/fr/5.2/topics/forms/formsets/#manually-rendered-can-delete-and-can-order).

### Surcharge des méthodes d’un `InlineFormSet`

Lorsque vous surchargez des méthodes de sous-formulaires groupés (`InlineFormSet`), il est préférable d’hériter de [`BaseInlineFormSet`](#django.forms.models.BaseInlineFormSet) plutôt que de [`BaseModelFormSet`](#django.forms.models.BaseModelFormSet).

Par exemple, si vous souhaitez surcharger `clean()`:

```
from django.forms import BaseInlineFormSet

class CustomInlineFormSet(BaseInlineFormSet):
    def clean(self):
        super().clean()
        # example custom validation across forms in the formset
        for form in self.forms:
            # your custom formset validation
            ...
```

Voir aussi [Surcharge de clean() d’un ModelFormSet](#model-formsets-overriding-clean).

Puis, au moment de créer les sous-formulaires groupés, passez le paramètre facultatif `formset`:

```pycon
>>> from django.forms import inlineformset_factory
>>> BookFormSet = inlineformset_factory(
...     Author, Book, fields=["title"], formset=CustomInlineFormSet
... )
>>> author = Author.objects.get(name="Mike Royko")
>>> formset = BookFormSet(instance=author)
```

### Plus d’une clé étrangère vers le même modèle

Si votre modèle contient plus d’une clé étrangère vers le même modèle, vous devrez résoudre l’ambiguïté manuellement en utilisant `fk_name`. Par exemple, considérez le modèle suivant :

```
class Friendship(models.Model):
    from_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name="from_friends",
    )
    to_friend = models.ForeignKey(
        Friend,
        on_delete=models.CASCADE,
        related_name="friends",
    )
    length_in_months = models.IntegerField()
```

Pour résoudre cela, vous pouvez définir `fk_name` dans [`inlineformset_factory()`](/fr/5.2/ref/forms/models/#django.forms.models.inlineformset_factory):

```pycon
>>> FriendshipFormSet = inlineformset_factory(
...     Friend, Friendship, fk_name="from_friend", fields=["to_friend", "length_in_months"]
... )
```

### Utilisation de sous-formulaires groupés dans une vue

Il peut être nécessaire de définir une vue permettant à un utilisateur de modifier les objets liés d’un modèle. Voici comment on peut le faire :

```
def manage_books(request, author_id):
    author = Author.objects.get(pk=author_id)
    BookInlineFormSet = inlineformset_factory(Author, Book, fields=["title"])
    if request.method == "POST":
        formset = BookInlineFormSet(request.POST, request.FILES, instance=author)
        if formset.is_valid():
            formset.save()
            # Do something. Should generally end with a redirect. For example:
            return HttpResponseRedirect(author.get_absolute_url())
    else:
        formset = BookInlineFormSet(instance=author)
    return render(request, "manage_books.html", {"formset": formset})
```

Notez comment nous définissons le paramètre `instance` aussi bien dans le cas `POST` que le cas `GET`.

### Indication des composants à utiliser dans le sous-formulaire

`inlineformset_factory` utilise `modelformset_factory` et transmet la plupart de ses paramètres à `modelformset_factory`. Cela signifie que vous pouvez utiliser le paramètre `widgets` tout comme vous le feriez pour `modelformset_factory`. Voir [Indication des composants de formulaire à utiliser avec widgets](#specifying-widgets-to-use-in-the-form-with-widgets) ci-dessus.
