Django REST Framework更新OneToOne字段时遇邮箱重复错误求助
问题描述
我有一个继承自AbstractUser的User模型,其中email字段设置为唯一;还有一个与User模型一对一关联的Profile模型。我编写了对应的ProfileViewSet和ProfileSerializer,实现了获取与更新个人资料的接口,但尝试更新个人资料时,始终返回400 Bad Request错误,提示“user with this email already exists”。即使改用PATCH方法或设置partial=True,问题依然存在,请问该如何解决?
相关代码
模型定义
class User(AbstractUser): email = models.EmailField(unique=True) class Profile(models.Model): user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) phone = models.CharField(max_length=13, validators=[phone_regex], unique=True, null=True, blank=True) birth_date = models.DateField(blank=True, null=True) about = models.TextField(max_length=2000, blank=True) def __str__(self): return f"{self.user.first_name} {self.user.last_name}"
视图代码(views.py)
class ProfileViewSet(ModelViewSet): .... @action(detail=False, methods=["GET", "PUT"], permission_classes=[IsAuthenticated]) def me(self, request, *args, **kwargs): profile = Profile.objects.get(user_id=request.user.id) if request.method == "GET": serializer = ProfileSerializer(profile) return Response(serializer.data) elif request.method == "PUT": serializer = ProfileSerializer(profile, request.data) serializer.is_valid(raise_exception=True) serializer.save() return Response(serializer.data)
序列化器代码(serializers.py)
class ProfileSerializer(serializers.ModelSerializer): user = CurrentUserSerializer() def update(self, instance, validated_data): user_data = validated_data.pop('user') user_ser = CurrentUserSerializer(instance=instance.user, data=user_data) if user_ser.is_valid(): user_ser.save() instance.phone = validated_data.get('phone', instance.phone) instance.birth_date = validated_data.get('birth_date', instance.birth_date) instance.about = validated_data.get('about', instance.about) instance.save() return instance class Meta: model = Profile fields = [ 'id', 'user', 'phone', 'birth_date', 'about' ]
错误信息
{ "user": { "email": [ "user with this email already exists." ] } }
问题原因
核心问题出在CurrentUserSerializer对email字段的唯一性校验逻辑:
- 更新Profile时,请求传入的user数据中包含当前用户的email,而序列化器默认的
UniqueValidator会遍历整个用户表进行校验,发现该邮箱已存在(即当前用户自身的邮箱),因此抛出唯一性冲突错误。 - 即使使用PATCH方法或设置
partial=True,只要请求数据中包含email字段,就会触发这一校验逻辑。
解决方案
根据业务需求,可选择以下任意一种方式解决:
方式一:修改CurrentUserSerializer,排除当前实例的唯一性校验
在CurrentUserSerializer中,给email字段的校验逻辑添加排除当前用户的规则,避免自身邮箱触发重复校验:
from rest_framework import serializers from .models import User class CurrentUserSerializer(serializers.ModelSerializer): def validate_email(self, value): # 排除当前用户自身,检查邮箱是否被其他用户占用 if User.objects.exclude(pk=self.instance.pk).filter(email=value).exists(): raise serializers.ValidationError("该邮箱已被注册") return value class Meta: model = User fields = ['id', 'email', 'first_name', 'last_name'] # 按需添加其他字段
方式二:将email设为只读字段(不允许修改邮箱场景)
如果业务上不允许用户通过个人资料接口修改邮箱,直接在CurrentUserSerializer中将email设为只读:
class CurrentUserSerializer(serializers.ModelSerializer): email = serializers.EmailField(read_only=True) class Meta: model = User fields = ['id', 'email', 'first_name', 'last_name']
这样即使请求数据中传入email,序列化器也会忽略该字段,不会触发唯一性校验。
方式三:仅更新有变化的用户字段
如果前端请求总是返回完整的user数据(包含未修改的email),可以在ProfileSerializer的update方法中,只提取有变化的字段进行更新,避免传入未修改的email:
class ProfileSerializer(serializers.ModelSerializer): user = CurrentUserSerializer() def update(self, instance, validated_data): user_data = validated_data.pop('user') # 筛选出与当前用户属性不同的字段 updated_user_data = {} for key, value in user_data.items(): if getattr(instance.user, key) != value: updated_user_data[key] = value if updated_user_data: # 使用partial=True允许部分更新 user_ser = CurrentUserSerializer(instance=instance.user, data=updated_user_data, partial=True) if user_ser.is_valid(): user_ser.save() # 更新Profile自身字段 instance.phone = validated_data.get('phone', instance.phone) instance.birth_date = validated_data.get('birth_date', instance.birth_date) instance.about = validated_data.get('about', instance.about) instance.save() return instance class Meta: model = Profile fields = ['id', 'user', 'phone', 'birth_date', 'about']
内容的提问来源于stack exchange,提问作者Ryan
相关产品推荐
相关产品推荐

