2016-03-09 32 views
5

如何在elasticsearch dsl上使用django分頁。 我的代碼:Python elasticsearch-dsl django分頁

query = MultiMatch(query=q, fields=['title', 'body'], fuzziness='AUTO') 

s = Search(using=elastic_client, index='post').query(query).sort('-created_at') 
response = s.execute() 

// this always returns page count 1 
paginator = Paginator(response, 100) 
page = request.GET.get('page') 
try: 
    posts = paginator.page(page) 
except PageNotAnInteger: 
    posts = paginator.page(1) 
except EmptyPage: 
    posts = paginator.page(paginator.num_pages) 

任何解決方案?

回答

9

我發現這個分頁程序對這個link

from django.core.paginator import Paginator, Page 

class DSEPaginator(Paginator): 
    """ 
    Override Django's built-in Paginator class to take in a count/total number of items; 
    Elasticsearch provides the total as a part of the query results, so we can minimize hits. 
    """ 
    def __init__(self, *args, **kwargs): 
     super(DSEPaginator, self).__init__(*args, **kwargs) 
     self._count = self.object_list.hits.total 

    def page(self, number): 
     # this is overridden to prevent any slicing of the object_list - Elasticsearch has 
     # returned the sliced data already. 
     number = self.validate_number(number) 
     return Page(self.object_list, number, self) 

,然後在視圖中使用:

q = request.GET.get('q', None) 
    page = int(request.GET.get('page', '1')) 
    start = (page-1) * 10 
    end = start + 10 

    query = MultiMatch(query=q, fields=['title', 'body'], fuzziness='AUTO') 
    s = Search(using=elastic_client, index='post').query(query)[start:end] 
    response = s.execute() 

    paginator = DSEPaginator(response, settings.POSTS_PER_PAGE) 
    try: 
     posts = paginator.page(page) 
    except PageNotAnInteger: 
     posts = paginator.page(1) 
    except EmptyPage: 
     posts = paginator.page(paginator.num_pages) 

這樣它完美..

+0

本例中'count'屬性只顯示頁面中的項目數量而非總數。您可以覆蓋分頁程序的'count' cached_property以返回'_count'作爲總計數 – Nasir

0

着另一種方法是創建Paginator與Elasticsearch查詢之間的代理。 Paginator需要兩件東西,__len__(或count)和__getitem__(需要一個分片)。代理的一個粗略的版本是這樣的:

class ResultsProxy(object): 
    """ 
    A proxy object for returning Elasticsearch results that is able to be 
    passed to a Paginator. 
    """ 

    def __init__(self, es, index=None, body=None): 
     self.es = es 
     self.index = index 
     self.body = body 

    def __len__(self): 
     result = self.es.count(index=self.index, 
           body=self.body) 
     return result['count'] 

    def __getitem__(self, item): 
     assert isinstance(item, slice) 

     results = self.es.search(
      index=self.index, 
      body=self.body, 
      from_=item.start, 
      size=item.stop - item.start, 
     ) 

     return results['hits']['hits'] 

代理實例可以傳遞給Paginator,並根據需要將請求ES。

0

根據Danielle Madeley的建議,我還爲搜索結果創建了一個代理,該代理與最新版本的django-elasticsearch-dsl==0.4.4配合使用。

from django.utils.functional import LazyObject 

class SearchResults(LazyObject): 
    def __init__(self, search_object): 
     self._wrapped = search_object 

    def __len__(self): 
     return self._wrapped.count() 

    def __getitem__(self, index): 
     search_results = self._wrapped[index] 
     if isinstance(index, slice): 
      search_results = list(search_results) 
     return search_results 

然後你就可以在你的搜索視圖中使用這樣的:

paginate_by = 20 
search = MyModelDocument.search() 
# ... do some filtering ... 
search_results = SearchResults(search) 

paginator = Paginator(search_results, paginate_by) 
page_number = request.GET.get("page") 
try: 
    page = paginator.page(page_number) 
except PageNotAnInteger: 
    # If page parameter is not an integer, show first page. 
    page = paginator.page(1) 
except EmptyPage: 
    # If page parameter is out of range, show last existing page. 
    page = paginator.page(paginator.num_pages) 

Django的LazyObject代理的所有屬性和方法,從分配給_wrapped屬性的對象。我重寫了Django的分頁程序需要的幾個方法,但是不要使用Search()實例開箱即用。