Django Rest Framework 序列化程序中的循环依赖
Circular dependency in Django Rest Framework serializers
我在使用 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)
遵循胖模型的原则我经常在我的模型中使用多个导入,以便使用配置文件上的方法轻松检索相关对象,但这很少导致循环依赖,因为我很少从另一端做同样的事情。
当我尝试将 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
但这感觉像是 丑陋、丑陋、uuuugly hack。
能否请您分享您遇到类似问题的经验?
谢谢!
我认为你的代码很好,因为你没有逻辑循环依赖。
您的 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 的声音阅读整个 post。
我会采取不同的方法,因为你确实有这样或那样的耦合。
我将继续定义我在应用程序本身中实际使用的序列化程序。
个人资料申请:
# 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()
分离普通序列化程序和嵌套序列化程序对我有用。
对于您的结构,它将类似于:
配置文件应用程序
# 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()
您可以像这样在本地导入序列化程序:
class MySerializer(Serializer):
from app.core.serializers import AnotherSerializer
在您的两个导入中都这样做。无需使用 sys.modules
也就是说,正如 Sebastian Wozny 所提到的,您没有逻辑循环依赖
我在 Django Serializers Circular Dependency 问题上受了很多苦,只找到了两种解决方法。
- 以某种方式安排我的代码,这样我就不必面对循环依赖(这在我的情况下是不可能的)
- 使用我需要的序列化程序创建单独的序列化程序 class 并在我需要的地方使用这个新的序列化程序。这个可能不是最有效的,但这解决了我的问题。
您应该考虑查看 Rest Framework 文档中的 Specifying nested serialization。 depth
元属性的使用使您能够检索相关对象到您设置的深度。
可以很方便的避免两边都使用serializers而出现循环导致的ImportError
The default ModelSerializer uses primary keys for relationships, but you can also easily generate nested representations using the depth option:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
depth = 1
我在使用 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)
遵循胖模型的原则我经常在我的模型中使用多个导入,以便使用配置文件上的方法轻松检索相关对象,但这很少导致循环依赖,因为我很少从另一端做同样的事情。
当我尝试将 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
但这感觉像是 丑陋、丑陋、uuuugly hack。
能否请您分享您遇到类似问题的经验?
谢谢!
我认为你的代码很好,因为你没有逻辑循环依赖。
您的 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 的声音阅读整个 post。
我会采取不同的方法,因为你确实有这样或那样的耦合。 我将继续定义我在应用程序本身中实际使用的序列化程序。
个人资料申请:
# 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()
分离普通序列化程序和嵌套序列化程序对我有用。
对于您的结构,它将类似于:
配置文件应用程序
# 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()
您可以像这样在本地导入序列化程序:
class MySerializer(Serializer):
from app.core.serializers import AnotherSerializer
在您的两个导入中都这样做。无需使用 sys.modules
也就是说,正如 Sebastian Wozny 所提到的,您没有逻辑循环依赖
我在 Django Serializers Circular Dependency 问题上受了很多苦,只找到了两种解决方法。
- 以某种方式安排我的代码,这样我就不必面对循环依赖(这在我的情况下是不可能的)
- 使用我需要的序列化程序创建单独的序列化程序 class 并在我需要的地方使用这个新的序列化程序。这个可能不是最有效的,但这解决了我的问题。
您应该考虑查看 Rest Framework 文档中的 Specifying nested serialization。 depth
元属性的使用使您能够检索相关对象到您设置的深度。
可以很方便的避免两边都使用serializers而出现循环导致的ImportError
The default ModelSerializer uses primary keys for relationships, but you can also easily generate nested representations using the depth option:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = Account
fields = ['id', 'account_name', 'users', 'created']
depth = 1