13

मैं निम्नलिखित मॉडल:अनुकूलन डेटाबेस प्रश्नों

class User(models.Model): 
    name = models.Charfield() 
    email = models.EmailField() 

class Friendship(models.Model): 
    from_friend = models.ForeignKey(User) 
    to_friend = models.ForeignKey(User) 

और उन मॉडलों निम्नलिखित दृश्य में उपयोग किया जाता है और serializer:

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 

तो बुनियादी तौर पर, प्रत्येक उपयोगकर्ता के लिए GetAllUsers व्यू द्वारा लौटाया गया, मैं यह मुद्रित करना चाहता हूं कि उपयोगकर्ता अनुरोधकर्ता के साथ एक मित्र है (वास्तव में मुझे दोनों से_ और to_friend की जांच करनी चाहिए, लेकिन इस मामले में कोई फर्क नहीं पड़ता)

मैं क्या देख डेटाबेस में एन उपयोगकर्ताओं के लिए, वहाँ serializer के get_is_friend_already

में सभी N उपयोगकर्ता प्राप्त करने के लिए 1 क्वेरी, और फिर 1xN प्रश्नों है कि एक तरह से आराम-ढांचे तरीके से इस से बचने के लिए होती है ? शायद select_related पास करने जैसी कुछ चीज़ों में सेरिअलाइज़र को क्वेरी शामिल है जिसमें प्रासंगिक Friendship पंक्तियां हैं?

उत्तर

19

Django रीस्ट फ्रेमवर्क स्वचालित रूप से आपके लिए प्रश्नों को अनुकूलित नहीं कर सकता है, वैसे ही Django स्वयं नहीं करेगा। ऐसी जगहें हैं जिन्हें आप युक्तियों के लिए देख सकते हैं, including the Django documentation। यह has been mentioned है कि Django रीस्ट फ्रेमवर्क स्वचालित रूप से होना चाहिए, हालांकि इसके साथ जुड़े कुछ चुनौतियां हैं।

यह प्रश्न आपके मामले के लिए बहुत विशिष्ट है, जहां आप एक कस्टम SerializerMethodField का उपयोग कर रहे हैं जो लौटाए गए प्रत्येक ऑब्जेक्ट का अनुरोध करता है। क्योंकि आप एक नया अनुरोध कर रहे हैं (Friends.objects प्रबंधक का उपयोग करके), क्वेरी को अनुकूलित करना बहुत मुश्किल है।

हालांकि आप नई क्वेरीसेट नहीं बनाकर और अन्य जगहों से मित्र को गिनने के बजाय समस्या को बेहतर बना सकते हैं। इसके लिए Friendship मॉडल पर बनाए जाने वाले पीछे के संबंध की आवश्यकता होगी, जो संभवतया related_name पैरामीटर के माध्यम से क्षेत्र में पैरामीटर के माध्यम से संभव है, ताकि आप Friendship ऑब्जेक्ट्स को प्रीफ़ेच कर सकें। लेकिन यह केवल तभी उपयोगी होता है जब आपको पूर्ण वस्तुओं की आवश्यकता होती है, न केवल वस्तुओं की गिनती।

यह एक दृश्य और निम्न के समान serializer परिणाम होगा:

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() उपयोग करने के समान) की जरूरत है, तो आप में पंक्तियों व्याख्या शामिल कर सकते हैं रिवर्स रिश्तों की गिनती के साथ क्वेरीसेट। यह विधि में .annotate(friends_count=Count("friends")) को अंत में जोड़कर किया जाएगा (यदि related_namefriends था), जो प्रत्येक ऑब्जेक्ट पर friends_count विशेषता को मित्रों की संख्या पर सेट करेगा।

यह एक दृश्य और निम्न के समान serializer परिणाम होगा:

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 
इन समाधानों में से

दोनों एन 1 प्रश्नों से बचने जाएगा, लेकिन एक आप लेने आप क्या हासिल करने की कोशिश कर रहे हैं पर निर्भर करता है।

+0

+1 महान जवाब केविन! – Fiver

+0

ग्रेट उत्तर केविन। बहुत बहुत धन्यवाद। केवल थोड़ी सी बात यह है कि obj.friends में दोस्त के बजाय, मुझे फोन करने की ज़रूरत है: दोस्त के लिए obj.friends.all() .. संबंधित धागा यहां है: http://stackoverflow.com/questions/6314841/ typeerror-relatedmanager-object-is-not-iterable – dowjones123

+0

"prefetch_related" वाला पहला दृष्टिकोण बोझिल होगा यदि उपयोगकर्ता के हजारों मित्र थे। उस मामले में प्रत्येक उपयोगकर्ता – xleon

7

वर्णित एन 1 समस्या, Django बाकी फ्रेमवर्क प्रदर्शन के अनुकूलन के दौरान एक नंबर एक मुद्दा है इसलिए विभिन्न राय से, यह अधिक ठोस दृष्टिकोण की आवश्यकता है तो get_queryset() दृश्य विधि में prefetch_related() या select_related() प्रत्यक्ष।

एकत्रित जानकारी के आधार पर, यहां एक मजबूत समाधान है जो एन + 1 (उदाहरण के रूप में ओपी के कोड का उपयोग करके) को समाप्त करता है। यह सजावट पर आधारित है और बड़े अनुप्रयोगों के लिए थोड़ा कम युग्मित है।

Serializer:

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 serializer विधि में परिभाषित एक मॉडल के लिए संबंधित रिकॉर्ड लाने के लिए में क्वेरीसमूह लौटे संशोधित करता है।

दृश्य:

class GetAllUsers(generics.ListAPIView): 
    serializer_class = GetAllUsersSerializer 

    @setup_eager_loading 
    def get_queryset(self): 
     return User.objects.all() 

यह पैटर्न overkill की तरह लग सकता है, लेकिन यह निश्चित रूप से अधिक सूखी है और विचारों के अंदर प्रत्यक्ष क्वेरीसमूह संशोधन से अधिक लाभ दिया है, के रूप में यह संबंधित संस्थाओं पर अधिक नियंत्रण की अनुमति देता है और के अनावश्यक नेस्टिंग समाप्त संबंधित वस्तुओं।

0

आप दृश्य को दो क्वेरी में विभाजित कर सकते हैं।
सबसे पहले, केवल उपयोगकर्ता सूची प्राप्त करें (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) 
0
from rest_framework import serializers 
from rest_framework.utils import model_meta 


class DeclarativeModelViewSetMetaclass(type): 
    """ 
    Metaclass to prefetch and select related objects of the queryset. 
    """ 
    @classmethod 
    def get_many_to_many_rel(cls, info, meta_fields): 
     many_to_many_fields = [] 
     for field_name, relation_info in info.relations.items(): 
      if relation_info.to_many and field_name in meta_fields: 
       many_to_many_fields.append(field_name) 
     return many_to_many_fields 

    @classmethod 
    def get_forward_rel(cls, info, meta_fields): 
     related_fields = [] 
     for field_name, relation_info in info.forward_relations.items(): 
      if field_name in meta_fields: 
       related_fields.append(field_name) 
     return related_fields 

    def __new__(cls, name, bases, attrs): 
     serializer_class = attrs.get('serializer_class', None) 
     many_to_many_fields = [] 
     related_fields = [] 

     for base in reversed(bases): 
      if hasattr(base, '_base_forward_rel'): 
       related_fields.extend(list(base._base_forward_rel)) 
     if serializer_class and issubclass(serializer_class, serializers.ModelSerializer): 
      if hasattr(serializer_class.Meta, 'model'): 
       info = model_meta.get_field_info(serializer_class.Meta.model) 
       meta_fields = tuple(serializer_class.Meta.fields) 
       many_to_many_fields.extend(cls.get_many_to_many_rel(info, meta_fields)) 
       related_fields.extend(cls.get_forward_rel(info, meta_fields)) 

     queryset = attrs.get('queryset', None) 
     if queryset: 
      if many_to_many_fields: 
       queryset = queryset.prefetch_related(*many_to_many_fields) 
      if related_fields: 
       queryset = queryset.select_related(*related_fields) 
      attrs['queryset'] = queryset.all() 
     return super(DeclarativeModelViewSetMetaclass, cls).__new__(cls, name, bases, attrs) 
संबंधित मुद्दे