# Documentation Django 5.2.8

# Clés primaires composites

New in Django 5.2.

Avec Django, chaque modèle dispose d’une clé primaire. Par défaut, la clé primaire consiste en un champ unique.

Dans la plupart des cas, une clé primaire sur un seul champ suffira. Toutefois, en conception de bases de données, la définition d’une clé primaire formée de plusieurs champs est parfois nécessaire.

Pour utiliser une clé primaire composite lors de la définition d’un modèle, définissez l’attribut `pk` à une clé `CompositePrimaryKey`:

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

    class Order(models.Model):
        reference = models.CharField(max_length=20, primary_key=True)

    class OrderLineItem(models.Model):
        pk = models.CompositePrimaryKey("product_id", "order_id")
        product = models.ForeignKey(Product, on_delete=models.CASCADE)
        order = models.ForeignKey(Order, on_delete=models.CASCADE)
        quantity = models.IntegerField()

Cela indiquera à Django de créer une clé primaire composite (`PRIMARY KEY (product_id, order_id)`) lors de la création de la table.

Une clé primaire composite est représentée par un `tuple`:

    >>> product = Product.objects.create(name="apple")
    >>> order = Order.objects.create(reference="A755H")
    >>> item = OrderLineItem.objects.create(product=product, order=order, quantity=1)
    >>> item.pk
    (1, "A755H")

Vous pouvez attribuer un `tuple` à l’attribut `pk`. Cela va définir les valeurs de champs associés :

    >>> item = OrderLineItem(pk=(2, "B142C"))
    >>> item.pk
    (2, "B142C")
    >>> item.product_id
    2
    >>> item.order_id
    "B142C"

Une clé primaire composite peut aussi être filtrée par un `tuple`:

    >>> OrderLineItem.objects.filter(pk=(1, "A755H")).count()
    1

Nous somme toujours en cours de travail pour la prise en charge des clés primaires composites pour les champs relationnels, y compris les champs `GenericForeignKey` et le site d’administration de Django. À ce stade, les modèles avec clé primaire composite ne peuvent pas être inscrits dans l’administration de Django. Nous espérons pouvoir résoudre ces questions dans de prochaines versions.

## Migration vers une clé primaire composite

Django ne prend actuellement pas en charge la migration depuis ou vers une clé primaire composite une fois qu’une table a été créée. Il ne gère pas non plus l’ajout ou la suppression de champs faisant partie de la clé primaire composite.

Si vous souhaitez faire migrer une table existante à partir d’une clé primaire unique vers une clé primaire composite, suivez les instructions de votre moteur de base de données pour le faire.

Après que la clé primaire composite est en place, ajoutez le champ `CompositePrimaryKey` à votre modèle. Cela permet à Django de reconnaître et de traiter correctement la clé primaire composite.

Même si les opérations de migration (par ex. `AddField`, `AlterField`) sur des champs de clé primaire ne sont pas prises en charge, `makemigrations` détecte tout de même les modifications.

Afin d’éviter les erreurs, il est recommandé d’appliquer de telles migrations avec `--fake`.

Une autre possibilité est d’utiliser `SeparateDatabaseAndState` pour exécuter les migrations spécifiques au moteur et les migrations générées par Django dans une seule opération.

## Les clés primaires composites et les relations

Les champs relationnels ainsi que les relations génériques ne prennent pas en charge les clés primaires composites.

Par exemple, considérant le modèle `OrderLineItem`, le code suivant n’est pas possible :

    class Foo(models.Model):
        item = models.ForeignKey(OrderLineItem, on_delete=models.CASCADE)

Car `ForeignKey` ne peut actuellement pas faire référence à des modèles avec clé primaire composite.

Pour contourner cette limite, `ForeignObject` peut être utilisé comme alternative :

    class Foo(models.Model):
        item_order_id = models.CharField(max_length=20)
        item_product_id = models.IntegerField()
        item = models.ForeignObject(
            OrderLineItem,
            on_delete=models.CASCADE,
            from_fields=("item_order_id", "item_product_id"),
            to_fields=("order_id", "product_id"),
        )

`ForeignObject` ressemble beaucoup à `ForeignKey`, sauf qu’il ne crée pas de colonne (par ex. `item_id`), de contrainte de clé étrangère ni d’index en base de données, et l’argument `on_delete` est ignoré.

Avertissement

`ForeignObject` est une API interne. Cela signifie qu’il n’est pas couvert par notre politique d’obsolescence.

## Les clés primaires composites et les fonctions de base de données

Beaucoup de fonctions de base de données n’acceptent qu’une seule expression.

    MAX("order_id")  -- OK
    MAX("product_id", "order_id")  -- ERROR

Dans ces cas, si on fournit une référence à une clé primaire composite, une erreur `ValueError` sera produite, car cette clé est composée de plusieurs colonnes. Une exception est admise pour `Count`.

    Max("order_id")  # OK
    Max("pk")  # ValueError
    Count("pk")  # OK

## Les clés primaires composites dans les formulaires

Comme une clé primaire composite est un champ virtuel, c’est-à-dire un champ qui ne représente pas une seule colonne de base de données, ce champ est exclu des formulaires `ModelForms`.

Par exemple, en prenant l’exemple de ce formulaire

    class OrderLineItemForm(forms.ModelForm):
        class Meta:
            model = OrderLineItem
            fields = "__all__"

Ce formulaire ne possède pas de champ de formulaire `pk` pour la clé primaire composite :

    >>> OrderLineItemForm()
    <OrderLineItemForm bound=False, valid=Unknown, fields=(product;order;quantity)>

Si on veut définir la clé primaire composite `pk` comme champ de formulaire, une erreur `FieldError` de champ inconnu est générée.

Les champs de clé primaire sont en lecture seule

Si vous modifiez la valeur d’une clé primaire d’un objet existant et que vous l’enregistrez, un nouvel objet est créé en parallèle à l’ancien (voir `Field.primary_key`).

Ceci se vérifie aussi pour les clés primaires composites. Ainsi, vous pouvez définir `Field.editable` à `False` pour tous les champs de clé primaire afin de les exclure des formulaires `ModelForm`.

## Les clés primaires composites dans la validation des modèles

Comme `pk` n’est qu’un champ virtuel, l’inclusion de `pk` comme nom de champ dans l’argument `exclude` de `Model.clean_fields()` n’a aucun effet. Pour exclure les champs de clé primaire composite de la validation des modèles, indiquez chacun des champs concernés individuellement. `Model.validate_unique()` peut tout de même être appelée avec `exclude={"pk"}` pour omettre les contrôles d’unicité.

## Construction d’applications compatibles avec les clés primaires composites

Avant l’introduction des clés primaires composites, le champ unique composant la clé primaire pouvait être consulté en interrogeant l’attribut `primary_key` de ses champs :

    >>> pk_field = None
    >>> for field in Product._meta.get_fields():
    ...     if field.primary_key:
    ...         pk_field = field
    ...         break
    ...
    >>> pk_field
    <django.db.models.fields.AutoField: id>

Maintenant qu’une clé primaire peut être composée de plusieurs champs, il n’est plus possible de se fier à l’attribut `primary_key` pour identifier la composition de la clé primaire, car il vaudra `False` pour maintenir la règle qu’au plus un seul champ par modèle peut avoir cet attribut à `True`:

    >>> pk_fields = []
    >>> for field in OrderLineItem._meta.get_fields():
    ...     if field.primary_key:
    ...         pk_fields.append(field)
    ...
    >>> pk_fields
    []

Si l’on veut construire du code applicatif qui sache gérer correctement les clés primaires composites, l’attribut `_meta.pk_fields` est maintenant disponible :

    >>> Product._meta.pk_fields
    [<django.db.models.fields.AutoField: id>]
    >>> OrderLineItem._meta.pk_fields
    [
        <django.db.models.fields.ForeignKey: product>,
        <django.db.models.fields.ForeignKey: order>
    ]