dow*_*123 19 python django-orm django-queryset django-select-related django-rest-framework
我有以下型号:
class User(models.Model):
name = models.Charfield()
email = models.EmailField()
class Friendship(models.Model):
from_friend = models.ForeignKey(User)
to_friend = models.ForeignKey(User)
Run Code Online (Sandbox Code Playgroud)
这些模型用于以下视图和序列化程序:
class GetAllUsers(generics.ListAPIView):
authentication_classes = (SessionAuthentication, TokenAuthentication)
permission_classes = (permissions.IsAuthenticated,)
serializer_class = GetAllUsersSerializer
model = User
def get_queryset(self):
return User.objects.all()
class GetAllUsersSerializer(serializers.ModelSerializer):
is_friend_already = serializers.SerializerMethodField('get_is_friend_already')
class Meta:
model = User
fields = ('id', 'name', 'email', 'is_friend_already',)
def get_is_friend_already(self, obj):
request = self.context.get('request', None)
if request.user != obj and Friendship.objects.filter(from_friend = user):
return True
else:
return False
Run Code Online (Sandbox Code Playgroud)
所以基本上,对于GetAllUsers视图返回的每个用户,我想打印出用户是否是请求者的朋友(实际上我应该检查from_和to_friend,但对于问题并不重要)
我看到的是,对于数据库中的N个用户,有1个查询用于获取所有N个用户,然后在序列化程序中查询1xN个查询 get_is_friend_already
有没有办法在休息框架方式中避免这种情况?也许是将select_related包含的查询传递给具有相关Friendship行的序列化程序?
Kev*_*own 28
Django REST Framework不能自动为您优化查询,就像Django本身不会这样.您可以查看有关提示的地方,包括Django文档.它已经提到的是Django的REST框架应该自动,虽然有与之相关的一些挑战.
这个问题非常适合您的情况,您使用的是自定义SerializerMethodField请求返回的每个对象.由于您正在发出新请求(使用Friends.objects管理器),因此优化查询非常困难.
您可以通过不创建新的查询集,而不是从其他位置获取朋友计数来改善问题.这将需要在Friendship模型上创建向后关系,最有可能通过related_name字段上的参数创建,因此您可以预取所有Friendship对象.但这仅在您需要完整对象时才有用,而不仅仅是对象的计数.
这将导致视图和序列化器类似于以下内容:
class Friendship(models.Model):
from_friend = models.ForeignKey(User, related_name="friends")
to_friend = models.ForeignKey(User)
class GetAllUsers(generics.ListAPIView):
...
def get_queryset(self):
return User.objects.all().prefetch_related("friends")
class GetAllUsersSerializer(serializers.ModelSerializer):
...
def get_is_friend_already(self, obj):
request = self.context.get('request', None)
friends = set(friend.from_friend_id for friend in obj.friends)
if request.user != obj and request.user.id in friends:
return True
else:
return False
Run Code Online (Sandbox Code Playgroud)
如果只需要对象的计数(类似于使用queryset.count()或queryset.exists()),则可以在查询集中使用反向关系的计数来注释行.这将在您的get_queryset方法中完成,方法是添加.annotate(friends_count=Count("friends"))到结尾(如果related_name是friends),这将把friends_count每个对象的属性设置为朋友的数量.
这将导致视图和序列化器类似于以下内容:
class Friendship(models.Model):
from_friend = models.ForeignKey(User, related_name="friends")
to_friend = models.ForeignKey(User)
class GetAllUsers(generics.ListAPIView):
...
def get_queryset(self):
from django.db.models import Count
return User.objects.all().annotate(friends_count=Count("friends"))
class GetAllUsersSerializer(serializers.ModelSerializer):
...
def get_is_friend_already(self, obj):
request = self.context.get('request', None)
if request.user != obj and obj.friends_count > 0:
return True
else:
return False
Run Code Online (Sandbox Code Playgroud)
这两种解决方案都可以避免N + 1查询,但您选择的查询取决于您要实现的目标.
Dam*_*nic 14
述N + 1个问题是在一个首要问题Django的REST框架性能优化,所以从各种观点,它需要更多的固体的方法然后直接prefetch_related()或select_related()在get_queryset()视图的方法.
根据收集的信息,这是一个消除N + 1的强大解决方案(以OP的代码为例).它基于装饰器,对于较大的应用程序而言稍微不那么耦合.
串行:
class GetAllUsersSerializer(serializers.ModelSerializer):
friends = FriendSerializer(read_only=True, many=True)
# ...
@staticmethod
def setup_eager_loading(queryset):
queryset = queryset.prefetch_related("friends")
return queryset
Run Code Online (Sandbox Code Playgroud)
这里我们使用静态类方法来构建特定的查询集.
装饰:
def setup_eager_loading(get_queryset):
def decorator(self):
queryset = get_queryset(self)
queryset = self.get_serializer_class().setup_eager_loading(queryset)
return queryset
return decorator
Run Code Online (Sandbox Code Playgroud)
此函数修改返回的查询集,以便获取setup_eager_loading序列化程序方法中定义的模型的相关记录.
视图:
class GetAllUsers(generics.ListAPIView):
serializer_class = GetAllUsersSerializer
@setup_eager_loading
def get_queryset(self):
return User.objects.all()
Run Code Online (Sandbox Code Playgroud)
这种模式可能看起来有点过分,但它肯定更干,并且优于视图内部的直接查询集修改,因为它允许更多地控制相关实体并消除不必要的相关对象嵌套.
| 归档时间: |
|
| 查看次数: |
12768 次 |
| 最近记录: |