首页 > 解决方案 > 具有自定义用户模型的 Django rest_framework - 重复键值违反唯一约束

问题描述

我开始学习 django (rest_framework) 并构建一个应用程序。我在代码下方创建了一个自定义用户模型和管理器:

class CustomUserManager(BaseUserManager):
    """Define a model manager for User model with no username field."""

    def _create_user(self, email, password=None, **extra_fields):
        """Create and save a User with the given email and password."""
        if not email:
            raise ValueError('The given email must be set')
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_user(self, email, password=None, **extra_fields):
        extra_fields.setdefault('is_staff', False)
        extra_fields.setdefault('is_superuser', False)
        return self._create_user(email, password, **extra_fields)

    def create_superuser(self, email, password=None, **extra_fields):
        """Create and save a SuperUser with the given email and password."""
        extra_fields.setdefault('is_staff', True)
        extra_fields.setdefault('is_superuser', True)

        if extra_fields.get('is_staff') is not True:
            raise ValueError('Superuser must have is_staff=True.')
        if extra_fields.get('is_superuser') is not True:
            raise ValueError('Superuser must have is_superuser=True.')

        return self._create_user(email, password, **extra_fields)


class CustomUser(AbstractUser):
    email = models.EmailField(_('email address'), unique=True)
    username = models.CharField(_('username'), max_length=255, unique=False, blank=True)
    mobile = models.CharField(_('mobile'),max_length=255, blank=True)
    picture = models.ImageField(upload_to='images/thumbnail/%Y/%m/%d/', null=True, blank=True)

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = []

    @property
    def picture_preview(self):
        if self.picture:
            return mark_safe('<img src="{}" width="300" height="300" />'.format(self.picture.url))
        return ""

    objects = CustomUserManager()

我还为管理员自定义了视图……一切正常。

现在,当我从管理员处注册新用户时,例如,我两次发送同一封电子邮件时,我收到此错误消息:具有此电子邮件地址的用户已存在。

但是,如果我使用邮递员注册一个新用户来调用 api,我会收到以下错误:

django.db.utils.IntegrityError: duplicate key value violates unique constraint "accounts_customuser_email_key" 
DETAIL:  Key (email)=(mario@mario.it) already exists.

在序列化器类之下

class RegisterSerializer(serializers.ModelSerializer):
    password = serializers.CharField(write_only=True)
    email = serializers.EmailField()

    class Meta:
        model = CustomUser
        fields = ['email', 'password']

    def validate(self, attrs):
        # Check if user exist
        email = attrs.get('email', '')
        # if CustomUser.objects.filter(email=email).exists():
        #     raise serializers.ValidationError(
        #         'User with this Email address already exists.')

        # Validate password
        errors = dict()
        user = CustomUser(**attrs)
        password = attrs.get('password', '')
        try:
            # validate the password and catch the exception
            validators.validate_password(password=password, user=user)

        # the exception raised here is different than serializers.ValidationError
        except ValidationError as e:
            errors['password'] = list(e.messages)

        if errors:
            raise serializers.ValidationError(errors)

        return attrs

    def create(self, validated_data):
        return CustomUser.objects.create_user(**validated_data)

在验证器中,我评论了检查用户是否已经注册的代码,因为我希望 django 应该知道电子邮件是否已经在没有额外代码的情况下注册,因为在管理员中它可以正常工作。

和视图类:

class RegisterView(generics.GenericAPIView):

    serializers_class = RegisterSerializer

    def post(self, request):
        user = request.data
        serializer = self.serializers_class(data=user)
        serializer.is_valid(raise_exception=True)
        serializer.save()

        user_data = serializer.data
        return Response(user_data, status.HTTP_201_CREATED)

我的问题是我做错了什么?我错过了什么吗?

谢谢大家...任何帮助将不胜感激。

标签: pythondjangodjango-rest-framework

解决方案


如果您使用相同的电子邮件创建用户,django 会检查并引发 IntegrityError。您必须捕获此异常并返回响应。此外,最好在视图部分而不是在序列化程序验证方法中执行此操作,因为序列化程序的验证方法的工作是仅检查给定的输入是否正确。所以你的视图应该是这样的。

class RegisterView(generics.GenericAPIView):

    serializers_class = RegisterSerializer

    def post(self, request):
        user = request.data
        serializer = self.serializers_class(data=user)
        serializer.is_valid(raise_exception=True)
        try:
            user = User.objects.create_user(
                email=serializer.data.get("email"),
                password=request.data.get("password"),
            )
        except IntegrityError:
            return Response("Email already exists.",
                status=status.HTTP_406_NOT_ACCEPTABLE,
            )
        response = jwt_response_payload_handler(user.get_jwt_token(), user, request) #  some payload handler, for user information
        return Response(response, status.HTTP_201_CREATED)

推荐阅读