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

时间:2022-10-01 11:58:39

I'm fighting with circular dependencies within serializers in my web API written using Django Rest Framework 3. Whereas I know that circular dependencies in a project is almost always a sign of bad design, I can't find a decent way of avoiding it without making the app a big monolithic nightmare.

我正在使用Django Rest Framework 3编写的Web API中的序列化器中的循环依赖性进行斗争。虽然我知道项目中的循环依赖几乎总是设计糟糕的标志,但我找不到一个好的方法来避免它使应用程序成为一个巨大的整体噩梦。

A simple stripped down example pictures well enough what happens in all places I'm having the similar problem.

一个简单的剥离示例很好地描绘了在所有地方发生的事情,我遇到了类似的问题。

Let's have two simple models in two apps:

让我们在两个应用程序中有两个简单的模型:

Profiles app

# 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 app

# images/models.py

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

Following the principle of fat models I often use multiple imports in my models to allow easy retrieval of related objects using methods on Profile, but that rarely causes circular dependencies, since I rarely do the same from the other end.

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

The problem begins when I try to add serializers to the bunch. To make the API footprint small and limit the amount of necessary calls to the minimum, I want to serialize on both ends some of the related objects in their simplified forms.

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

I want to be able to retrieve profiles on /profile endpoint that will have simplified info about few recent images created by the user nested. Also, when retrieving images from /images endpoint I'd like to have profile info embedded in the image JSON.

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

To achieve this and avoid recursive nesting, I have two serializers - one that nests related objects, and one that does not, for both apps.

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

Profiles app

# profiles/serializers.py

from images.serializers import SimplifiedImageSerializer

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

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

Images app

# images/serializers.py

from profiles.serializers import SimplifiedProfileSerializer

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

The expected behaviour is to get the following JSON results:

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

Profiles app at /profiles

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

Images app at /images

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

but then I hit the wall with circular imports of the serializers.

但后来我用循环导入的序列化器击中了墙。

I feel that joining those two apps into one is definitely not the road to take - after all, images are something completely different from user profiles.

我认为将这两个应用程序合二为一,绝对不是可行之路 - 毕竟,图像与用户配置文件完全不同。

The serializers also in my view should belong to their respective apps.

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

The only way to go around this problem I found as of now is import in the method as follows:

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

class ImageSerializer(SimplifiedProfileSerializer):
    profile = SerializerMethodField()

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

but that feels like an ugly, ugly, uuuugly hack.

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

Could you please share your experience with similar problems?

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

Thanks!

谢谢!

2 个解决方案

#1


17  

In my opinion your code is fine, because you do not have a logic circular dependency.

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

Your ImportError is only raised because of the way import() evaluates top level statements of the entire file when called.

只会引发ImportError,因为import()在调用时会评估整个文件的*语句。

However, nothing is impossible in python...

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

There is a way around it if you positively want your imports on top:

如果您肯定希望进口最重要,那么可以采取一种方法:

From David Beazleys excellent talk Modules and Packages: Live and Let Die! - PyCon 2015, 1:54:00, here is a way to deal with circular imports in 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']

This tries to import SimplifiedImageSerializer and if ImportError is raised, because it already is imported, it will pull it from the importcache.

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

PS: You have to read this entire post in David Beazley's voice.

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

#2


0  

I'd take a different approche as you do have coupling in a way or another. I'd go with defining the serializer I actually use within the application itself.

我会采取不同的方法,因为你有某种方式的耦合。我将定义我在应用程序本身中实际使用的序列化程序。

Profile application

档案申请

# profiles/serializers.py

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

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

Image application

图像应用

# images/serializers.py

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()

#1


17  

In my opinion your code is fine, because you do not have a logic circular dependency.

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

Your ImportError is only raised because of the way import() evaluates top level statements of the entire file when called.

只会引发ImportError,因为import()在调用时会评估整个文件的*语句。

However, nothing is impossible in python...

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

There is a way around it if you positively want your imports on top:

如果您肯定希望进口最重要,那么可以采取一种方法:

From David Beazleys excellent talk Modules and Packages: Live and Let Die! - PyCon 2015, 1:54:00, here is a way to deal with circular imports in 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']

This tries to import SimplifiedImageSerializer and if ImportError is raised, because it already is imported, it will pull it from the importcache.

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

PS: You have to read this entire post in David Beazley's voice.

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

#2


0  

I'd take a different approche as you do have coupling in a way or another. I'd go with defining the serializer I actually use within the application itself.

我会采取不同的方法,因为你有某种方式的耦合。我将定义我在应用程序本身中实际使用的序列化程序。

Profile application

档案申请

# profiles/serializers.py

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

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

Image application

图像应用

# images/serializers.py

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

class ImageSerializer(SimplifiedImageSerializer):
    profile = SimplifiedProfileSerializer()