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_typesuper().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_nametrain_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)完整的定义,或许我能提供更精确的反向关系名称解析。