在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
我们仍在研究复合主要密钥支持 relational fields ,包括 GenericForeignKey
字段和Django管理员。目前无法在Django管理员中注册具有复合公钥的模型。您可以在未来的版本中看到这一点。
创建表后,Django不支持迁移到或从复合公钥迁移。它也不支持从复合公钥中添加或删除字段。
如果您想将现有表从单个公钥迁移到复合公钥,请按照数据库后台的说明进行操作。
复合公钥到位后,添加 CompositePrimaryKey
字段到您的模型。这使得Django能够正确识别和处理复合公钥。
而迁移操作(例如 AddField
, AlterField
)不支持在主要关键字段上, makemigrations
仍然会检测到变化。
为了避免错误,建议将此类迁移与 --fake
.
或者, SeparateDatabaseAndState
可用于在单个操作中执行特定于后台的迁移和Django生成的迁移。
Relationship fields ,包括 generic relations 不支持复合公钥。
例如,给定 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。这意味着它不属于我们的范围 deprecation policy .
许多数据库函数只接受单个表达。
MAX("order_id") -- OK
MAX("product_id", "order_id") -- ERROR
在这些情况下,提供复合主要关键字引用会引发 ValueError
,因为它由多个列表达式组成。但有一个例外, Count
.
Max("order_id") # OK
Max("pk") # ValueError
Count("pk") # OK
由于复合公钥是一个虚拟字段,该字段不代表单个数据库列,因此该字段被排除在模型表单中。
例如,采用以下形式::
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
将它们从模型表单中排除。
以来 pk
只是一个虚拟领域,包括 pk
作为字段名称 exclude
论点 Model.clean_fields()
没有效果。将复合主关键字字段排除在 model validation ,单独指定每个字段。 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>
]
5月 28, 2025