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

Mat*_*nik 25 django serialization circular-dependency python-import django-rest-framework

我正在使用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)
Run Code Online (Sandbox Code Playgroud)

图像应用程序

# images/models.py

class Image(models.Model):
    profile = models.ForeignKey('profiles.Profile')
    title = models.CharField(max_length=140)
Run Code Online (Sandbox Code Playgroud)

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

当我尝试向串口添加序列化器时,问题就开始了.为了缩小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)
Run Code Online (Sandbox Code Playgroud)

图像应用程序

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()
Run Code Online (Sandbox Code Playgroud)

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

/ profile的个人资料应用

[{
    'name': 'Test profile',
    'recent_images': [{
        'title': 'Test image 1'
    }, {
        'title': 'Test image 2'
    }]
]]
Run Code Online (Sandbox Code Playgroud)

图像应用程序在/图像

[{
    'title': 'Test image 1',
    'profile': {
        'name': 'Test profile'
    }
},
{
    'title': 'Test image 2',
    'profile': {
        'name': 'Test profile'
    }
}]
Run Code Online (Sandbox Code Playgroud)

但后来我用循环导入的序列化器撞墙了.

我觉得将这两个应用程序合二为一,绝对不是要走的路 - 毕竟,图像与用户配置文件完全不同.

在我看来,序列化程序也应属于各自的应用程序.

我现在发现的解决这个问题的唯一方法是导入方法如下:

class ImageSerializer(SimplifiedProfileSerializer):
    profile = SerializerMethodField()

    def get_profile(self, instance):
        from profiles.serializers import SimplifiedProfileSerializer
        return SimplifiedProfileSerializer(instance.profile).data
Run Code Online (Sandbox Code Playgroud)

但这感觉就像一个丑陋,丑陋,丑陋的黑客.

你能否分享一下类似问题的经验?

谢谢!

Seb*_*zny 21

在我看来,你的代码很好,因为你没有逻辑循环依赖.

ImportError才升起的,因为这样import()叫时估算整个文件的顶层语句.

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

如果你肯定希望你的进口位于最前面,那就有办法解决这个问题:

来自David Beazleys的优秀演讲模块和套餐: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']
Run Code Online (Sandbox Code Playgroud)

这会尝试导入SimplifiedImageSerializer,如果ImportError被引发,因为它已经被导入,它将从importcache中提取它.

PS:你必须以David Beazley的声音阅读这整篇文章.

  • @SebastianWozny 你好,当我尝试这个时,我收到了 Key 错误,我应该在两个文件中都尝试这个吗? (4认同)
  • @SebastianWozny 为什么 Python 默认不这样做? (2认同)

And*_*nos 6

分离普通序列化器和嵌套序列化器对我来说很有效。

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

个人资料应用程序

# profiles/serializers/common.py

from images.serializers.nested import SimplifiedImageSerializer

class ProfileSerializer(SimplifiedProfileSerializer):
    recent_images = SimplifiedImageSerializer(many=True)
Run Code Online (Sandbox Code Playgroud)

并嵌套:

# profiles/serializers/nested.py

class SimplifiedProfileSerializer(serializers.Serializer):
    name = serializers.CharField()
Run Code Online (Sandbox Code Playgroud)

图片应用程序

# images/serializers/common.py

from profiles.serializers.nested import SimplifiedProfileSerializer

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()
Run Code Online (Sandbox Code Playgroud)

并嵌套:

# images/serializers/nested.py

class SimplifiedImageSerializer(serializers.Serializer):
    title = serializers.CharField()
Run Code Online (Sandbox Code Playgroud)