Clés primaires compositesLien vers cette rubrique

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:

Code
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:

Python console
>>> 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 :

Python console
>>> 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:

Python console
>>> 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 compositeLien vers cette rubrique

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 relationsLien vers cette rubrique

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 :

Code
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 :

Code
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é.

Les clés primaires composites et les fonctions de base de donnéesLien vers cette rubrique

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

SQL
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.

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

Les clés primaires composites dans les formulairesLien vers cette rubrique

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

Code
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 :

Python console
>>> 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 clés primaires composites dans la validation des modèlesLien vers cette rubrique

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 compositesLien vers cette rubrique

Prior to the introduction of composite primary keys, the single field composing the primary key of a model could be retrieved by introspecting the primary_key attribute of its fields:

Python console
>>> 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>

Now that a primary key can be composed of multiple fields the primary_key attribute can no longer be relied upon to identify members of the primary key as it will be set to False to maintain the invariant that at most one field per model will have this attribute set to True:

Python console
>>> 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 :

Python console
>>> 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>
]