Составные первичные ключи
В Django у каждой модели есть первичный ключ. По умолчанию этот первичный ключ состоит из одного поля.
В большинстве случаев одного первичного ключа достаточно. Однако при проектировании базы данных иногда необходимо определить первичный ключ, состоящий из нескольких полей.
Чтобы использовать составной первичный ключ, при определении модели задайте атрибут pk как 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()
Это укажет Django создать составной первичный ключ (PRIMARY KEY (product_id, order_id)) при создании таблицы.
Составной первичный ключ представлен объектом 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")
Вы можете присвоить tuple атрибуту pk. Это задаёт значения связанных полей:
>>> item = OrderLineItem(pk=(2, "B142C")) >>> item.pk (2, "B142C") >>> item.product_id 2 >>> item.order_id "B142C"
Также можно фильтровать по составному первичному ключу, передав tuple:
>>> OrderLineItem.objects.filter(pk=(1, "A755H")).count() 1
Мы всё ещё работаем над поддержкой составных первичных ключей для полей связей, включая поля GenericForeignKey, а также для административного интерфейса Django. В настоящее время модели с составными первичными ключами нельзя зарегистрировать в административном интерфейсе Django. Ожидайте появления этой возможности в будущих выпусках.
Переход на составной первичный ключ
Django не поддерживает переход на составной первичный ключ или отказ от него после создания таблицы. Также не поддерживается добавление или удаление полей составного первичного ключа.
Если вы хотите перейти в существующей таблице от одного первичного ключа к составному, следуйте инструкциям для вашей серверной части базы данных.
После создания составного первичного ключа добавьте в модель поле CompositePrimaryKey. Это позволит Django распознать составной первичный ключ и правильно его обрабатывать.
Операции миграции (например, AddField, AlterField) над полями первичного ключа не поддерживаются, однако makemigrations по-прежнему будет обнаруживать изменения.
Во избежание ошибок рекомендуется применять такие миграции с помощью --fake.
В качестве альтернативы можно использовать SeparateDatabaseAndState, чтобы выполнить специфичные для серверной части миграции и созданные Django миграции в рамках одной операции.
Составные первичные ключи и связи
Поля связей, включая универсальные связи, не поддерживают составные первичные ключи.
Например, для модели OrderLineItem не поддерживается следующее:
class Foo(models.Model):
item = models.ForeignKey(OrderLineItem, on_delete=models.CASCADE)
Это связано с тем, что ForeignKey в настоящее время не может ссылаться на модели с составными первичными ключами.
Чтобы обойти это ограничение, в качестве альтернативы можно использовать ForeignObject:
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 во многом похож на ForeignKey, но не создаёт в базе данных столбцов (например, item_id), ограничений внешнего ключа или индексов, а аргумент on_delete игнорируется.
Предупреждение
ForeignObject — это внутренний API. Это означает, что на него не распространяется наша политика устаревания.
Составные первичные ключи и функции базы данных
Многие функции базы данных принимают только одно выражение.
MAX("order_id") -- OK
MAX("product_id", "order_id") -- ERROR
В таких случаях передача ссылки на составной первичный ключ вызывает исключение ValueError, поскольку такой ключ состоит из нескольких выражений столбцов. Исключение сделано для Count.
Max("order_id") # OK
Max("pk") # ValueError
Count("pk") # OK
Составные первичные ключи в формах
Составной первичный ключ — виртуальное поле, то есть поле, не соответствующее одному столбцу базы данных, поэтому оно исключается из ModelForms.
Рассмотрим, например, следующую форму:
class OrderLineItemForm(forms.ModelForm):
class Meta:
model = OrderLineItem
fields = "__all__"
В этой форме нет поля формы pk для составного первичного ключа:
>>> OrderLineItemForm() <OrderLineItemForm bound=False, valid=Unknown, fields=(product;order;quantity)>
Если задать составное поле первичного ключа pk как поле формы, будет вызвана ошибка о неизвестном поле FieldError.
Поля первичного ключа доступны только для чтения
Если изменить значение первичного ключа существующего объекта и сохранить его, будет создан новый объект наряду со старым (см. Field.primary_key).
Это относится и к составным первичным ключам. Поэтому для всех полей первичного ключа можно задать Field.editable равным False, чтобы исключить их из ModelForms.
Составные первичные ключи при проверке моделей
Поскольку pk — это только виртуальное поле, указание pk в качестве имени поля в аргументе exclude метода Model.clean_fields() не даст никакого эффекта. Чтобы исключить поля составного первичного ключа из проверки модели, укажите каждое поле отдельно. Метод Model.validate_unique() по-прежнему можно вызвать с аргументом exclude={"pk"}, чтобы пропустить проверки уникальности.
Разработка приложений с поддержкой составных первичных ключей
До появления составных первичных ключей единственное поле, составляющее первичный ключ модели, можно было найти, проверив атрибут primary key у её полей:
>>> 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>
Теперь, когда первичный ключ может состоять из нескольких полей, на атрибут primary key больше нельзя полагаться при определении полей, входящих в состав первичного ключа, поскольку для них он будет иметь значение False. Это необходимо, чтобы сохранить инвариант: не более одного поля модели может иметь значение True для этого атрибута:
>>> pk_fields = [] >>> for field in OrderLineItem._meta.get_fields(): ... if field.primary_key: ... pk_fields.append(field) ... >>> pk_fields []
Чтобы создавать код приложения, корректно обрабатывающий составные первичные ключи, следует использовать атрибут _meta.pk_fields:
>>> 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>
]
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/6.0/topics/composite-primary-key/