# Documentation Django 5.2.8

# Écriture de champs de modèles personnalisés

## Introduction

La documentation de référence sur les modèles explique comment utiliser les classes de champs standard de Django : `CharField`, `DateField`, etc. Dans la plupart des cas, ces classes sont suffisantes. Cependant, il peut arriver que l’offre de Django ne réponde pas à toutes vos exigences, ou que vous vouliez utiliser un champ totalement différent de ceux mis à disposition par Django.

Les types de champs intégrés de Django ne gèrent pas tous les types possibles de colonnes de bases de données, mais seulement les plus courants comme `VARCHAR` et `INTEGER`. Pour des types de colonnes moins usités, comme les polygones géographiques ou même des types définis par les utilisateurs du style des types PostgreSQL personnalisés, vous pouvez définir vos propres sous-classes de `Field`.

D’un autre côté, vous pourriez avoir un objet Python complexe pouvant être sérialisé d’une manière ou d’une autre dans un type de colonne standard d’une base de données. C’est une autre situation où une sous-classe de `Field` peut vous aider à utiliser cet objet avec vos modèles.

### Notre exemple d’objet

Creating custom fields requires a bit of attention to detail. To make things easier to follow, we’ll use a consistent example throughout this document: wrapping a Python object representing the deal of cards in a hand of Bridge. Don’t worry, you don’t have to know how to play Bridge to follow this example. You only need to know that 52 cards are dealt out equally to four players, who are traditionally called north, east, south and west. Our class looks something like this:

    class Hand:
        """A hand of cards (bridge style)"""

        def __init__(self, north, east, south, west):
            # Input parameters are lists of cards ('Ah', '9s', etc.)
            self.north = north
            self.east = east
            self.south = south
            self.west = west

        # ... (other possibly useful methods omitted) ...

C’est une classe Python ordinaire sans spécificité Django. Nous aimerions pouvoir faire des choses telles que celles-ci dans nos modèles (en supposant que l’attribut `hand` du modèle est une instance de `Hand`) :

    example = MyModel.objects.get(pk=1)
    print(example.hand.north)

    new_hand = Hand(north, east, south, west)
    example.hand = new_hand
    example.save()

Nous écrivons et lisons dans l’attribut `hand` de notre modèle comme on le ferait avec tout autre classe Python. L’astuce est d’indiquer à Django comment enregistrer et charger un tel objet.

Pour pouvoir utiliser la classe `Hand` dans nos modèles, nous ne devons en aucune façon modifier cette classe. C’est idéal, car cela signifie qu’il est facilement possible d’implémenter la fonctionnalité « modèle » pour des classes existantes dont le code source n’est pas modifiable.

Note

Il se peut que vous vouliez uniquement profiter de types personnalisés de colonnes de bases de données et gérer les données comment des types Python standards dans vos modèles ; chaînes ou nombres à virgules, par exemple. Cette situation est semblable à notre exemple `Hand` et nous signalerons d’éventuelles différences quand elles apparaîtront.

## Contexte théorique

### Stockage de base de données

Commençons par les champs de modèles. Si on le décompose, un champ de modèle est un moyen de traiter un objet Python normal (une chaîne, une valeur booléenne, un `datetime` ou un objet plus complexe comme `Hand`) et de le convertir en un format adapté à l’utilisation avec une base de données (ce format convient également pour la sérialisation, mais nous verrons plus loin que cela se conçoit assez naturellement lorsque la partie base de données est maîtrisée).

D’une manière ou d’une autre, les champs d’un modèle doivent être transformés pour correspondre à un type de colonne d’une base de données. Des bases de données différentes fournissent différents choix de types de colonnes valables, mais la règle est toujours la même : ce sont ces types avec lesquels vous devez travailler. Tout ce que vous voulez stocker dans la base de données doit correspondre à l’un de ces types.

Normalement, soit vous écrivez un champ Django qui correspondra à un type particulier de colonne de base de données, soit il faut trouver une manière de convertir vos données, par exemple en une chaîne.

Pour notre exemple `Hand`, nous pourrions convertir les données des cartes en une chaîne de 104 caractères en concaténant toutes les cartes dans un ordre prédéterminé, par exemple d’abord toutes les cartes nord, puis toutes les cartes est, sud et ouest. Ainsi les objets `Hand` peuvent être enregistrés dans des colonnes texte ou caractères de la base de données.

### Que fait une classe de champ ?

Tous les champs Django (et lorsque nous parlons de champs dans ce document, nous parlons toujours de champs de modèles et non pas de champs de formulaires) sont des sous-classes de `django.db.models.Field`. La plupart des informations que Django conserve sur un champ sont communs à tous les champs (nom, texte d’aide, unicité, etc.). Le stockage de toutes ces informations est géré par `Field`. Nous verrons plus précisément ce que peut faire `Field` un peu plus tard ; pour l’instant, contentons-nous de savoir que tout hérite de `Field` et personnalise les éléments clés du comportement de la classe.

Il est important de réaliser qu’une classe de champ Django n’est pas ce qui est stocké dans les attributs de vos modèles. Les attributs des modèles contiennent des objets Python habituels. Les classes de champs que vous définissez dans un modèle sont en réalité stockés dans la classe `Meta` au moment de la création de la classe de modèle (il n’est pas important ici de connaître les détails précis de ce processus). Ceci est dû au fait que les classes de champs ne sont pas nécessaires tant qu’il s’agit simplement de créer et modifier des attributs. Par contre, elles fournissent les mécanismes de conversion entre les valeurs d’attributs et ce qui est effectivement stocké dans la base de données ou envoyé au sérialiseur.

Gardez ceci à l’esprit lors de la création de vos propres champs personnalisés. La sous-classe `Field` Django que vous écrivez fournit les mécanismes de conversion entre vos instances Python et les valeurs de base de données ou de sérialisation de diverses manières (il existe par exemple des différences entre le stockage d’une valeur et son utilisation dans une requête). Si tout cela semble un peu compliqué, ne vous inquiétez pas, cela deviendra plus clair dans les exemples ci-dessous. Rappelez-vous seulement que vous allez souvent créer deux classes lorsque vous voulez créer un champ personnalisé :

- La première classe constitue l’objet Python que vos utilisateurs vont manipuler. Ils l’utiliseront comme attribut de modèle, ils liront ses valeurs à destination de l’affichage, et ainsi de suite. Il s’agit là de la classe `Hand` de notre exemple.

- La seconde classe est la sous-classe de `Field`. C’est la classe qui sait comment convertir votre première classe de sa forme utile pour le stockage vers sa forme Python et vice versa.

## Écriture d’une sous-classe de champ

Lors de la planification de votre sous-classe de `Field`, réfléchissez d’abord à quelle classe `Field` existante votre nouveau champ ressemble le plus. Serait-il possible d’hériter d’un champ Django existant et d’économiser un peu de travail ? Si non, vous devrez hériter de la classe `Field` qui est le parent de toutes les autres.

L’initialisation du nouveau champ consiste à séparer tout paramètre qui est spécifique à votre champ, des paramètres standards, et de transmettre ces derniers à la méthode `__init__()` de `Field` (ou d’une autre classe parente).

Dans notre exemple, nous appellerons notre champ `HandField` (il est conseillé d’appeler votre sous-classe de `Field` sur le modèle `<QuelqueChose>Field` afin qu’on distingue rapidement qu’il s’agit d’une sous-classe de `Field`). Notre champ ne ressemble à aucun autre champ existant, nous allons donc hériter directement de `Field`:

    from django.db import models

    class HandField(models.Field):
        description = "A hand of cards (bridge style)"

        def __init__(self, *args, **kwargs):
            kwargs["max_length"] = 104
            super().__init__(*args, **kwargs)

Le champ `HandField` accepte la plupart des options standards de champs (voir liste ci-dessous), mais nous voulons nous assurer que sa longueur soit fixe, étant donné qu’il ne doit stocker que les valeurs de 52 cartes plus leur couleur ; 104 caractères au total.

Note

Beaucoup de champs de modèles Django acceptent des options qui ne sont pas exploitées. Par exemple, vous pouvez passer à la fois `editable` et `auto_now` à `django.db.models.DateField` et il ignorera le paramètre `editable` (la définition d”`auto_now` implique que``editable=False``). Aucune erreur ne survient dans ce cas.

Ce comportement simplifie les classes de champs, car elles n’ont pas besoin de vérifier les options qui ne sont pas nécessaires. Elles transmettent toutes les options à la classe parente et ne les utilisent plus. Il ne tient qu’à vous d’être plus strict sur les options acceptées, ou d’adopter le comportement plus permissif des champs actuels.

La méthode `Field.__init__()` accepte les paramètres suivants :

- `verbose_name`

- `name`

- `primary_key`

- `max_length`

- `unique`

- `blank`

- `null`

- `db_index`

- `rel`: utilisé pour les champs de liaison (comme `ForeignKey`). Uniquement pour une utilisation avancée.

- `default`

- `editable`

- `serialize`: si `False`, le champ ne sera pas sérialisé lorsque le modèle est transmis aux sérialiseurs de Django. La valeur par défaut est `True`.

- `unique_for_date`

- `unique_for_month`

- `unique_for_year`

- `choices`

- `help_text`

- `db_column`

- `db_tablespace` : seulement pour la création d’index, pour autant que le moteur gère les espaces de tables. Vous pouvez généralement ignorer cette option.

- `auto_created`: vaut `True` si le champ a été créé automatiquement, comme pour le champ `OneToOneField` utilisé dans un contexte d’héritage de modèles. Pour utilisation avancée uniquement.

Toutes les options sans explications dans la liste ci-dessus ont la même signification que pour les champs Django normaux. Consultez la documentation sur les champs pour des exemples et des détails.

### Déconstruction de champ

La contrepartie à l’écriture de la méthode `__init __()` est l’écriture de la méthode `deconstruct()`. Celle-ci est utilisée durant les :docs:`migrations de modèles </topics/migrations>` pour indiquer à Django comment prendre une instance du nouveau champ afin de le réduire en format sérialisé ; en particulier, le choix des paramètres à passer à `__init __()` pour le recréer.

Si vous n’avez pas ajouté d’options supplémentaires au champ dont vous avez hérité, il n’y a alors pas besoin d’écrire une nouvelle méthode `deconstruct()`. Si toutefois vous modifiez les paramètres passés à `__init__()` (comme nous l’avons fait avec `HandField`), il sera nécessaire de compléter les valeurs transmises.

`deconstruct()` renvoie un tuple de quatre éléments : le nom d’attribut du champ, le chemin d’importation complet de la classe du champ, les paramètres positionnels (sous forme de liste), et les paramètres nommés (sous forme de dictionnaire). Notez la différence avec la méthode `deconstruct()` des classes personnalisées qui renvoie un tuple de trois éléments.

En tant qu’auteur de champ personnalisé, vous n’avez pas besoin de vous soucier des deux premières valeurs ; la classe de base `Field` contient tout le code pour déterminer le nom d’attribut et le chemin d’importation du champ. Vous devez cependant vous préoccuper des paramètres positionnels et nommés, puisque ceux-ci font généralement partie des éléments que vous modifiez.

Par exemple, dans notre classe `HandField`, nous forçons toujours l’utilisation de `max_length` dans `__init__()`. La méthode `deconstruct()` de la classe de base `Field` le verra et essayera de le renvoyer dans les paramètres nommés ; ainsi, nous pouvons le supprimer des paramètres nommés pour une meilleure lisibilité :

    from django.db import models

    class HandField(models.Field):
        def __init__(self, *args, **kwargs):
            kwargs["max_length"] = 104
            super().__init__(*args, **kwargs)

        def deconstruct(self):
            name, path, args, kwargs = super().deconstruct()
            del kwargs["max_length"]
            return name, path, args, kwargs

Si vous ajoutez un nouveau paramètre nommé, vous devez écrire vous-même du code dans `deconstruct()` qui place sa valeur dans `kwargs`. Vous devriez aussi omettre la valeur de `kwargs` lorsqu’il n’est pas nécessaire de reconstruire l’état du champ, comme par exemple lorsque la valeur par défaut est utilisée

    from django.db import models

    class CommaSepField(models.Field):
        "Implements comma-separated storage of lists"

        def __init__(self, separator=",", *args, **kwargs):
            self.separator = separator
            super().__init__(*args, **kwargs)

        def deconstruct(self):
            name, path, args, kwargs = super().deconstruct()
            # Only include kwarg if it's not the default
            if self.separator != ",":
                kwargs["separator"] = self.separator
            return name, path, args, kwargs

Des exemples plus complexes sont au-delà de la portée de ce document, mais rappelez-vous – pour toute configuration de votre instance de champ, `deconstruct()` doit renvoyer les paramètres que vous pouvez passer à `__init__` afin de reconstruire cet état.

Portez une attention toute particulière si vous définissez de nouvelles valeurs par défaut pour les paramètres de la classe parente `Field`; vous devez vous assurer qu’elles sont toujours incluses, sinon elles pourraient disparaître si elles prennent les anciennes valeurs par défaut.

En outre, essayez d’éviter de renvoyer les valeurs en tant que paramètres positionnels ; autant que possible, renvoyez les valeurs en tant que paramètres nommés pour une compatibilité future maximale. Si vous changez plutôt le nom que la position des éléments dans la liste des paramètres du constructeur, les paramètres positionnels sont préférables, mais gardez à l’esprit que les gens reconstruiront votre champ à partir de la version sérialisée pendant un certain temps (voire des années), selon la durée de vie de vos migrations.

Vous pouvez voir les résultats de la déconstruction en regardant les migrations qui incluent ledit champ, et vous pouvez tester la déconstruction dans les tests unitaires en déconstruisant et reconstruisant le champ :

    name, path, args, kwargs = my_field_instance.deconstruct()
    new_instance = MyField(*args, **kwargs)
    self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)

### Les attributs de champ n’affectant pas les définitions de colonnes de bases de données

Vous pouvez surcharger `Field.non_db_attrs` pour personnaliser les attributs d’un champ qui n’affectent pas la définition de sa colonne de base données. Cet attribut est utilisé lors des migrations de modèles pour détecter les opérations `AlterField` qui n’affectent pas la base de données.

Par exemple :

    class CommaSepField(models.Field):
        @property
        def non_db_attrs(self):
            return super().non_db_attrs + ("separator",)

### Modification de la classe de base d’un champ personnalisé

Il n’est pas possible de modifier la classe de base d’un champ personnalisé, car Django ne détectera pas le changement et ne créera pas de migration. Par exemple, si vous commencez avec :

    class CustomCharField(models.CharField): ...

puis que vous décidez de vous baser plutôt sur `TextField`, vous ne pouvez pas simplement modifier la sous-classe comme ceci :

    class CustomCharField(models.TextField): ...

Vous devez plutôt créer une nouvelle classe de champ personnalisé et mettre à jour vos modèles pour référencer cette nouvelle classe :

    class CustomCharField(models.CharField): ...

    class CustomTextField(models.TextField): ...

Comme discuté dans suppression de champs, vous devez conserver la classe `CustomCharField` originale tant que vous conservez des migrations qui la référencent.

### Documentation du champ personnalisé

Comme toujours, il est souhaitable de documenter votre type de champ, afin que les utilisateurs sachent de quoi il s’agit. En plus de lui fournir une chaîne « docstring » utile pour les développeurs, vous pouvez également permettre aux utilisateurs de l’application d’administration de voir une courte description du type de champ via l’application django.contrib.admindocs. Pour ce faire, fournissez un texte descriptif dans l’attribut de classe `description` du champ personnalisé. Dans l’exemple ci-dessus, la description affichée par l’application `admindocs` pour un `HandField` sera « A hand of cards (bridge style) ».

Dans l’affichage `django.contrib.admindocs`, la description du champ est interpolée avec `field.__dict__`, ce qui permet à la description de contenir des paramètres du champ. Par exemple, la description de `CharField` est :

    description = _("String (up to %(max_length)s)")

### Méthodes utiles

Après avoir créé la sous-classe de `Field`, il peut être utile de surcharger quelques méthodes standards, en fonction du comportement du champ. La liste de méthodes ci-dessous est plus ou moins dans l’ordre décroissant d’importance, il faut donc commencer par les premières.

#### Types de base de données personnalisés

Disons que vous avez créé un type personnalisé PostgreSQL appelé `mytype`. Vous pouvez dériver `Field` et implémenter la méthode `db_type()`, comme suit :

    from django.db import models

    class MytypeField(models.Field):
        def db_type(self, connection):
            return "mytype"

Après avoir créé `MytypeField`, il est possible de l’utiliser dans n’importe quel modèle, comme pour tout autre type `Field`:

    class Person(models.Model):
        name = models.CharField(max_length=80)
        something_else = MytypeField()

Si votre objectif est de créer une application indépendante du choix de la base de données, vous devriez tenir compte des différences de types de colonne de base de données. Par exemple, le type de colonne date/heure dans PostgreSQL est appelé `timestamp` alors que la même colonne dans MySQL est appelée `datetime`. Vous pouvez gérer cela dans une méthode `db_type()` en vous basant sur l’attribut `connection.vendor`. Les noms actuels des fournisseurs intégrés sont : `sqlite`, `postgresql`, `mysql` et `oracle`.

Par exemple :

    class MyDateField(models.Field):
        def db_type(self, connection):
            if connection.vendor == "mysql":
                return "datetime"
            else:
                return "timestamp"

Les méthodes `db_type()` et `rel_db_type()` sont appelées par Django lorsque le système construit les instructions `CREATE TABLE` pour votre application – c’est-à-dire lors de la création initiale des tables. Elles sont aussi appelées lors de la construction d’une clause `WHERE` qui comprend le champ de modèle – c’est-à-dire lorsque vous récupérez des données à l’aide de méthodes de QuerySet telles que `get()`, `filter()` ou `exclude()` et que le champ de modèle se trouve en paramètre.

Some database column types accept parameters, such as `CHAR(25)`, where the parameter `25` represents the maximum column length. In cases like these, it’s more flexible if the parameter is specified in the model rather than being hardcoded in the `db_type()` method. For example, it wouldn’t make much sense to have a `CharMaxlength25Field`, shown here:

    # This is a silly example of hardcoded parameters.
    class CharMaxlength25Field(models.Field):
        def db_type(self, connection):
            return "char(25)"

    # In the model:
    class MyModel(models.Model):
        # ...
        my_field = CharMaxlength25Field()

La meilleure manière de résoudre ce problème serait d’autoriser le paramètre à être défini au moment de l’exécution, c’est-à-dire quand la classe est instanciée. Pour faire cela, implémentez la méthode `Field.__init__()` comme ceci :

    # This is a much more flexible example.
    class BetterCharField(models.Field):
        def __init__(self, max_length, *args, **kwargs):
            self.max_length = max_length
            super().__init__(*args, **kwargs)

        def db_type(self, connection):
            return "char(%s)" % self.max_length

    # In the model:
    class MyModel(models.Model):
        # ...
        my_field = BetterCharField(25)

Pour terminer, si votre colonne exige une configuration SQL vraiment complexe, renvoyez `None` depuis `db_type()`. Cela aura pour conséquence que le code de création SQL de Django va omettre ce champ. Vous êtes alors responsable de créer la colonne dans la bonne table d’une autre manière, mais au moins cela vous permet d’indiquer à Django de ne pas intervenir dans ce processus.

La méthode `rel_db_type()` est appelée par des champs tels que `ForeignKey` et `OneToOneField` qui pointent vers un autre champ pour déterminer leur type de colonne de base de données. Par exemple, si vous avez un `UnsignedAutoField`, vous avez aussi besoin que les clés étrangères qui pointent vers ce champ utilisent le même type de données :

    # MySQL unsigned integer (range 0 to 4294967295).
    class UnsignedAutoField(models.AutoField):
        def db_type(self, connection):
            return "integer UNSIGNED AUTO_INCREMENT"

        def rel_db_type(self, connection):
            return "integer UNSIGNED"

#### Conversion de valeurs en objets Python

Si votre classe `Field` personnalisée manipule des structures de données plus complexes que des chaînes, des dates, des entiers ou des nombres à virgule, il peut être nécessaire de surcharger `from_db_value()` et `to_python()`.

Si elle est présente dans la sous-classe de champ, `from_db_value()` sera appelée dans tous les cas où des données sont chargées à partir de la base de données, y compris dans les appels d’agrégation et les appels `values()`.

`to_python()` est appelée par la désérialisation et dans le cadre de la méthode `clean()` utilisée depuis les formulaires.

En règle générale, `to_python()` devrait gérer de manière élégante tous les paramètres suivants :

- Une instance ayant le type correct (par exemple `Hand` dans le cas modèle de ce document).

- Une chaîne

- `None` (si le champ autorise `null=True`)

Dans notre classe `HandField`, nous stockons les données avec un champ `VARCHAR` dans la base de données, il faut donc pouvoir gérer des chaînes de caractères et la valeur `None` dans `from_db_value()`. Dans``to_python()``, il est aussi nécessaire de gérer les instances de `Hand`:

    import re

    from django.core.exceptions import ValidationError
    from django.db import models
    from django.utils.translation import gettext_lazy as _

    def parse_hand(hand_string):
        """Takes a string of cards and splits into a full hand."""
        p1 = re.compile(".{26}")
        p2 = re.compile("..")
        args = [p2.findall(x) for x in p1.findall(hand_string)]
        if len(args) != 4:
            raise ValidationError(_("Invalid input for a Hand instance"))
        return Hand(*args)

    class HandField(models.Field):
        # ...

        def from_db_value(self, value, expression, connection):
            if value is None:
                return value
            return parse_hand(value)

        def to_python(self, value):
            if isinstance(value, Hand):
                return value

            if value is None:
                return value

            return parse_hand(value)

Remarquez que ces méthodes renvoient toujours une instance `Hand`. C’est le type d’objet Python que nous voulons stocker dans l’attribut du modèle.

Pour `to_python()`, si quoi que ce soit se passe mal durant la conversion de la valeur, il faut lever une exception de type `ValidationError`.

#### Conversion d’objets Python en valeurs de requête

Comme l’utilisation d’une base de données nécessite une conversion dans les deux sens, si vous surchargez `from_db_value()` vous devrez aussi surcharger `get_prep_value()` pour convertir les objets Python vers des valeurs de requête.

Par exemple :

    class HandField(models.Field):
        # ...

        def get_prep_value(self, value):
            return "".join(
                ["".join(l) for l in (value.north, value.east, value.south, value.west)]
            )

Avertissement

Si votre champ personnalisé utilise les types `CHAR`, `VARCHAR` ou `TEXT` de MySQL, vous devez garantir que la méthode `get_prep_value()` renvoie toujours un type chaîne. MySQL effectue des correspondances souples mais imprévisibles lorsque des requêtes sur ces types se font avec un nombre entier, ce qui peut faire que les résultats contiendront des objets non prévus. Ce problème ne peut pas se produire si vous renvoyez systématiquement des chaînes dans `get_prep_value()`.

#### Conversion de valeurs de requête en valeurs de base de données

Certains types de données (par exemple les dates) doivent être dans un format spécifique avant de pouvoir être utilisées par un moteur de base de données. `get_db_prep_value()` est la méthode où ces conversions doivent être réalisées. La connexion spécifique qui sera utilisée pour la requête est transmise en tant que paramètre `connection`. Cela vous permet d’utiliser la logique de conversion spécifique au moteur si cela est nécessaire.

Par exemple, Django utilise la méthode suivante pour son champ `BinaryField`:

    def get_db_prep_value(self, value, connection, prepared=False):
        value = super().get_db_prep_value(value, connection, prepared)
        if value is not None:
            return connection.Database.Binary(value)
        return value

Dans le cas où votre champ personnalisé a besoin d’une conversion spéciale lors de son enregistrement qui n’est pas la même que la conversion utilisée pour les paramètres normaux dans une requête, vous pouvez surcharger `get_db_prep_save()`.

#### Pré-traitement des valeurs avant enregistrement

Si vous souhaitez pré-traiter la valeur juste avant l’enregistrement, vous pouvez utiliser `pre_save()`. Par exemple, le champ `DateTimeField` de Django utilise cette méthode pour définir correctement l’attribut dans le cas de `auto_now` ou `auto_now_add`.

Si vous surchargez cette méthode, vous devez renvoyez la valeur de l’attribut à la fin. Vous devriez aussi mettre à jour l’attribut du modèle si vous modifiez la valeur, afin que tout code ayant des références sur le modèle voie toujours la valeur correcte.

#### Sélection du champ de formulaire pour un champ de modèle

Pour personnaliser le champ de formulaire utilisé par `ModelForm`, vous pouvez surcharger `formfield()`.

La classe de champ de formulaire peut être indiquée via les paramètres `form_class` et `choices_form_class` ; ce dernier est utilisé si le champ contient une liste de choix, sinon c’est `form_class` qui est pris en compte. Si ces paramètres ne sont pas présents, c’est `CharField` ou `TypedChoiceField` qui seront utilisés.

Tout le contenu du dictionnaire `kwargs` est directement transmis à la méthode `__init__()` du champ de formulaire. En principe, tout ce qu’il y a à faire est de définir une bonne valeur par défaut du paramètre `form_class` (ou `choices_form_class`) puis de déléguer le reste à la classe parente. Il est possible que vous deviez écrire un champ de formulaire personnalisé (et même un composant de formulaire). Consultez la documentation sur les formulaires pour plus de détails sur ce sujet.

Si vous souhaitez exclure le champ de `ModelForm`, vous pouvez surcharger la méthode `formfield()` pour qu’elle renvoie `None`.

Poursuivant notre exemple en cours, on peut écrire la méthode `formfield()` ainsi :

    class HandField(models.Field):
        # ...

        def formfield(self, **kwargs):
            # Exclude the field from the ModelForm when some condition is met.
            some_condition = kwargs.get("some_condition", False)
            if some_condition:
                return None

            # Set up some defaults while letting the caller override them.
            defaults = {"form_class": MyFormField}
            defaults.update(kwargs)
            return super().formfield(**defaults)

Nous partons du principe que la classe de champ `MyFormField` a été importée (elle possède son propre composant par défaut). Ce document ne traite pas en détails de l’écriture de champs de formulaires personnalisés.

#### Émulation de types de champs intégrés

Si vous avez créé une méthode `db_type()`, pas besoin de vous préoccuper de `get_internal_type()`, elle ne sera que peu utilisée. Il peut cependant arriver que le stockage de votre base de données a un même type qu’un autre champ, vous pouvez donc réutiliser la logique de ce dernier pour créer la bonne colonne.

Par exemple :

    class HandField(models.Field):
        # ...

        def get_internal_type(self):
            return "CharField"

Quel que soit le moteur de base de données utilisé, cela signifie que `migrate` et les autres commandes SQL créeront le bon type de colonne pour le stockage d’une chaîne.

Si `get_internal_type()` renvoie une chaîne qui n’est pas connu de Django pour le moteur de base de données que vous utilisez – autrement dit, qu’il n’apparaît pas dans `django.db.backends.<db_name>.base.DatabaseWrapper.data_types` – la chaîne sera quand même utilisée par le sérialiseur, mais la méthode par défaut `db_type()` retournera `None`. Consultez la documentation de `db_type()` pour les raisons pour lesquelles cela pourrait être utile. Mettre une chaîne descriptive dans le type du champ pour la sérialisation est une idée utile si vous devez un jour utiliser la sortie de sérialisation à un autre endroit, en dehors de Django.

#### Conversion des données de champs pour la sérialisation

Pour personnaliser la façon dont les valeurs sont sérialisés par un sérialiseur, vous pouvez surcharger `value_to_string()`. L’appel à `value_from_object()` est le meilleur moyen d’obtenir la valeur du champ avant sa sérialisation. Par exemple, comme par ailleurs le champ `HandField` utilise des chaînes pour le stockage de ses données, nous pouvons réutiliser du code existant de conversion :

    class HandField(models.Field):
        # ...

        def value_to_string(self, obj):
            value = self.value_from_object(obj)
            return self.get_prep_value(value)

### Quelques conseils généraux

L’écriture d’un champ personnalisé peut être un processus fastidieux, particulièrement si vous procédez à des conversions complexes entre vos types Python et les formats de base de données et de sérialisation. Voici quelques astuces qui faciliteront ces opérations :

1. Observez les champs Django existants (dans django/db/models/fields/__init__.py) comme source d’inspiration. Cherchez un champ qui ressemble à ce que vous voulez faire et complétez ce qui manque, au lieu de créer un tout nouveau champ à partir de zéro.

2. Écrivez une méthode `__str__()` pour la classe de champ que vous rédigez. À beaucoup d’endroits, le comportement par défaut du code du champ est d’appeler `str()` sur la valeur (dans les exemples de ce document, `value` serait une instance `Hand`, pas `HandField`). Ainsi, si votre méthode `__str__()` convertit automatiquement l’objet Python sous sa forme textuelle, vous économiserez beaucoup de travail.

## Écriture d’une sous-classe

En plus des méthodes ci-dessus, les champs qui manipulent des fichiers doivent répondre à quelques exigences supplémentaires dont il faut tenir compte. La plupart des mécanismes fournis par `FileField`, comme la maîtrise du stockage en base de données et de la récupération des données, peuvent rester tels quels ; il reste donc aux sous-classes la tâche pas toujours triviale de s’occuper de la prise en charge d’un type particulier de fichier.

Django propose une classe `File` utilisée comme intermédiaire et chargée de s’occuper des contenus des fichiers et de leurs opérations. Elle peut être la base de sous-classes qui peuvent personnaliser l’accès au fichier et les méthodes disponibles. Elle se trouve dans `django.db.models.fields.files` et son comportement par défaut est expliqué dans la documentation sur les fichiers.

Quand une sous-classe de `File` a été créée, il s’agit d’indiquer à la nouvelle sous-classe de `FileField` qu’elle doit l’utiliser. Pour cela, définissez la nouvelle sous-classe de `File` dans l’attribut spécial `attr_class` de la sous-classe de `FileField`.

### Quelques suggestions

En plus des détails ci-dessus, voici quelques lignes de conduite qui peuvent grandement améliorer l’efficacité et la lisibilité du code du champ.

1. Le code source du champ `ImageField` de Django (dans django/db/models/fields/files.py) est un très bon exemple de la manière dont une sous-classe de `FileField` peut prendre en charge un type particulier de fichier, car elle tient compte de toutes les techniques décrites ci-dessus.

2. Mettez si possible en cache les attributs de fichier. Comme les fichiers peuvent être stockés sur des systèmes distants, leur accès peut impliquer des coûts de temps ou même financiers, ce qui n’est pas toujours nécessaire. Dès qu’un fichier est récupéré pour obtenir certaines informations sur son contenu, mettez le plus possible de ces données en cache pour réduire le nombre d’accès au fichier qui pourraient se produire lors d’éventuelles demandes futures pour ces mêmes informations.