Django ModelForm更新时未显示存储选中项及to_field_name配置问题
问题描述
我有一个常规的ModelForm,其中occupation字段是关联选项列表的ForeignKey。在更新场景下,通过Crispy渲染的表单未将存储值标记为选中项(即<select><option...>中的选中状态),而是显示空选项,但初始值已指向数据库记录的ID。
此外,我希望将occupation.to_field_name改为occupation.code(一个CharField),已在表单的__init__方法中尝试调整,虽能转换选项值格式,但仍无法让存储值显示为选中状态。请问该如何实现?是否需要通过JS处理,还是我的表单设置有误?
提供的代码
表单基础定义
class Meta: model = TutorOptionalServiceAndDemographics fields = ['highest_degree', 'country_of_birth', 'immigrant', 'us_citizen', 'employment_status_at_enrollment', 'occupation', 'employer_name', 'referral', ] labels = { 'highest_degree': 'Highest degree/diploma earned', 'us_citizen': 'US Citizen:', 'immigrant': 'Immigrant:', 'country_of_birth': 'Country of Birth:', 'employment_status_at_enrollment': 'Employment status at time of registration:', 'occupation': 'Occupation:', 'employer_name': 'Employer Name:', 'referral': 'Referred by or heard about the program from source:', } help_texts = { 'employment_status_at_enrollment': '', 'referral': 'Select all that apply', } yes_no = [('True', 'Yes'), ('False', 'No')] widgets = { 'immigrant': HorizontalRadioSelect(choices=yes_no), 'us_citizen': HorizontalRadioSelect(choices=yes_no), } def __init__(self, *args, **kwargs): super(TutorOptionalForm, self).__init__(*args, **kwargs) # futzing goes here self.helper = FormHelper(self) self.helper.attrs['autocomplete'] = 'off' self.helper.form_id = 'id-gather-name' self.helper.form_class = 'form-horizontal' self.helper.form_method = 'post' # self.helper.form_action = '' self.helper.label_class = 'col-5' self.helper.field_class = 'col-6' self.helper.form_tag = False self.helper.layout = Layout( 'highest_degree', 'country_of_birth', InlineRadios('immigrant'), InlineRadios('us_citizen'), 'employment_status_at_enrollment', 'occupation', 'employer_name', 'referral', )
调试用代码片段
# for i in self.fields['occupation']._queryset: # print(f"i is {i} {i.id}") initial_value = [x for x in self.fields['occupation']._queryset if x.id == self.initial['occupation']] print(f"initial value is {initial_value[0].code}") self.fields['occupation'].initial = initial_value[0].code self.fields['occupation'].to_field_name = 'code' pprint(vars(self.fields['occupation'])) # print("widget") # pprint(vars(self.fields['occupation'].widget)) # try: # actual_occupation = Occupation.objects.get(id=29) # this is a hardcoded example just trying to figure out what's happening # self.fields['occupation'].initial = actual_occupation.code # except Occupation.DoesNotExist: # self.fields['occupation'].initial = "" # print(f"after ") # pprint(vars(self.fields['occupation']))
解决方案
不需要JS处理,问题出在to_field_name的设置时机和初始值匹配逻辑上,以下是修正方案:
1. 正确指定to_field_name
不要在__init__中动态修改to_field_name,直接在字段定义阶段就指定,确保表单初始化时就以code作为选项值的基准:
# 在表单类顶部自定义occupation字段 occupation = forms.ModelChoiceField( queryset=Occupation.objects.all(), to_field_name='code', # 直接指定用code作为选项value label='Occupation:' )
2. 同步更新场景下的初始值
ModelForm默认会用模型实例的ID作为初始值,但现在我们用code作为选项值,需要在__init__中把初始值替换为对应对象的code:
def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 如果是更新场景(存在绑定的实例),替换初始值为code if self.instance and self.instance.occupation: self.initial['occupation'] = self.instance.occupation.code # 原有的Crispy配置保持不变 self.helper = FormHelper(self) # ... 其余Crispy配置代码 ...
完整修正后的表单代码
from django import forms from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, InlineRadios from .models import TutorOptionalServiceAndDemographics, Occupation class TutorOptionalForm(forms.ModelForm): # 自定义occupation字段,指定to_field_name为code occupation = forms.ModelChoiceField( queryset=Occupation.objects.all(), to_field_name='code', label='Occupation:' ) class Meta: model = TutorOptionalServiceAndDemographics fields = ['highest_degree', 'country_of_birth', 'immigrant', 'us_citizen', 'employment_status_at_enrollment', 'occupation', 'employer_name', 'referral', ] labels = { 'highest_degree': 'Highest degree/diploma earned', 'us_citizen': 'US Citizen:', 'immigrant': 'Immigrant:', 'country_of_birth': 'Country of Birth:', 'employment_status_at_enrollment': 'Employment status at time of registration:', 'employer_name': 'Employer Name:', 'referral': 'Referred by or heard about the program from source:', } help_texts = { 'employment_status_at_enrollment': '', 'referral': 'Select all that apply', } yes_no = [('True', 'Yes'), ('False', 'No')] widgets = { 'immigrant': forms.HorizontalRadioSelect(choices=yes_no), 'us_citizen': forms.HorizontalRadioSelect(choices=yes_no), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # 更新场景下替换初始值为code if self.instance and self.instance.occupation: self.initial['occupation'] = self.instance.occupation.code self.helper = FormHelper(self) self.helper.attrs['autocomplete'] = 'off' self.helper.form_id = 'id-gather-name' self.helper.form_class = 'form-horizontal' self.helper.form_method = 'post' self.helper.label_class = 'col-5' self.helper.field_class = 'col-6' self.helper.form_tag = False self.helper.layout = Layout( 'highest_degree', 'country_of_birth', InlineRadios('immigrant'), InlineRadios('us_citizen'), 'employment_status_at_enrollment', 'occupation', 'employer_name', 'referral', )
关键说明
to_field_name必须在字段定义时指定,否则表单的选项值匹配逻辑会因为初始化顺序问题失效- 更新场景下,初始值必须和
to_field_name指定的字段值一致,才能让表单正确识别选中项 - 全程后端处理即可,无需前端JS介入
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

