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

DRF注册API自定义错误信息未生效问题求助

问题描述

使用DRF开发注册API时,期望返回自定义错误信息「Email already taken」「username already taken」,但实际收到Django默认错误信息「Custom user with this email already exists」,当前错误响应如下:

{
    "status_code": 400,
    "status": false,
    "message": "User registration failed",
    "data": null,
    "errors": {
        "error_message": [
            [
                "custom user with this email already exists."
            ],
            [
                "custom user with this username already exists."
            ]
        ]
    }
}

用户提供的代码文件如下:

View.py

from rest_framework import generics
from rest_framework import status
from rest_framework.permissions import AllowAny
from .serializers import RegisterSerializer
from projectname.utils import custom_response

class RegisterView(generics.CreateAPIView):
    serializer_class = RegisterSerializer
    permission_classes = [AllowAny]

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        if serializer.is_valid():
            user = serializer.save()
            response_data = {
                "username": user.username,
                "first_name": user.first_name,
                "last_name": user.last_name,
                "email": user.email,
                "user_type": user.user_type,
            }
            return custom_response(
                success=True,
                message="User registered successfully",
                data=response_data,
                status_code=status.HTTP_201_CREATED
            )
        else:
            return custom_response(
                success=False,
                message="User registration failed",
                errors={"error_message": list(serializer.errors.values())},
                status_code=status.HTTP_400_BAD_REQUEST
            )

serializer.py

from rest_framework import serializers
from .models import CustomUser

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True)
    
    class Meta:
        model = CustomUser
        fields = ('email', 'username', 'first_name', 'last_name', 'user_type', 'password')

    def validate(self, data):
        required_fields = ['email', 'username', 'first_name', 'last_name', 'user_type', 'password']
        errors = {}

        for field in required_fields:
            if field not in data or not data[field].strip():
                errors[field] = f"{field.replace('_', ' ').capitalize()} is required."

        if CustomUser.objects.filter(email=data.get('email', '')).exists():
            errors['error_message'] = "Email already exists."

        if CustomUser.objects.filter(username=data.get('username', '')).exists():
            errors['error_message'] = "Username already exists."

        if errors:
            raise serializers.ValidationError({"error_message": list(errors.values())})

        return data

    def create(self, validated_data):
        user = CustomUser.objects.create_user(
            email=validated_data['email'],
            username=validated_data['username'],
            first_name=validated_data['first_name'],
            last_name=validated_data['last_name'],
            user_type=validated_data['user_type'],
            password=validated_data['password']
        )
        return user

models.py

from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models

class CustomUserManager(BaseUserManager):
    def create_user(self, email, username, first_name, last_name, user_type, password=None):
        if not email:
            raise ValueError('The Email field must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, username=username, first_name=first_name, last_name=last_name, user_type=user_type)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, username, password=None):
        user = self.create_user(email, username, password)
        user.is_admin = True
        user.is_staff = True
        user.is_superuser = True
        user.save(using=self._db)
        return user

class CustomUser(AbstractBaseUser, PermissionsMixin):
    USER_TYPE_CHOICES = (
        ('child', 'Child'),
        ('parent', 'Parent'),
        ('admin', 'Admin'),
    )

    email = models.EmailField(unique=True)
    username = models.CharField(max_length=30, unique=True)
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    user_type = models.CharField(max_length=10, choices=USER_TYPE_CHOICES)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_admin = models.BooleanField(default=False)

    objects = CustomUserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username', 'first_name', 'last_name', 'user_type']

    def __str__(self):
        return self.email

问题原因
  1. 模型层默认校验优先执行:CustomUser模型中email和username字段设置了unique=True,DRF的ModelSerializer会自动为这些字段添加唯一性校验,且该校验逻辑在自定义的validate方法之前执行,导致默认错误信息先被抛出。
  2. 自定义校验逻辑缺陷:自定义validate方法中,若同时存在邮箱和用户名重复的情况,后添加的错误信息会覆盖前一个;且自定义校验抛出的错误会与模型层默认错误共存,导致响应中出现冗余信息。
  3. 视图层错误处理不规范:视图中直接将serializer.errors.values()打包返回,未对默认错误信息做替换或整理。

修复方案

1. 禁用ModelSerializer自动唯一性校验

在Serializer的Meta类中添加extra_kwargs,关闭email和username的默认唯一性校验:

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True)
    
    class Meta:
        model = CustomUser
        fields = ('email', 'username', 'first_name', 'last_name', 'user_type', 'password')
        # 禁用自动唯一性校验
        extra_kwargs = {
            'email': {'validators': []},
            'username': {'validators': []}
        }

2. 修正自定义validate方法逻辑

确保错误信息不被覆盖,同时使用目标自定义文本:

def validate(self, data):
    required_fields = ['email', 'username', 'first_name', 'last_name', 'user_type', 'password']
    errors = {}

    # 校验必填字段
    for field in required_fields:
        if field not in data or not data[field].strip():
            errors[field] = f"{field.replace('_', ' ').capitalize()} is required."

    # 校验邮箱唯一性
    email = data.get('email', '').strip()
    if email and CustomUser.objects.filter(email=email).exists():
        errors['email'] = "Email already taken"

    # 校验用户名唯一性
    username = data.get('username', '').strip()
    if username and CustomUser.objects.filter(username=username).exists():
        errors['username'] = "Username already taken"

    if errors:
        raise serializers.ValidationError(errors)

    return data

3. 优化视图层错误响应格式

直接返回序列化器整理好的错误信息,避免嵌套冗余:

else:
    return custom_response(
        success=False,
        message="User registration failed",
        errors=serializer.errors,
        status_code=status.HTTP_400_BAD_REQUEST
    )

验证结果

修复后,错误响应会符合预期:

  • 邮箱重复时:
{
    "status_code": 400,
    "status": false,
    "message": "User registration failed",
    "data": null,
    "errors": {
        "email": "Email already taken"
    }
}
  • 用户名重复时:
{
    "status_code": 400,
    "status": false,
    "message": "User registration failed",
    "data": null,
    "errors": {
        "username": "Username already taken"
    }
}
  • 两者都重复时:
{
    "status_code": 400,
    "status": false,
    "message": "User registration failed",
    "data": null,
    "errors": {
        "email": "Email already taken",
        "username": "Username already taken"
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 13:17:36