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號