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

Djoser注册报错ValueError:需为CustomUser.native_city传入City实例

问题描述

使用Djoser实现JWT注册逻辑时触发错误:

ValueError: Cannot assign "OrderedDict([('city_name', 'New York')])": "CustomUser.native_city" must be a "City" instance.

尝试继承serializers.ModelSerializer后,用户无法通过含uid和token的激活链接完成账号激活,排查许久未解决,请求帮助。

相关代码

models.py

class City(models.Model):
    city_name = models.CharField(unique=True)

    class Meta:
        ordering = ('id',)

    def __str__(self):
        return self.city_name


class CustomUser(AbstractUser):
    email = models.EmailField(unique=True, validators=[validate_email])
    native_city = models.ForeignKey(
        City,
        on_delete=models.CASCADE,
        blank=True,
        null=True
    )

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username']

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=('email', 'username'),
                name='unique_username_email')
        ]

    def __str__(self):
        return self.email

serializers.py

class CitySerializer(serializers.ModelSerializer):

    class Meta:
        model = City
        fields = '__all__'


class CustomUserCreateSerializer(UserCreateSerializer):
    email = serializers.EmailField(
        validators=[UniqueValidator(queryset=CustomUser.objects.all())]
    )

    native_city = CitySerializer()

    class Meta:
        model = CustomUser
        fields = ('id', 'username', 'email', 'password', 'first_name', 'last_name', 'native_city')

    def create(self, validated_data):
        native_city_data = validated_data.pop('native_city')
        city_instance, _ = City.objects.get_or_create(city_name=native_city_data)
        city_instance.save()
        user = CustomUser.objects.create(native_city=city_instance, **validated_data)
        user.save()
        return user


class CustomUserSerializer(UserSerializer):
    native_city = CitySerializer()

    class Meta:
        model = CustomUser
        fields = ('id', 'username', 'email', 'first_name', 'last_name', 'native_city')

完整报错栈

Internal Server Error: /auth/users/
djangoweatherremider-web-1  | Traceback (most recent call last):
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/exception.py", line 55, in inner
djangoweatherremider-web-1  |     response = get_response(request)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/django/core/handlers/base.py", line 197, in _get_response
djangoweatherremider-web-1  |     response = wrapped_callback(request, *callback_args, **callback_kwargs)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/django/views/decorators/csrf.py", line 56, in wrapper_view
djangoweatherremider-web-1  |     return view_func(*args, **kwargs)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/rest_framework/viewsets.py", line 125, in view
djangoweatherremider-web-1  |     return self.dispatch(request, *args, **kwargs)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 509, in dispatch
djangoweatherremider-web-1  |     response = self.handle_exception(exc)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 469, in handle_exception
djangoweatherremider-web-1  |     self.raise_uncaught_exception(exc)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
djangoweatherremider-web-1  |     raise exc
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/rest_framework/views.py", line 506, in dispatch
djangoweatherremider-web-1  |     response = handler(request, *args, **kwargs)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/rest_framework/mixins.py", line 18, in create
djangoweatherremider-web-1  |     serializer.is_valid(raise_exception=True)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/rest_framework/serializers.py", line 227, in is_valid
djangoweatherremider-web-1  |     self._validated_data = self.run_validation(self.initial_data)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/rest_framework/serializers.py", line 429, in run_validation
djangoweatherremider-web-1  |     value = self.validate(value)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/djoser/serializers.py", line 71, in validate
djangoweatherremider-web-1  |     user = User(**attrs)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/django/db/models/base.py", line 543, in __init__
djangoweatherremider-web-1  |     _setattr(self, field.name, rel_obj)
djangoweatherremider-web-1  |   File "/usr/local/lib/python3.10/site-packages/django/db/models/fields/related_descriptors.py", line 266, in __set__
djangoweatherremider-web-1  |     raise ValueError(
djangoweatherremider-web-1  | ValueError: Cannot assign "OrderedDict([('city_name', 'New York')])": "CustomUser.native_city" must be a "City" instance.
djangoweatherremider-web-1  | [30/Aug/2023 10:06:42] "POST /auth/users/ HTTP/1.1" 500 16728
解决方案

错误根源

  1. Djoser的validate方法会提前尝试用验证后的属性创建User实例,但native_city此时是嵌套序列化返回的OrderedDict,不是City模型实例,导致赋值失败。
  2. 直接继承ModelSerializer会丢失Djoser内置的激活、密码重置等核心逻辑,导致激活链接失效。

修复步骤

1. 重写CustomUserCreateSerializer的字段验证逻辑

保留对DjoserUserCreateSerializer的继承,在验证阶段提前将前端传入的城市字典转换为City实例:

class CustomUserCreateSerializer(UserCreateSerializer):
    email = serializers.EmailField(
        validators=[UniqueValidator(queryset=CustomUser.objects.all())]
    )
    # 输出用嵌套序列化器,输入允许字典/空值
    native_city = CitySerializer(required=False, allow_null=True)

    class Meta:
        model = CustomUser
        fields = ('id', 'username', 'email', 'password', 'first_name', 'last_name', 'native_city')

    def validate_native_city(self, value):
        # 处理前端传入的城市字典
        if isinstance(value, dict):
            city_name = value.get('city_name')
            if not city_name:
                raise serializers.ValidationError("city_name字段不能为空")
            # 存在则获取,不存在则创建
            city, _ = City.objects.get_or_create(city_name=city_name)
            return city
        # 空值或ID直接返回,Django会自动处理ID转实例
        return value

    def create(self, validated_data):
        # 验证阶段已处理native_city,直接创建用户
        user = CustomUser.objects.create(**validated_data)
        user.set_password(validated_data['password'])
        user.save()
        return user

2. 保留Djoser激活逻辑

不要替换继承链为ModelSerializer,必须基于Djoser提供的UserCreateSerializer和UserSerializer扩展,这样才能保留激活邮件生成、链接验证等内置功能。你原有的CustomUserSerializer写法无需修改。

3. 前端传参格式验证

注册请求中native_city需传如下格式:

{
  "username": "testuser",
  "email": "test@example.com",
  "password": "testpass123",
  "native_city": {
    "city_name": "New York"
  }
}

若需要支持直接传城市ID,可在validate_native_city中添加对整数类型的判断逻辑。

验证修复效果

  • 发送注册请求,validate_native_city会将字典转为City实例,Djoser的验证流程可正常创建用户。
  • 用户注册后可收到激活邮件,点击含uid和token的链接可完成账号激活。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 04:35:59