Django 模型如何定义 CompositePrimaryKey 复合主键并处理关联
【免费下载链接】djangoThe Web framework for perfectionists with deadlines.项目地址: https://gitcode.com/GitHub_Trending/dj/django
当数据库设计无法用单个字段做主键时(例如“订单明细”需要同时由产品 ID 和订单号唯一确定一行),在 Django 中可以通过CompositePrimaryKey为模型定义由多个字段组成的复合主键。该特性在 Django 5.2 中引入(见 5.2 版本说明),在 Django 6.0 中又补齐了QuerySet.raw支持以及对复合主键结果的子查询 lookup 支持(见 6.0 版本说明)。本文覆盖两个连续任务:在模型上正确声明复合主键,以及处理其他模型指向该模型的关联——因为关系字段目前不支持引用复合主键模型,需要用ForeignObject作为替代路径。
在模型上声明 CompositePrimaryKey
CompositePrimaryKey是一个虚拟字段,用于定义复合主键。它必须被定义为模型的pk属性,否则检查框架会报告fields.E013:CompositePrimaryKeymust be namedpk(见 checks 文档)。参数*field_names是组成主键的字段名的位置参数列表,Django 建表时会创建对应的复合主键(如PRIMARY KEY (product_id, order_id))。字段定义参考 fields 文档:
from django.db import models 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()这段示例直接来自 复合主键主题文档。注意pk参数中写的是"product_id"、"order_id",即两个ForeignKey在数据库中实际产生的列名,而不是外键字段的属性名。
复合主键的运行表现
声明后,实例的pk是一个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在应用代码中识别主键成员时,不要依赖字段的primary_key属性:复合主键模型的各成员字段该属性均为False(为了保持“一个模型至多一个字段primary_key=True”的不变量)。应改用Options.pk_fields属性(见 meta 文档):
>>> 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> ]上面的输出同样是文档示例,展示单字段模型与复合主键模型返回值的差异。
处理指向复合主键模型的关联
关系字段(包括ForeignKey和泛型关系GenericForeignKey)目前不支持引用复合主键模型。对OrderLineItem写下面这种ForeignKey是不支持的,检查框架会报告fields.E347:Field defines a relation involving model which has aCompositePrimaryKeyand such relations are not supported:
# 不支持:ForeignKey 无法引用复合主键模型 class Foo(models.Model): item = models.ForeignKey(OrderLineItem, on_delete=models.CASCADE)文档给出的替代路径是ForeignObject:自己声明指向复合主键各列的字段,再用from_fields/to_fields建立关联:
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参数会被忽略。也就是说引用完整性由你自己声明的字段承载,而不是数据库约束。
已建表模型的迁移限制
Django 不支持在表创建之后通过迁移切换到(或从)复合主键,也不支持向复合主键中增删字段。如果已有单主键表需要改为复合主键,按对应数据库后端的说明在数据库层面完成;之后在模型上补加CompositePrimaryKey,让 Django 能识别并正确处理该主键。
对于主键字段上的迁移操作(如AddField、AlterField),Django 不支持执行,但makemigrations仍会检测到这些变更。为避免报错,文档建议用--fake应用这类迁移;或者使用SeparateDatabaseAndState,在单个操作中同时执行后端相关迁移和 Django 生成的状态迁移。
此外,检查框架的models.E048会拦截constraints/indexes/unique_together引用CompositePrimaryKey字段名的写法(CompositePrimaryKeys are not supported for that option),声明相关约束时要避开这种写法。
数据库函数、表单与校验中的边界
这几处行为直接来自文档,遇到报错时可对照判断:
- 数据库函数:多数聚合只接受单个表达式,
Max("pk")这类对复合主键的引用会抛ValueError,因为pk由多个列表达式组成;Count是例外。文档示例:Max("order_id")合法,Max("pk")抛ValueError,Count("pk")合法。 - ModelForms:复合主键是虚拟字段(不对应单个数据库列),因此
fields = "__all__"的 ModelForm 不包含pk表单字段;把pk显式写成表单字段会抛FieldError(unknown field)。由于修改已存在对象的主键再保存会新建对象(复合主键同样如此),文档建议对所有主键字段设置editable = False,将其排除出 ModelForm。 - 模型校验:
pk只是虚拟字段,在clean_fields的exclude参数里写pk不生效;要跳过复合主键各字段的校验需逐字段列出。validate_unique则可以直接用exclude={"pk"}跳过唯一性检查。 - Django admin:复合主键模型目前不能注册到 admin。
- 查询增强(Django 6.0 起):
QuerySet.raw支持复合主键模型;返回复合主键的子查询可以用作__in以外的 lookup(如__exact)目标。
验证定义是否正确
完成声明后可以按顺序核对:
- 运行 Django 检查框架(
manage.py check)。若CompositePrimaryKey没有命名为pk,会看到fields.E013;若有关系字段指向复合主键模型,会看到fields.E347(错误码含义见 checks 文档)。 - 在 shell 中创建对象并查看
item.pk,确认返回由各成员列值组成的tuple,与上文示例结果形态一致。 - 用
Model._meta.pk_fields确认返回的字段列表包含复合主键引用的所有字段。 - 执行
objects.filter(pk=(...))的tuple过滤,确认命中预期行。
目前这一特性的已知边界是:关系字段(含泛型关系)与 Django admin 的支持仍在完善中,文档明确提示这些能力预计在后续版本提供;引用复合主键模型时请固定使用ForeignObject路径,而不是等待ForeignKey支持。
【免费下载链接】djangoThe Web framework for perfectionists with deadlines.项目地址: https://gitcode.com/GitHub_Trending/dj/django
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考