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

Django报TypeError:意外关键字参数'choices'的解决方法

问题根因

你遇到的报错来自两个核心写法错误:

  1. 字段类型用错:forms.CharField 本身不支持choices参数,Django表单中渲染下拉选择框需要使用forms.ChoiceField(单选场景),这是触发TypeError: Field.__init__() got an unexpected keyword argument 'choices'报错的直接原因。
  2. choices格式错误:不管是模型层还是表单层,Django要求choices参数传入的每个选项必须是**(存入数据库的值, 页面显示的文本)** 格式的二元组,你当前写的单值元组不符合规范,哪怕字段类型改对了也会出现选项渲染异常。

另外代码里还有两个隐性问题:一是表单字段设置初始值应该用initial参数,default是模型层字段的参数,表单层不识别;二是Customer模型中关联User的外键命名为username,和User模型自带的username字段重名,后续会出现属性覆盖的bug;三是自定义的保存方法名data_save不符合Django表单的默认规范,视图层调用form.save()时不会执行你写的Customer表创建逻辑。

正确实现代码

建议把选项常量统一定义在Customer模型中,避免多位置重复编写导致的不一致问题。

修正后的models.py

from django.db import models
from django.contrib.auth.models import User

class Customer(models.Model):
    # 选项常量建议全大写命名,符合Python编码规范
    MEMBER_TIERS = (
        ('Basic', 'Basic'),
        ('Intermediate', 'Intermediate'),
        ('Beast', 'Beast'),
    )
    # 关联User的外键直接命名为user,避免和User自带的username字段冲突
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
    email_address = models.EmailField(max_length=30)
    membership_tier = models.CharField(max_length=30, choices=MEMBER_TIERS, default='Basic')

修正后的forms.py

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.db import transaction
from django.contrib.auth.models import User
from .models import Customer

class CustomerSignUpForm(UserCreationForm):
    first_name = forms.CharField(required=True)
    last_name = forms.CharField(required=True)
    email_address = forms.EmailField(required=True)
    # 替换为ChoiceField,直接引用模型中定义的选项常量,初始值用initial参数
    membership_tier = forms.ChoiceField(choices=Customer.MEMBER_TIERS, initial='Basic', required=True)

    class Meta(UserCreationForm.Meta):
        model = User
    
    @transaction.atomic
    def save(self):
        # 遵循Django表单默认方法命名,确保视图层调用form.save()时能执行关联表创建逻辑
        user = super().save(commit=False)
        user.first_name = self.cleaned_data.get('first_name')
        user.last_name = self.cleaned_data.get('last_name')
        user.is_customer = True
        user.save()
        customer = Customer.objects.create(user=user)
        customer.email_address = self.cleaned_data.get('email_address')
        customer.membership_tier = self.cleaned_data.get('membership_tier')
        customer.save()
        return user
开发注意事项
  • 严格区分模型字段和表单字段的参数、类型差异:模型层models.CharField支持choices、default参数,但表单层forms.CharField不支持choices;下拉选择场景表单必须用forms.ChoiceField,表单初始值用initial参数,不要混用模型层的default参数。
  • choices必须遵循二元组格式:哪怕存储值和页面显示值完全相同,也必须写成(value, label)的二元组结构,禁止直接传单值列表/元组。
  • 选项常量不要重复定义:统一在模型或者单独的常量文件中维护枚举选项,表单、业务逻辑中直接引用,避免后续修改选项时漏改导致的数据不一致问题。
  • 遵循Django表单的方法命名规范:表单的保存方法统一用save()作为方法名,自定义命名会导致视图层调用默认save方法时,关联表创建逻辑不执行,产生数据缺失。
  • 关联模型外键命名不要和关联表的已有字段重名:比如关联User模型的OneToOneField直接命名为user即可,不要用username这类User自带的字段名,避免属性覆盖导致的异常。
  • 多表写入场景保留@transaction.atomic装饰器:可以保证多表写入的原子性,要么全部成功要么全部回滚,避免产生只有User没有对应Customer记录的脏数据。

内容的提问来源于stack exchange,提问作者FuadThabet

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 02:39:09