2016-07-01 10 views
2

So habe ich diese queryset:Optimierung Djangos count() -Methode

from django.contrib.gis.db.models.query import GeoQuerySet 
from django.db import models as base_models 

class RestaurantsQuerySet(GeoQuerySet): 
    def get_list(self, lng, lat): 
     reference_point = Point(lng, lat, srid=SRID) 

     return self.annotate(rating=models.Avg('comments__rating'))\ 
        .annotate(distance=Distance('location', reference_point)) 

    def count(self): 
     return self.values('id').aggregate(count=base_models.Count('id'))['count'] 

Ich dachte, dass die Abfrage etwas aussehen würde:

SELECT COUNT("__col1") 
FROM (
    SELECT "restaurants_restaurant"."id" AS "__col1" 
    FROM "restaurants_restaurant" 
    GROUP BY "restaurants_restaurant"."id") subquery 

Und statt django ORM schafft diese kleine Ungeheuerlichkeit:

SELECT COUNT("__col1") 
    FROM (
     SELECT "restaurants_restaurant"."id" AS Col1, "restaurants_restaurant"."id" AS "__col1" 
     FROM "restaurants_restaurant" 
     LEFT OUTER JOIN "comments_comment" ON ("restaurants_restaurant"."id" = "comments_comment"."restaurant_id") 
     GROUP BY "restaurants_restaurant"."id", ST_Distance_Sphere("restaurants_restaurant"."location", 
       ST_GeomFromEWKB('\x0101000020e61000003eb555a41d2d4b405a338d81d0a73240'::bytea 
))) subquery 

Die erste aufgerufene Methode ist get_list. Es sieht so aus, als ob sich der Django an diesen Aufruf "erinnern" würde und dass das Qs mit rating und distance annotiert wurde und es ebenfalls in die count Abfrage platziert. Ich denke also, die Frage ist - wie setze ich dieses Queryset in den Zustand zurück, bevor ich es annotiere?

EDIT:

Okay, scheint meine Frage nicht vollständig war. Ich habe auch eine RestaurantsList Ansicht wie folgt definiert:

class RestaurantList(generics.ListAPIView): 
    def get_queryset(self): 
     return Restaurant.objects.get_list(self._lng, self._lat) 

Ich warf einen Blick ins Innere der django-Rest-Rahmen und ich kann diese sehen:

class ListModelMixin(object): 
    """ 
    List a queryset. 
    """ 
    def list(self, request, *args, **kwargs): 
     queryset = self.filter_queryset(self.get_queryset()) 

     page = self.paginate_queryset(queryset) 
     if page is not None: 
      serializer = self.get_serializer(page, many=True) 
      return self.get_paginated_response(serializer.data) 

     serializer = self.get_serializer(queryset, many=True) 
     return Response(serializer.data) 

So sieht es aus wie es immer verwendet das Abfrage-Set, das von der get_queryset-Methode zurückgegeben wurde, und seit es mit distance und rating annotiert ist, wird es in der Count-Abfrage enthalten. Immer noch keine Lösung dieses ...

Antwort

0

Nach dem Surfen durch Code django-Rest-Rahmen kam ich mit dieser Idee:

  1. außer Kraft setzt Standard Paginator Klasse für eine bestimmte Ansicht:

    class RestaurantList(generics.ListAPIView): 
        pagination_class = custom.LimitOffsetPagination 
    
  2. erstellen von benutzerdefinierten Paginierung Klasse:

    class LimitOffsetPagination(pagination.LimitOffsetPagination): 
        def __init__(self): 
         self._countable_queryset = None 
         self._was_counted = False 
    
        def was_initialized(self): 
         return self._countable_queryset is not None 
    
        def set_raw_queryset_for_count(self, queryset: QuerySet): 
         self._countable_queryset = queryset 
    
        def paginate_queryset(self, queryset, request, view=None): 
         self.limit = self.get_limit(request) 
         if self.limit is None: 
          return None 
         self.offset = self.get_offset(request) 
         self.count = self._countable_queryset.count() 
         self.request = request 
         if self.count > self.limit and self.template is not None: 
          self.display_page_controls = True 
         return list(queryset[self.offset:self.offset + self.limit]) 
    
  3. Aufschalten Ansichten paginator Eigenschaft:

    @property 
    def paginator(self): 
        paginator = super().paginator 
        if not paginator.was_initialized(): 
         paginator.set_raw_queryset_for_count(Restaurant.objects.all()) 
        return paginator 
    

nun die Zählung Abfrage ein wenig freundlichen

SELECT COUNT(*) AS "__count" FROM "restaurants_restaurant" 
aussehen