更新相关对象 django rest framework

Updating related objects django rest framework

在我的 django 项目中,我有 2 个相关模型:

class User(AbstractUser):
    email = models.EmailField(
        verbose_name='e-mailaddress',
        max_length=255,
        unique=True)
    # other props not important
        
class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    #other props not important

对于创建和检索,在序列化程序中将 User 作为嵌套对象很方便:

class ProfileSerializer(serializers.ModelSerializer):
    user = UserSerializer()
    class Meta:
        model = Profile
        fields = '__all__'

    def create(self, validated_data):
        user_data = validated_data.pop('user')
        user = User.objects.create(**user_data)
        user.save()
        # because of reasons, an empty profile is created on initial save. This works, trust me
        d, _ = Profile.objects.update_or_create(user=user, defaults=validated_data)
        d.save()
        return d

然而。我也希望能够执行的一项操作是同时更新配置文件和用户的属性。使用我的序列化器执行此操作时,serializer.is_valid() 失败,因为提供的电子邮件已存在于数据库中。这也表明,即使验证通过(即由于更新的电子邮件地址),也将创建一个新的用户对象并将其耦合到配置文件。所以我的问题是:

如何使序列化程序的验证检查嵌套对象的 edit 是否有效,而不是检查是否可以创建新对象?

您可以通过执行类似的操作来更新个人资料及其相关用户的属性。

class ProfileSerializer(serializers.ModelSerializer):
    user = UserSerializer()
    class Meta:
        model = Profile
        fields = '__all__'

def update(self, instance, validated_data):
    # update the profile-related fields here.
    # the other properties which you didn't mention.
    ...
    profile = instance
    # get the profile object
    ...
    instance.save()

    user = validated_data.get('user')
    # get the related user.
    # update the user properties here, like:
    user.email = 'UPDATED_EMAIL'
    user.save() # you need to save the user as well as the profile!

    return instance

我设法自己找到了一些解决方法。使用序列化器字段的 source 属性,可以将相关对象的某些属性添加到序列化器。就我而言,我能够将以下行添加到 ProfileSerializer 以获得我需要的功能:

    first_name = serializers.CharField(max_length=30, source='user.first_name', required=False)
    last_name = serializers.CharField(max_length=150, source='user.last_name', required=False)
    email = serializers.CharField(max_length=255, source='user.email')

User 还有更多的属性,但对于我目前的要求,我不需要它们并且这工作正常(尽管如果您需要所有属性,为所有属性添加这样一行会很乏味).它确实需要 createupdate 函数的自定义实现,但我认为无论如何都会需要它们。我个人使用:

    def create(self, validated_data):
        user_data = validated_data.pop('user')
        user = User.objects.create(**user_data)
        user.save()
        instance = ...
        return instance

    def update(self, instance, validated_data):
        user_data = validated_data.pop('user')
        user = instance.user
        for k,v in user_data.items():
            setattr(user, k, v)
        user.save()
        ...
        return instance

注意:django-rest 自动将具有共享源对象的属性分组到嵌套对象中:{'user': {'email': ..., 'first_name': ..., 'last_name': ...}, 'phone_number': ...}(其中 phone_number 是配置文件 属性)