Sammansatta primärnycklar¶
I Django har varje modell en primärnyckel. Som standard består denna primärnyckel av ett enda fält.
I de flesta fall bör det räcka med en enda primärnyckel. Vid databasdesign är det dock ibland nödvändigt att definiera en primärnyckel som består av flera fält.
För att använda en sammansatt primärnyckel måste du när du definierar en modell ange att attributet pk ska vara en 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()
Detta kommer att instruera Django att skapa en sammansatt primärnyckel (PRIMARY KEY (product_id, order_id)) när tabellen skapas.
En sammansatt primärnyckel representeras av en tupel:
>>> 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")
Du kan tilldela en tuple till attributet pk. Detta ställer in de associerade fältvärdena:
>>> item = OrderLineItem(pk=(2, "B142C"))
>>> item.pk
(2, "B142C")
>>> item.product_id
2
>>> item.order_id
"B142C"
En sammansatt primärnyckel kan också filtreras med en tupel:
>>> OrderLineItem.objects.filter(pk=(1, "A755H")).count()
1
Vi arbetar fortfarande med stöd för sammansatta primärnycklar för relationsfält, inklusive GenericForeignKey-fält, och Django-admin. Modeller med sammansatta primärnycklar kan för närvarande inte registreras i Django-admin. Du kan förvänta dig att se detta i framtida utgåvor.
Migrera till en sammansatt primärnyckel¶
Django stöder inte migrering till eller från en sammansatt primärnyckel efter att tabellen har skapats. Det finns inte heller stöd för att lägga till eller ta bort fält från den sammansatta primärnyckeln.
Om du vill migrera en befintlig tabell från en enda primärnyckel till en sammansatt primärnyckel ska du följa instruktionerna i din databasbackend för att göra det.
När den sammansatta primärnyckeln är på plats lägger du till fältet CompositePrimaryKey i din modell. Detta gör att Django kan känna igen och hantera den sammansatta primära nyckeln på lämpligt sätt.
Även om migreringsåtgärder (t.ex. AddField, AlterField) på primärnyckelfält inte stöds, kommer makemigrations ändå att upptäcka ändringar.
För att undvika fel rekommenderas att sådana migreringar görs med --fake.
Alternativt kan SeparateDatabaseAndState användas för att utföra backend-specifika migreringar och Django-genererade migreringar i en enda operation.
Sammansatta primärnycklar och relationer¶
Relationsfält, inklusive generiska relationer har inte stöd för sammansatta primärnycklar.
Till exempel, med modellen OrderLineItem stöds inte följande:
class Foo(models.Model):
item = models.ForeignKey(OrderLineItem, on_delete=models.CASCADE)
Eftersom ForeignKey för närvarande inte kan referera till modeller med sammansatta primärnycklar.
För att komma runt denna begränsning kan ForeignObject användas som ett alternativ:
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 är ungefär som ForeignKey, förutom att det inte skapar några kolumner (t.ex. item_id), främmande nyckelbegränsningar eller index i databasen, och argumentet on_delete ignoreras.
Sammansatta primärnycklar och databasfunktioner¶
Många databasfunktioner accepterar bara ett enda uttryck.
MAX("order_id") -- OK
MAX("product_id", "order_id") -- ERROR
I dessa fall ger en sammansatt primärnyckelreferens upphov till ett ValueError, eftersom den består av flera kolumnuttryck. Undantag görs för Count.
Max("order_id") # OK
Max("pk") # ValueError
Count("pk") # OK
Sammansatta primärnycklar i formulär¶
Eftersom en sammansatt primärnyckel är ett virtuellt fält, ett fält som inte representerar en enda databaskolumn, är detta fält uteslutet från ModelForms.
Ta till exempel följande formulär:
class OrderLineItemForm(forms.ModelForm):
class Meta:
model = OrderLineItem
fields = "__all__"
Det här formuläret har inte något formulärfält pk för den sammansatta primärnyckeln:
>>> OrderLineItemForm()
<OrderLineItemForm bound=False, valid=Unknown, fields=(product;order;quantity)>
Om du anger det primära sammansatta fältet pk som ett formulärfält uppstår ett okänt fält FieldError.
Primärnyckelfält är skrivskyddade
Om du ändrar värdet på en primärnyckel i ett befintligt objekt och sedan sparar det, skapas ett nytt objekt vid sidan av det gamla (se Field.primary_key).
Detta gäller även för sammansatta primärnycklar. Därför kan det vara bra att ställa in Field.editable till False på alla primärnyckelfält för att utesluta dem från ModelForms.
Sammansatta primärnycklar i modellvalidering¶
Eftersom pk bara är ett virtuellt fält har det ingen effekt att inkludera pk som ett fältnamn i argumentet exclude i Model.clean_fields(). För att utesluta de sammansatta primärnyckelfälten från modellvalidering, ange varje fält individuellt. Model.validate_unique() kan fortfarande anropas med exclude={"pk"} för att hoppa över unikhetskontroller.
Bygga färdiga applikationer med sammansatt primärnyckel¶
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:
>>> 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:
>>> pk_fields = []
>>> for field in OrderLineItem._meta.get_fields():
... if field.primary_key:
... pk_fields.append(field)
...
>>> pk_fields
[]
För att bygga applikationskod som korrekt hanterar sammansatta primärnycklar bör attributet _meta.pk_fields användas istället:
>>> 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>
]