【问题标题】:Python elasticsearch-dsl django paginationPython elasticsearch-dsl django 分页
【发布时间】:2016-06-23 04:40:35
【问题描述】:

我如何在 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)

有什么解决办法吗?

【问题讨论】:

    标签: python django pagination elasticsearch-dsl


    【解决方案1】:

    我在 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)
    

    这样可以完美运行..

    【讨论】:

    • 此示例中的count 属性仅显示页面中的项目数,而不是总数。您可以覆盖分页器的count cached_property 以返回_count 作为总数
    【解决方案2】:

    根据 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() 实例一起使用。

    【讨论】:

      【解决方案3】:

      一个非常简单的解决方案是使用MultipleObjectMixin 并通过覆盖它来提取get_queryset() 中的弹性结果。在这种情况下,如果您添加 paginate_by 属性,Django 将自行处理分页。

      应该是这样的:

      class MyView(MultipleObjectMixin, ListView):
          paginate_by = 10
      
          def get_queryset(self):
              object_list = []
              """ Query Elastic here and return the response data in `object_list`.
                  If you wish to add filters when querying Elastic,
                  you can use self.request.GET params here. """
              return object_list
      

      注意:上面的代码很宽泛,与我自己的情况不同,所以我不能保证它有效。我通过继承其他 Mixins、覆盖 get_queryset() 并利用 Django 的内置分页来使用类似的解决方案 - 它对我来说非常有用。由于这是一个简单的修复,我决定在这里发布一个类似的示例。

      【讨论】:

      • 它也适用于 DRF,无需任何额外代码。只需在get_queryset 中返回Search 对象即可
      【解决方案4】:

      另一种方法是在 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 发出请求。

      【讨论】:

        猜你喜欢
        • 2021-06-09
        • 2021-04-15
        • 1970-01-01
        • 2021-02-08
        • 2021-03-22
        • 2021-11-26
        • 2020-08-29
        • 1970-01-01
        • 2016-05-06
        相关资源
        最近更新 更多