DjangoRestFramework - 如何使用模型序列化程序访问 OneToOneField 反向关系的其他字段?

Posted

技术标签:

【中文标题】DjangoRestFramework - 如何使用模型序列化程序访问 OneToOneField 反向关系的其他字段?【英文标题】:DjangoRestFramework - How to access other fields of a OneToOneField reverse relationship using a model serializer? 【发布时间】:2015-12-27 11:38:34 【问题描述】:

我正在使用默认的 User 模型,并且还使用 UserExtended 模型对其进行扩展:

class Country(models.Model):
    countryName = models.CharField(max_length=50, unique=True)
    countryCode = models.CharField(max_length=10, unique=True)

class UserExtended(models.Model):
    user = models.OneToOneField(User, related_name="userextended")
    country = models.ForeignKey(Country)

我正在尝试遵循此处记录的内容:http://www.django-rest-framework.org/api-guide/relations/#reverse-relations

这是我的 UserSerializer:

class UserSerializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
            super(UserSerializer, self).__init__(*args, **kwargs) # call the super() 
            for field in self.fields: # iterate over the serializer fields
                self.fields[field].error_messages['required'] = 'Enter a valid %s.'%field # set the custom error message

    class Meta:
        model = User
        fields = ('username', 'password', 'email', 'userextended')

        extra_kwargs = 
                    'password': 
                        'write_only': True,
                    
                

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

我的问题是,如果最终用户在提交之前没有填写表单的“国家”部分,Django 会向前端发送一条错误消息,说“输入有效的用户扩展”。 “userextended”是反向关系的名称,每个用户对象都链接到一个用户扩展对象,反之亦然。 “userextended”是强制性的,但最终用户在创建用户对象时不必指定“userextended”字段,因为这已经是给定的(最终用户创建用户对象的那一刻,用户对象将具有反向关系默认情况下使用 UserExtended 对象)。

最终用户必须填写表单中的“国家/地区”部分,因为国家/地区是最终用户的要求。话虽如此,我如何让 Django 说“输入一个有效的国家”而不是“输入一个有效的用户扩展”?

如果最终用户成功保存了一个用户对象,并在表单的“国家”部分提交了“加拿大”,那么DRF如何知道将“加拿大”保存为国家?因为目前,DRF 似乎认为表单中的“国家/地区”部分指的是“用户扩展”反向关系字段。

我期望的是,在用户序列化程序的“字段”数组中,我使用点表示法?像这样?:

fields = ('username', 'password', 'email', 'userextended.country')

编辑:我刚刚也创建了一个 UserExtended 序列化程序,如下所示:

class UserExtendedSerializer(serializers.ModelSerializer):

    class Meta:
        model = UserExtended
        fields = ('country')

但我不知道如何将此序列化程序与原始 UserSerializer “合并”。

【问题讨论】:

您是否尝试过为 UserExtended 创建一个序列化程序并将其添加到您的 UserSerializer 中? @ejey 是的,我只是尝试按照 IgorPomaranskiy 下面的建议进行操作(请参阅答案部分)。我评论了他的回复,并提到了为什么该解决方案似乎不起作用。 【参考方案1】:

Igor 给出的解决方案仅在您想获取用户详细信息时才有效,因为 read_only 字段。默认情况下,序列化程序不支持嵌套写入。要编写嵌套对象,您应该重写 UserSerializer 的 create 方法。

class UserSerializer(serializers.ModelSerializer):

    def __init__(self, *args, **kwargs):
            super(UserSerializer, self).__init__(*args, **kwargs) # call the super()
            for field in self.fields: # iterate over the serializer fields
                self.fields[field].error_messages['required'] = 'Enter a valid %s.'%field # set the custom error message

    country = serializers.RelatedField(source='userextended.country')

    class Meta:
        model = User
        fields = ('username', 'password', 'email', 'country')

        extra_kwargs = 
                    'password': 
                        'write_only': True,
                    
                

    def create(self, validated_data):
        user = User.objects.create_user(
            email = validated_data['email'],
            username = validated_data['username'],
            password = validated_data['password'],
        )
        country=Country.objects.get(pk=validated_data['country'])
        UserExtended.objects.create(user=user, country=country)

        return user

创建时要发送的数据:

'username':'sachin', 'email': 'email@gmail.com', 'password': 'abc', 'country':2

【讨论】:

谢谢。如果您在此线程上查看 KevinStone 的答案:***.com/questions/19772457/… 他还提到要实现自定义 restore_object() 方法。我做了研究,并意识到在 DRF 3 中 restore_object() 已被 create() 和 update() 替换:django-rest-framework.org/topics/3.0-announcement/#serializers 在您的回答中,您定义了 create,但是在这种情况下定义 update() 的正确方法是什么? 另外,当我尝试您在上面发布的代码时,我收到 AssesrtionError 说“关系字段必须提供 queryset 参数,或设置 read_only=True。”它指的是“country = serializers.RelatedField(source='userextended.country')”这一行。我怎么会收到这个错误?当我添加“queryset=UserExtended.objects.all()”时,我收到一条错误消息,提示“必须实现 RelatedField.to_internal_value()。”。知道为什么会出现这两个错误吗? 更新函数的语法是这个 def update(self, instance, valid_data): 你可以在 ModelSerializer 类中看到 rest_framework/serializers.py。对于 country ,可以设置 read_only=True 则不会出现错误,但是由于您已经覆盖了 create 函数 country 将被更新。 还有一件事,我建议您使用自定义用户模型而不是一对一关系。我使用了这种方法,你会时不时地陷入这样的问题。您可以阅读这篇文章 caktusgroup.com/blog/2013/08/07/… 以及 django 文档 感谢您的澄清。最后,您使用了“country = serializers.RelatedField(source='userextended.country')”。是否应该改为 PrimaryKeyRelatedField(因为在要发送的数据示例中,您为“国家”字段发送了一个整数 - 我假设它是主键)?在这里:django-rest-framework.org/api-guide/relations/… 它提到“PrimaryKeyRelatedField 可用于使用其主键来表示关系的目标。”那你为什么选择使用“RelatedField”而不是“PrimaryKeyRelatedField”?【参考方案2】:

你应该做下一个:

    为 UserExtended 模型实例编写序列化程序。 使用此序列化程序序列化userextended 字段。

类似这样的:

class UserExtendedSerializer(serializers.ModelSerializer):
# define serializer here...

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ('username', 'password', 'email', 'userextended')

    userextended = UserExtendedSerializer(read_only=True)

【讨论】:

当我这样做时,最终用户不需要填写表单的“国家”部分,Django 也不会向前端发送“请输入有效的用户扩展”消息.似乎此解决方案在创建 UserSerializer 对象时使最终用户的 userextended / Country 字段不是必需的。 Please enter a valid userextended 无论如何都是无用的消息。如果您有一个表单,错误消息应该与确切的字段或整个表单相关(例如,当数据库访问出现问题时)。 如果您必须在此序列化程序中保存扩展的用户配置文件,请尝试userextended = UserExtendedSerializer()(不带read_only=True)。 Right,"Please enter a valid userextended" 没用,这是我的问题。 Django 没有看到“国家”是必填字段,它认为用户扩展(OneToOneField 关系)是最终用户所需要的。如何使“国家”字段成为必需的?如果我做 read_only=True 那么Django允许用户提交表单而不填写“userextended”字段。如果我执行 read_only=False ,那么 Django 会再次发送一条错误消息,提示“请输入有效的用户扩展名”,因为 Django 不知道国家/地区字段是必需的。 你是UserExtendedSerializer吗?您是否为Country 制作了序列化程序? UserExtendedSerializer 和/或模型中是否需要 country

以上是关于DjangoRestFramework - 如何使用模型序列化程序访问 OneToOneField 反向关系的其他字段?的主要内容,如果未能解决你的问题,请参考以下文章

djangorestframework 可浏览 api:如何显示所有可用的端点 url?

如何解码令牌并获取 Django 的 djangorestframework-jwt 包的信息

DjangoRestFramework - 如何使用模型序列化程序访问 OneToOneField 反向关系的其他字段?

需要如何使用 djangorestframework-api-key 的示例

如何使用 DRF djangorestframework-simplejwt 包将 JWT 令牌存储在 HttpOnly cookie 中?

如何利用 Django 进行 API 开发