Django Rest Framework 序列化程序中的循环依赖

Posted

技术标签:

【中文标题】Django Rest Framework 序列化程序中的循环依赖【英文标题】:Circular dependency in Django Rest Framework serializers 【发布时间】:2016-01-29 13:16:15 【问题描述】:

我正在与使用 Django Rest Framework 3 编写的 Web API 中的序列化程序中的循环依赖作斗争。虽然我知道项目中的循环依赖几乎总是设计不良的标志,但我找不到合适的方法避免它而不会使应用程序成为一个巨大的单体噩梦。

一个简单的精简示例图片,在我遇到类似问题的所有地方都发生了什么。

让我们在两个应用中创建两个简单的模型:

个人资料应用

# profiles/models.py

from images.models import Image

class Profile(models.Model):
    name = models.CharField(max_length=140)  

    def recent_images(self):
        return Image.objects.recent_images_for_user(self)

图片应用

# images/models.py

class Image(models.Model):
    profile = models.ForeignKey('profiles.Profile')
    title = models.CharField(max_length=140)

遵循胖模型的原则 我经常在我的模型中使用多个导入,以便使用 Profile 上的方法轻松检索相关对象,但这很少会导致循环依赖,因为我很少从另一端。

当我尝试将 serializers 添加到一堆时,问题就开始了。为了使 API 占用空间更小并将必要的调用量限制在最低限度,我想在两端以简化形式序列化一些相关对象。

我希望能够在/profile 端点上检索配置文件,该配置文件将简化有关嵌套用户创建的少数最近图像的信息。此外,当从 /images 端点检索图像时,我希望将配置文件信息嵌入图像 JSON 中。

为了实现这一点并避免递归嵌套,我有两个序列化程序——一个嵌套相关对象,一个不嵌套,用于两个应用程序。

个人资料应用

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

图片应用

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

预期的行为是获得以下 JSON 结果:

位于 /profiles 的个人资料应用

[
    'name': 'Test profile',
    'recent_images': [
        'title': 'Test image 1'
    , 
        'title': 'Test image 2'
    ]
]]

图片应用位于 /images

[
    'title': 'Test image 1',
    'profile': 
        'name': 'Test profile'
    
,

    'title': 'Test image 2',
    'profile': 
        'name': 'Test profile'
    
]

但后来我用循环导入的序列化程序碰壁了。

我觉得将这两个应用程序合二为一绝对不可行 - 毕竟,图像与用户个人资料完全不同。

在我看来,序列化程序也应该属于它们各自的应用程序。

到目前为止,我发现解决此问题的唯一方法是导入方法如下:

class ImageSerializer(SimplifiedProfileSerializer):
    profile = SerializerMethodField()

    def get_profile(self, instance):
        from profiles.serializers import SimplifiedProfileSerializer
        return SimplifiedProfileSerializer(instance.profile).data

但这感觉就像一个 uglyuglyuuuugly hack。

您能分享一下您遇到类似问题的经验吗?

谢谢!

【问题讨论】:

谢谢你的丑陋,丑陋,uuuugly hack 您也可以暂时使用 BaseSerializer,然后在可用时用适当的序列化程序覆盖它 【参考方案1】:

在我看来,您的代码很好,因为您没有 logic 循环依赖。

您的ImportError 仅因import() 在调用时评估整个文件的***语句的方式而引发。

然而,在 python 中没有什么是不可能的......

如果您确实希望您的导入位于顶部,则有一种解决方法

来自 David Beazley 的精彩演讲 Modules and Packages: Live and Let Die! - PyCon 2015、1:54:00,这里有一种在 python 中处理循环导入的方法:

try:
    from images.serializers import SimplifiedImageSerializer
except ImportError:
    import sys
    SimplifiedImageSerializer = sys.modules[__package__ + '.SimplifiedImageSerializer']

这会尝试导入SimplifiedImageSerializer,如果引发ImportError,因为它已经被导入,它会从导入缓存中拉取它。

PS:你必须用 David Beazley 的声音阅读整篇文章。

【讨论】:

谢谢,我一定会完整观看 PyCon 演示文稿!我担心在可以被多次调用的方法中发出导入语句可能会影响性能,但了解模块缓存的工作原理似乎可以减轻这种特殊的痛苦。所以你要说的是,它归结为循环依赖实际上是一个硬依赖(如果删除该依赖,代码将没有意义),或者仅仅是一个循环导入问题,由便利辅助方法如何依赖来自其他模块的类? import() 在执行时将始终运行文件中的所有***语句。如果有的话,延迟加载可能会更具响应性。如果我的回答回答了你的问题,请采纳。 是的,存在无法克服的循环依赖关系,但通常只是 import() 阻碍。 @SebastianWozny 为什么 Python 默认不这样做? @SebastianWozny 嗨,我在尝试此操作时遇到 Key 错误,是否应该在两个文件中都尝试此操作。【参考方案2】:

我会采取不同的方法,因为您确实以一种或另一种方式耦合。 我会定义我在应用程序本身中实际使用的序列化程序。

档案申请:

# profiles/serializers.py

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

图像应用:

# images/serializers.py

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

【讨论】:

我不这样做是有原因的,除了我提到的(序列化程序应该与模型属于同一个应用程序)。当您有两个像这样交织在一起的模型时,这可能是一个解决方案,但是如果在引用同一模型的其他应用程序中使用简化的序列化程序,那么当在使用它们的应用程序中有简单的序列化程序时,就会出现序列化程序重复问题。 足够公平,那么我可能会在每个模块中有两个不同的文件,其中包含基本序列化程序和嵌套序列化程序,这样我就可以区分它们的用途。【参考方案3】:

分离普通序列化器和嵌套序列化器对我有用。

对于您的结构,它将类似于:

个人资料应用

# profiles/serializers/common.py

from images.serializers.nested import SimplifiedImageSerializer

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)

并嵌套:

# profiles/serializers/nested.py

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()

图片应用

# images/serializers/common.py

from profiles.serializers.nested import SimplifiedProfileSerializer

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

并嵌套:

# images/serializers/nested.py

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()

【讨论】:

【参考方案4】:

您可以像这样在本地导入序列化程序:

class MySerializer(Serializer):
    from app.core.serializers import AnotherSerializer

在您的两个导入中都这样做。无需使用 sys.modules

也就是说,正如 Sebastian Wozny 所说,你没有逻辑循环依赖

【讨论】:

这种语句在函数内部起作用,但在类内部不起作用。当你在函数中使用这些语句时,它们不会在启动时被评估,但是当它们在一个类中时,它们将像***语句一样被评估【参考方案5】:

我在 Django Serializers 循环依赖问题上受了很多苦,发现只有两种方法可以解决。

    以某种方式安排我的代码,这样我就不必面对循环依赖(这在我的情况下是不可能的) 使用我需要的序列化程序创建单独的序列化程序类,并在我需要的地方使用这个新的序列化程序。这个可能不是最有效的,但这解决了我的问题。

【讨论】:

【参考方案6】:

您应该考虑查看 Rest Framework 文档中的 Specifying nested serialization。使用depth 元属性可以让您将相关对象检索到您设置的深度。

非常方便,避免两边都使用序列化器,避免循环导致 ImportError。

默认的 ModelSerializer 使用主键来表示关系,但您也可以使用深度选项轻松生成嵌套表示:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']
        depth = 1

【讨论】:

以上是关于Django Rest Framework 序列化程序中的循环依赖的主要内容,如果未能解决你的问题,请参考以下文章

python django-rest-framework 3.3.3 更新嵌套序列化程序

django-rest-framework、多表模型继承、ModelSerializers 和嵌套序列化器

Django.rest_framework:如何序列化一对多?

Django 序列化器与 rest_framework 序列化器

Django-Rest-Framework 中的序列化程序问题

django.core 序列化器和 Django Rest Framework 序列化器之间的区别