3 回答

TA貢獻1802條經驗 獲得超4個贊
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
如果只需要計數對象(類似于使用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
這兩種解決方案都可以避免N + 1個查詢,但是您選擇的查詢取決于您要實現的目標。

TA貢獻1831條經驗 獲得超10個贊
述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
在這里,我們使用靜態類方法來構建特定的查詢集。
裝飾器:
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
此函數修改返回的查詢集,以獲取setup_eager_loading序列化器方法中定義的模型的相關記錄。
視圖:
class GetAllUsers(generics.ListAPIView):
serializer_class = GetAllUsersSerializer
@setup_eager_loading
def get_queryset(self):
return User.objects.all()
這種模式可能看起來像是一個過大的殺手,但是它肯定更干了,并且比直接在視圖內部修改查詢集更具優勢,因為它可以更好地控制相關實體,并消除了不必要的相關對象嵌套。

TA貢獻1799條經驗 獲得超8個贊
您可以將視圖分為兩個查詢。
首先,僅獲取“用戶”列表(無is_friend_already字段)。這僅需要一個查詢。
其次,獲取request.user的好友列表。
第三,根據用戶是否在request.user的朋友列表中來修改結果。
class GetAllUsersSerializer(serializers.ModelSerializer):
...
class UserListView(ListView):
def get(self, request):
friends = request.user.friends
data = []
for user in self.get_queryset():
user_data = GetAllUsersSerializer(user).data
if user in friends:
user_data['is_friend_already'] = True
else:
user_data['is_friend_already'] = False
data.append(user_data)
return Response(status=200, data=data)
添加回答
舉報