Clés primaires composites¶
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
is much like ForeignKey
, except that it doesn’t create
any columns (e.g. item_id
), foreign key constraints or indexes in the
database, and the on_delete
argument is ignored.
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>
]