Составные первичные ключи
В 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.IntegerField()
item_product_id = models.CharField(max_length=20)
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), ограничений внешних ключей или индексов в базе данных.
Предупреждение
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/5.2/topics/composite-primary-key/