You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django3更新用户手机号/邮箱报1非有效选项错误如何解决

报错原因

Select a valid choice. 1 is not one of the available choices报错由两个问题触发:

  1. 动态生成的ChoiceField选项值类型与表单提交值类型不匹配:通过values_list('pk', 'type__name')生成的选项中,主键值是整数类型,但表单提交的选中值是字符串格式,Django对ChoiceField做合法性校验时会做严格的值+类型匹配,字符串"1"无法匹配整数1,直接触发校验失败。
  2. 表单类中new_info = []定义为类属性,所有表单实例会共享同一个列表,多次实例化时会出现历史数据累积,导致保存逻辑异常。
修复方案

按以下步骤修改forms.py代码即可:

  1. 移除类层级的new_info = []定义,将其改为实例属性,避免跨实例数据污染
  2. 所有动态生成的ChoiceField选项值统一转为字符串类型,和表单提交的字符串值格式对齐
  3. 清洗数据时,将外键、数字类型的字段值从字符串转回对应数字类型,再赋值给模型实例

修改后的核心代码片段如下:

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 04:39:57