Django3更新用户手机号/邮箱报1非有效选项错误如何解决
报错原因
Select a valid choice. 1 is not one of the available choices报错由两个问题触发:
- 动态生成的
ChoiceField选项值类型与表单提交值类型不匹配:通过values_list('pk', 'type__name')生成的选项中,主键值是整数类型,但表单提交的选中值是字符串格式,Django对ChoiceField做合法性校验时会做严格的值+类型匹配,字符串"1"无法匹配整数1,直接触发校验失败。 - 表单类中
new_info = []定义为类属性,所有表单实例会共享同一个列表,多次实例化时会出现历史数据累积,导致保存逻辑异常。
修复方案
按以下步骤修改forms.py代码即可:
- 移除类层级的
new_info = []定义,将其改为实例属性,避免跨实例数据污染 - 所有动态生成的
ChoiceField选项值统一转为字符串类型,和表单提交的字符串值格式对齐 - 清洗数据时,将外键、数字类型的字段值从字符串转回对应数字类型,再赋值给模型实例
修改后的核心代码片段如下:
class ClientUserInfoUpdateForm(forms.ModelForm): user = None methods = None def __init__(self, *args, **kwargs): self.user = kwargs.pop('user') self.methods = kwargs.pop('methods') super(ClientUserInfoUpdateForm, self).__init__(*args, **kwargs) # 初始化实例级别的new_info列表 self.new_info = [] # 基于传入的methods动态生成表单项 for dm in self.methods: methodtypes = ContactInfoMethodType.objects.types_by(dm['method'].name) # 将选项pk转为字符串,匹配表单提交的字符串值 methodtypes_choices = [(str(pk), name) for pk, name in methodtypes.values_list('pk', 'type__name')] if(dm['method'].use_dialling_code): # 拨号选项值同样转字符串 dialling_code_choices = [(str(code), label) for code, label in countries_sorted_with_dial_choices] i = 1 for info in dm['items']: fn = info.dynamic_form_field_name() self.fields[fn] = forms.CharField() if(dm['method'].name.lower() == "email"): self.fields[fn] = forms.EmailField() self.fields[fn].name = fn self.fields[fn].verbose_name = dm['method'].name self.fields[fn].label = dm['method'].name + " " + str(i) self.fields[fn].required = True self.fields[fn].initial = info.info self.fields[fn].widget.attrs['class'] = 'span2' fns = info.dynamic_form_methodtype_select_field_name() self.fields[fns] = forms.ChoiceField() self.fields[fns].choices = methodtypes_choices self.fields[fns].widget.attrs['class'] = 'input-small' self.fields[fns].initial = str(info.methodtype.pk) # 初始值也转字符串保证选中态正常 self.fields[fns].required = True fndc = info.dynamic_form_dialling_code_select_field_name() if(dm['method'].use_dialling_code): self.fields[fndc] = forms.ChoiceField() self.fields[fndc].choices = dialling_code_choices self.fields[fndc].widget.attrs['class'] = 'span2' self.fields[fndc].initial = str(info.dialling_code) # 初始值转字符串 self.fields[fndc].required = True i += 1 def clean(self): cleaned_data = super().clean() # 调用父类clean方法获取标准清洗后数据 for dm in self.methods: for info in dm['items']: fn = info.dynamic_form_field_name() fns = info.dynamic_form_methodtype_select_field_name() if(fn not in cleaned_data or fns not in cleaned_data): raise forms.ValidationError("There is some field data missing") info.info = cleaned_data[fn] # 把字符串类型的methodtype_id转成整数再赋值给模型外键字段 info.methodtype_id = int(cleaned_data[fns]) fndc = info.dynamic_form_dialling_code_select_field_name() if(dm['method'].use_dialling_code): if fndc not in cleaned_data: raise forms.ValidationError("There is some field data missing") # 拨号代码同样转整数 info.dialling_code = int(cleaned_data[fndc]) self.new_info.append(info) del cleaned_data[fn] del cleaned_data[fns] if(dm['method'].use_dialling_code): del cleaned_data[fndc] return cleaned_data def save(self, commit=True): obj = super(ClientUserInfoUpdateForm, self).save(commit=False) for info in self.new_info: info.save() return obj class Meta: model = ClientUser exclude = ['password', 'client', 'email', 'invitation', 'name', 'role', 'last_login', 'minimum_holiday', 'budget_expenses', 'vat']
额外说明
views.py逻辑无需修改,改完表单代码后重启服务即可正常提交编辑- 若后续新增其他动态
ChoiceField,都需要保证选项值类型和提交值类型一致,避免同类校验错误
内容的提问来源于stack exchange,提问作者python developer
相关产品推荐
相关产品推荐

