限制外键查询集后,Django更新表单无法显示当前外键值
问题描述
我创建了名为TimeTable的Django模型(代码如下),在更新表单中需要显示mon_1等外键字段的当前值。但由于通过queryset限制了外键的可选范围,当前值无法正常显示。我尝试将mon_1字段的initial值设置为当前模型实例的mon_1,但并未生效。请问我存在哪些错误?
models.py
class TimeTable(models.Model): standard_choice = ( ('Grade 1','Grade 1'), ('Grade 2','Grade 2'), ('Grade 3','Grade 3'), ('Grade 4','Grade 4'), ('Grade 5','Grade 5'), ('Grade 6','Grade 6'), ('Grade 7','Grade 7'), ('Grade 8','Grade 8'), ('Grade 9','Grade 9'), ('Grade 10','Grade 10'), ('Grade 11','Grade 11'), ('Grade 12','Grade 12'), ) section_choice = ( ('A','A'), ('B','B'), ('C','C'), ('D','D'), ) standard = models.CharField(max_length=256,choices=standard_choice) section = models.CharField(max_length=1,choices=section_choice) mon_1 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='mon_1_subject_teacher') mon_2 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='mon_2_subject_teacher') mon_3 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='mon_3_subject_teacher') mon_4 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='mon_4_subject_teacher') mon_5 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='mon_5_subject_teacher') tue_1 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='tue_1_subject_teacher') tue_2 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='tue_2_subject_teacher') tue_3 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='tue_3_subject_teacher') tue_4 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='tue_4_subject_teacher') tue_5 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='tue_5_subject_teacher') wednes_1 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='wed_1_subject_teacher') wednes_2 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='wed_2_subject_teacher') wednes_3 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='wed_3_subject_teacher') wednes_4 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='wed_4_subject_teacher') wednes_5 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='wed_5_subject_teacher') thurs_1 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='thurs_1_subject_teacher') thurs_2 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='thurs_2_subject_teacher') thurs_3 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='thurs_3_subject_teacher') thurs_4 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='thurs_4_subject_teacher') thurs_5 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='thurs_5_subject_teacher') fri_1 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='fri_1_subject_teacher') fri_2 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='fri_2_subject_teacher') fri_3 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='fri_3_subject_teacher') fri_4 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='fri_4_subject_teacher') fri_5 = models.ForeignKey(Teachers,on_delete=models.CASCADE,related_name='fri_5_subject_teacher') def __str__(self): return self.standard+' '+self.section+ ' '+ 'TimeTable' class Meta(): unique_together = [ ['standard','section'], ['standard','mon_1'], ['standard','mon_2'], ['standard','mon_3'], ['standard','mon_4'], ['standard','mon_5'], ['standard','tue_1'], ['standard','tue_2'], ['standard','tue_3'], ['standard','tue_4'], ['standard','tue_5'], ['standard','wednes_1'], ['standard','wednes_2'], ['standard','wednes_3'], ['standard','wednes_4'], ['standard','wednes_5'], ['standard','thurs_1'], ['standard','thurs_2'], ['standard','thurs_3'], ['standard','thurs_4'], ['standard','thurs_5'], ['standard','fri_1'], ['standard','fri_2'], ['standard','fri_3'], ['standard','fri_4'], ['standard','fri_5'], ]
forms.py
def limit_choices_teachers(period): return Teachers.objects.exclude(id__in=TimeTable.objects.values_list(f"{period}_id",flat=True)) class TimeTableUpdateForm(forms.ModelForm): class Meta(): model = models.TimeTable fields = ('__all__') def __init__(self, *args, **kwargs): super(TimeTableUpdateForm, self).__init__(*args, **kwargs) Instance = models.TimeTable.objects.get(pk=self.instance.pk) self.fields['mon_1'].initial = Instance.mon_1 self.fields['mon_1'].queryset = limit_choices_teachers('mon_1')
views.py
class UpdateTimeTable(UpdateView): model = models.TimeTable form_class = forms.TimeTableUpdateForm template_name = 'TimeTableApp/update_timetable.html' success_url = reverse_lazy('TimeTableApp:inner_index')
错误分析与解决方案
核心错误点
- QuerySet过滤逻辑错误:
limit_choices_teachers函数排除了所有TimeTable记录中该时段的老师,包括当前正在编辑的实例的老师。这导致下拉选项里没有当前值,即使设置了initial也无法显示(Django表单会自动忽略不在QuerySet范围内的initial值)。 - 冗余的实例查询:
self.instance已经是当前编辑的实例,无需再次调用models.TimeTable.objects.get(pk=self.instance.pk)获取。 - 字段定义语法错误:
fields = ('__all__')会被解析成字符串而非元组,正确写法应为fields = '__all__'或fields = ('__all__',)。
修正后的代码
调整limit_choices_teachers函数
新增参数排除当前实例的记录,确保当前老师保留在可选范围内:
def limit_choices_teachers(period, current_pk=None): queryset = TimeTable.objects.all() if current_pk is not None: # 排除当前正在编辑的实例,避免把当前老师也过滤掉 queryset = queryset.exclude(pk=current_pk) # 获取其他实例中已选的老师ID used_teacher_ids = queryset.values_list(f"{period}_id", flat=True) return Teachers.objects.exclude(id__in=used_teacher_ids)
修正表单的__init__方法
class TimeTableUpdateForm(forms.ModelForm): class Meta(): model = models.TimeTable fields = '__all__' # 修正字段定义语法 def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 直接使用self.instance,无需重复查询 current_pk = self.instance.pk if self.instance else None # 调整QuerySet,保留当前实例的老师 self.fields['mon_1'].queryset = limit_choices_teachers('mon_1', current_pk) # 无需手动设置initial,ModelForm会自动从instance读取字段值
额外说明
ModelForm初始化时会自动从self.instance读取字段值作为初始值,所以不需要手动设置initial参数。只要当前值在QuerySet范围内,表单就会正常显示。
内容的提问来源于stack exchange,提问作者Rahul
相关产品推荐
相关产品推荐

