Sammansatta primärnycklarLink to this heading

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:

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()

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:

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")

Du kan tilldela en tuple till attributet pk. Detta ställer in de associerade fältvärdena:

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

Python console
>>> 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ärnyckelLink to this heading

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 relationerLink to this heading

Relationsfält, inklusive generiska relationer har inte stöd för sammansatta primärnycklar.

Till exempel, med modellen OrderLineItem stöds inte följande:

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

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 ä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 databasfunktionerLink to this heading

Många databasfunktioner accepterar bara ett enda uttryck.

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

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

Sammansatta primärnycklar i formulärLink to this heading

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:

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

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

Sammansatta primärnycklar i modellvalideringLink to this heading

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ärnyckelLink to this heading

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
[]

För att bygga applikationskod som korrekt hanterar sammansatta primärnycklar bör attributet _meta.pk_fields användas istället:

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