Django 模型如何定义 CompositePrimaryKey 复合主键并处理关联
2026/9/13 14:30:07 网站建设 项目流程

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.E013CompositePrimaryKeymust 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"), )

使用这条路径前要知道ForeignObjectForeignKey的三个差异(均来自 主题文档):它不会在数据库创建额外列(如item_id)、外键约束或索引;on_delete参数会被忽略。也就是说引用完整性由你自己声明的字段承载,而不是数据库约束。

已建表模型的迁移限制

Django 不支持在表创建之后通过迁移切换到(或从)复合主键,也不支持向复合主键中增删字段。如果已有单主键表需要改为复合主键,按对应数据库后端的说明在数据库层面完成;之后在模型上补加CompositePrimaryKey,让 Django 能识别并正确处理该主键。

对于主键字段上的迁移操作(如AddFieldAlterField),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")ValueErrorCount("pk")合法。
  • ModelForms:复合主键是虚拟字段(不对应单个数据库列),因此fields = "__all__"的 ModelForm 不包含pk表单字段;把pk显式写成表单字段会抛FieldError(unknown field)。由于修改已存在对象的主键再保存会新建对象(复合主键同样如此),文档建议对所有主键字段设置editable = False,将其排除出 ModelForm。
  • 模型校验pk只是虚拟字段,在clean_fieldsexclude参数里写pk不生效;要跳过复合主键各字段的校验需逐字段列出。validate_unique则可以直接用exclude={"pk"}跳过唯一性检查。
  • Django admin:复合主键模型目前不能注册到 admin。
  • 查询增强(Django 6.0 起)QuerySet.raw支持复合主键模型;返回复合主键的子查询可以用作__in以外的 lookup(如__exact)目标。

验证定义是否正确

完成声明后可以按顺序核对:

  1. 运行 Django 检查框架(manage.py check)。若CompositePrimaryKey没有命名为pk,会看到fields.E013;若有关系字段指向复合主键模型,会看到fields.E347(错误码含义见 checks 文档)。
  2. 在 shell 中创建对象并查看item.pk,确认返回由各成员列值组成的tuple,与上文示例结果形态一致。
  3. Model._meta.pk_fields确认返回的字段列表包含复合主键引用的所有字段。
  4. 执行objects.filter(pk=(...))tuple过滤,确认命中预期行。

目前这一特性的已知边界是:关系字段(含泛型关系)与 Django admin 的支持仍在完善中,文档明确提示这些能力预计在后续版本提供;引用复合主键模型时请固定使用ForeignObject路径,而不是等待ForeignKey支持。

【免费下载链接】djangoThe Web framework for perfectionists with deadlines.项目地址: https://gitcode.com/GitHub_Trending/dj/django

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询