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

Django自定义用户注册异常:无用户却提示邮箱已存在

自定义用户注册时的邮箱重复验证问题

我基于Django创建了Provider和Seeker两种自定义用户类型,编写注册视图后发现:添加Provider用户时,明明数据库里没有任何用户,却始终提示邮箱已存在。

我试过两种解决思路,但都没解决问题:

  • 移除ProviderSerializer中的UniqueValidator,在视图里自行实现重复邮箱检测逻辑
  • 先将用户保存到数据库,再检查是否存在重复邮箱

相关代码

注册视图代码

from django.shortcuts import render
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import AllowAny, IsAdminUser
from .models import Provider, Seeker
from .serializer import ProviderSerializer
from django.contrib.auth.models import Group
from rest_framework import serializers

@api_view(['POST'])
@permission_classes([AllowAny])
def Register(request):
    if request.data.get('work_email'):
        seri = ProviderSerializer(data=request.data)
        if seri.is_valid():
            try:
                user = seri.save()
                try:
                    group = Group.objects.get(name='Provider')
                    user.groups.add(group)
                except Group.DoesNotExist:
                    return Response({"Message": "Exception at group assigning"})
            except serializers.ValidationError as e:
                return Response(e.errors, status=status.HTTP_400_BAD_REQUEST)
            return Response({"Message": "User created successfully"})
        else:
            return Response(seri.errors, status=status.HTTP_400_BAD_REQUEST)
    else:
        return Response({"Message": "No Work_email"}, status=status.HTTP_400_BAD_REQUEST)

模型代码

from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.

class GenericUser(AbstractUser):
    username = models.CharField(max_length=150, blank=True,null = True)

    email = models.EmailField(max_length=255,unique=True,db_index=True)
    
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS= []

class Provider(GenericUser):
    work_email = models.EmailField(max_length=255, unique=True, blank=False, null = True)

class Seeker(GenericUser):
    ed_level = models.EmailField(max_length=255, blank=True)

问题根源

你的问题核心在于继承模型的字段唯一性验证逻辑错误:

  1. GenericUser的email字段设置了全局唯一约束,所有用户(Provider/Seeker)的邮箱都在父表中唯一
  2. ProviderSerializer默认会继承父类的email字段,并自动生成针对Provider.objects的UniqueValidator,但实际上邮箱的唯一性需要在整个GenericUser范围内检查,而非仅Provider用户
  3. 另外,默认的ModelSerializer创建用户时不会调用create_user方法,会导致密码明文存储,这也是潜在问题

解决方案

方案1:修正序列化器的验证逻辑

重写ProviderSerializer,为email字段指定正确的全局查询集,同时处理密码哈希:

from rest_framework import serializers
from .models import Provider, GenericUser
from django.contrib.auth.hashers import make_password

class ProviderSerializer(serializers.ModelSerializer):
    email = serializers.EmailField(
        validators=[
            serializers.UniqueValidator(
                queryset=GenericUser.objects.all(),
                message="该邮箱已被注册"
            )
        ]
    )
    work_email = serializers.EmailField(
        validators=[
            serializers.UniqueValidator(
                queryset=Provider.objects.all(),
                message="该工作邮箱已被注册"
            )
        ]
    )

    class Meta:
        model = Provider
        fields = ('email', 'password', 'work_email', 'first_name', 'last_name') # 根据需求添加字段
        extra_kwargs = {
            'password': {'write_only': True}
        }

    def create(self, validated_data):
        # 密码哈希处理
        validated_data['password'] = make_password(validated_data['password'])
        return super().create(validated_data)

方案2:在视图中手动处理唯一性验证(优化你尝试的思路)

移除序列化器中的UniqueValidator,在视图中直接检查全局邮箱和工作邮箱的唯一性:

序列化器修改:

from rest_framework import serializers
from .models import Provider
from django.contrib.auth.hashers import make_password

class ProviderSerializer(serializers.ModelSerializer):
    class Meta:
        model = Provider
        fields = ('email', 'password', 'work_email', 'first_name', 'last_name')
        extra_kwargs = {
            'password': {'write_only': True}
        }

    def create(self, validated_data):
        validated_data['password'] = make_password(validated_data['password'])
        return super().create(validated_data)

视图修改:

from django.shortcuts import render
from rest_framework import status
from rest_framework.response import Response
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from .models import Provider, GenericUser
from .serializer import ProviderSerializer
from django.contrib.auth.models import Group

@api_view(['POST'])
@permission_classes([AllowAny])
def Register(request):
    work_email = request.data.get('work_email')
    email = request.data.get('email')
    
    if not work_email:
        return Response({"Message": "缺少工作邮箱"}, status=status.HTTP_400_BAD_REQUEST)
    
    # 检查全局邮箱是否已存在
    if GenericUser.objects.filter(email=email).exists():
        return Response({"email": ["该邮箱已被注册"]}, status=status.HTTP_400_BAD_REQUEST)
    
    # 检查工作邮箱是否已存在
    if Provider.objects.filter(work_email=work_email).exists():
        return Response({"work_email": ["该工作邮箱已被注册"]}, status=status.HTTP_400_BAD_REQUEST)
    
    seri = ProviderSerializer(data=request.data)
    if seri.is_valid(raise_exception=True):
        user = seri.save()
        try:
            group = Group.objects.get(name='Provider')
            user.groups.add(group)
        except Group.DoesNotExist:
            return Response({"Message": "分配用户组失败:Provider组不存在"}, status=status.HTTP_400_BAD_REQUEST)
        return Response({"Message": "用户创建成功"}, status=status.HTTP_201_CREATED)

额外注意事项

  1. 确保数据库已正确迁移:执行python manage.py makemigrations和python manage.py migrate,保证GenericUser和Provider表的唯一索引正确创建
  2. 测试时使用不同的邮箱和工作邮箱,避免字段值重复导致的验证错误
  3. 如果使用SQLite,注意删除旧数据库文件后重新迁移,确保测试环境干净

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 01:19:53