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.
Varning
ForeignObject
är ett internt API. Detta innebär att det inte omfattas av vår :ref:deprecation policy <internal-release-deprecation-policy>
.
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 :ref:modellvalidering <validating-objects>
, 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¶
Före införandet av sammansatta primärnycklar kunde det enskilda fält som utgjorde primärnyckeln för en modell hämtas genom introspektion av attributet primary key
för dess fält:
>>> 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>
Nu när en primärnyckel kan bestå av flera fält kan attributet primary key
inte längre användas för att identifiera medlemmar av primärnyckeln eftersom det kommer att sättas till False
för att upprätthålla invariansen att högst ett fält per modell kommer att ha detta attribut satt till 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>
]