Django外键反向查询机制解析
Django中不使用 _set 语法,是因为Django在未显式定义 related_name 时,为多个指向同一模型的外键自动生成了唯一的反向名称。
🔍 原理分析:为什么你的代码可以工作
在你的 ChengWuKaoQinBiao 模型中,有两个外键指向了 JiaoLuBiao:
class ChengWuKaoQinBiao(models.Model):
"""
职工乘务交路考勤表
"""
date_start = models.DateField(verbose_name="本月结算始发日期", default=timezone.now)
date_end = models.DateField(verbose_name="本月结算终到日期", default=timezone.now)
employee_jiaolu_team = models.ForeignKey(
Employee, on_delete=models.CASCADE, verbose_name="乘务员"
)
jiaolu_team = models.ForeignKey(
Team, on_delete=models.CASCADE, verbose_name="乘务时所在班组"
)
jiaolu_zhiwu = models.ForeignKey(
ZhiWu, on_delete=models.CASCADE, verbose_name="乘务时职务"
)
# train_jiaolu = models.JSONField(verbose_name='乘务车次', default=list)
train_jiaolu = models.ForeignKey(
JiaoLuBiao, on_delete=models.CASCADE, verbose_name="乘务车次"
)
ti_cheng_employee = models.ForeignKey(
Employee,
on_delete=models.CASCADE,
verbose_name="替乘人员",
blank=True,
null=True,
related_name="ti_cheng_model",
)
calculate_cheng_wu_fei = models.BooleanField(
default=True,
verbose_name="计算乘务费",
help_text="勾选表示此乘务记录计入乘务费计算"
)
calculate_shi_ji_gua_gou = models.BooleanField(
default=True,
verbose_name="计算实际挂钩工资",
help_text="勾选表示此乘务记录计入实际挂钩工资计算"
)
ji_xiao_train = models.ForeignKey(
JiaoLuBiao,
on_delete=models.CASCADE,
verbose_name="绩效核算车次",
blank=True,
null=True,
related_name="ji_xiao_train_model",
)
ji_xiao_xi_shu = models.DecimalField(
max_digits=5, decimal_places=2, verbose_name="绩效系数", default=1.0
)
custom_jiao_lu_type = models.ForeignKey(
JiaoLuType,
on_delete=models.CASCADE,
verbose_name="自定义交路类型",
blank=True,
null=True, # 允许为空,保存时会自动设置
help_text="默认使用乘务车次的交路类型",
)
custom_xi_shu = models.BooleanField(default=False, verbose_name="是否手动修改系数")
def save(self, *args, **kwargs):
if not self.custom_xi_shu:
self.ji_xiao_xi_shu = self.jiaolu_zhiwu.ji_xiao_xi_shu
# 设置默认的交路类型(如果未设置且train_jiaolu存在)
if not self.custom_jiao_lu_type and self.train_jiaolu_id:
# 获取train_jiaolu的第一个关联交路类型
first_type = self.train_jiaolu.jiao_lu_type.first()
if first_type:
self.custom_jiao_lu_type = first_type
super().save(*args, **kwargs)
# 在保存之前可以进行数据验证或格式化
# if not isinstance(self.train_jiaolu, dict):
# raise ValueError("train_jiaolu必须是字典列表")
# super().save(*args, **kwargs)
def __str__(self):
return (
self.employee_jiaolu_team.name
+ ":"
+ self.date_start.strftime("%Y-%m-%d")
+ " 至 "
+ self.date_end.strftime("%Y-%m-%d")
+ " "
+ self.train_jiaolu.train
+ "次"
)
class Meta:
unique_together = (
("employee_jiaolu_team", "date_start", "date_end", "train_jiaolu"),
)
verbose_name = "职工乘务交路考勤表"
verbose_name_plural = verbose_name
train_jiaolu = models.ForeignKey(
JiaoLuBiao, on_delete=models.CASCADE, verbose_name="乘务车次"
)
ji_xiao_train = models.ForeignKey(
JiaoLuBiao,
on_delete=models.CASCADE,
verbose_name="绩效核算车次",
blank=True,
null=True,
related_name="ji_xiao_train_model", # 这个你显式设置了
)
这里的关键点在于:
-
Django 的默认行为:通常,如果只有一个外键指向某个模型且未设置
related_name,Django 会使用小写模型名_set作为默认的反向查询名(例如jiaolubiao_set)。 -
多外键时的自动处理:当同一个模型(这里是
ChengWuKaoQinBiao)有多个外键指向另一个模型(JiaoLuBiao)时,Django 要求这些反向关系名称必须是唯一的。如果你没有为所有外键显式设置related_name,Django 就无法使用统一的小写模型名_set,因为它无法区分你具体想查询哪个外键关系。因此,Django 会自动为你生成一个唯一的反向名称。你代码中使用的chengwukaoqinbiao正是Django为train_jiaolu这个外键字段自动生成的默认反向名称。
💡 最佳实践与建议
虽然当前代码可以运行,但为了代码的清晰性和可维护性,我强烈建议你:
-
显式设置
related_name:为所有外键关系手动指定一个清晰、明确的反向名称,避免依赖Django的自动生成规则。这能让你的代码意图更明确,也便于后续维护。例如,你可以将
train_jiaolu字段修改为: -
之后你的查询就可以使用train_jiaolu = models.ForeignKey( JiaoLuBiao, on_delete=models.CASCADE, verbose_name="乘务车次", related_name="chengwu_kaoqin_records" # 添加一个清晰的名称 )chengwu_kaoqin_records了。 -
检查实际生成的反向名称:你可以通过查看项目的迁移文件,或者使用Django shell进行检查,来确认Django最终为你的模型生成了哪些具体的反向关系名称。
总而言之,你观察到的现象是Django在处理模型多外键关系时的一种长期存在的机制,并非5.2版本的新改动。养成显式定义 related_name 的习惯,会让你的项目结构更清晰。
如果你能分享这两个模型(JiaoLuBiao 和 ChengWuKaoQinBiao)完整的定义,或许我能提供更精确的反向关系名称解析。

浙公网安备 33010602011771号