diff --git a/apps/search_indexes/filters.py b/apps/search_indexes/filters.py index ab47ef84..5966d540 100644 --- a/apps/search_indexes/filters.py +++ b/apps/search_indexes/filters.py @@ -1,7 +1,48 @@ """Search indexes filters.""" from elasticsearch_dsl.query import Q -from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend +from django_elasticsearch_dsl_drf.filter_backends import SearchFilterBackend, FacetedSearchFilterBackend from search_indexes.utils import OBJECT_FIELD_PROPERTIES +from six import iteritems + + +class CustomFacetedSearchFilterBackend(FacetedSearchFilterBackend): + + def __init__(self): + self.facets_computed = {} + + def aggregate(self, request, queryset, view): + """Aggregate. + + :param request: + :param queryset: + :param view: + :return: + """ + def makefilter(cur_facet): + def myfilter(x): + return cur_facet['facet']._params['field'] != next(iter(x._params)) + return myfilter + __facets = self.construct_facets(request, view) + for __field, __facet in iteritems(__facets): + agg = __facet['facet'].get_aggregation() + agg_filter = Q('match_all') + if __facet['global']: + queryset.aggs.bucket( + '_filter_' + __field, + 'global' + ).bucket(__field, agg) + else: + qs = queryset._clone() + filterer = makefilter(__facet) + qs.query._proxied._params['must'] = list(filter(filterer, qs.query._proxied._params['must'])) + facet_name = '_filter_' + __field + qs.aggs.bucket( + facet_name, + 'filter', + filter=agg_filter + ).bucket(__field, agg) + self.facets_computed.update({facet_name: qs.execute().aggregations[facet_name]}) + return queryset class CustomSearchFilterBackend(SearchFilterBackend): diff --git a/apps/search_indexes/views.py b/apps/search_indexes/views.py index e352f9be..37ac5928 100644 --- a/apps/search_indexes/views.py +++ b/apps/search_indexes/views.py @@ -14,7 +14,25 @@ from search_indexes.documents.product import ProductDocument from utils.pagination import ESDocumentPagination -class NewsDocumentViewSet(BaseDocumentViewSet): +class FacetedResponseMixin: + def filter_queryset(self, queryset): + """ + Given a queryset, filter it with whichever filter backend is in use. + + You are unlikely to want to override this method, although you may need + to call it either from a list view, or from a custom `get_object` + method if you want to apply the configured filtering backend to the + default queryset. + """ + for backend in list(self.filter_backends): + bc = backend() + queryset = bc.filter_queryset(self.request, queryset, self) + if hasattr(bc, 'facets_computed'): + setattr(self, 'facets_computed', bc.facets_computed) + return queryset + + +class NewsDocumentViewSet(BaseDocumentViewSet, FacetedResponseMixin): """News document ViewSet.""" document = NewsDocument @@ -26,7 +44,7 @@ class NewsDocumentViewSet(BaseDocumentViewSet): filter_backends = [ filters.CustomSearchFilterBackend, FilteringFilterBackend, - FacetedSearchFilterBackend, + filters.CustomFacetedSearchFilterBackend, ] faceted_search_fields = { @@ -86,7 +104,7 @@ class MobileNewsDocumentViewSet(NewsDocumentViewSet): ] -class EstablishmentDocumentViewSet(BaseDocumentViewSet): +class EstablishmentDocumentViewSet(BaseDocumentViewSet, FacetedResponseMixin): """Establishment document ViewSet.""" document = EstablishmentDocument @@ -103,7 +121,7 @@ class EstablishmentDocumentViewSet(BaseDocumentViewSet): FilteringFilterBackend, filters.CustomSearchFilterBackend, GeoSpatialFilteringFilterBackend, - FacetedSearchFilterBackend, + filters.CustomFacetedSearchFilterBackend, ] faceted_search_fields = { @@ -306,7 +324,7 @@ class ProductDocumentViewSet(BaseDocumentViewSet): filter_backends = [ FilteringFilterBackend, filters.CustomSearchFilterBackend, - FacetedSearchFilterBackend, + filters.CustomFacetedSearchFilterBackend, ] search_fields = { diff --git a/apps/utils/pagination.py b/apps/utils/pagination.py index ed5ce89e..199d55b6 100644 --- a/apps/utils/pagination.py +++ b/apps/utils/pagination.py @@ -6,7 +6,6 @@ from django.conf import settings from rest_framework.pagination import CursorPagination, PageNumberPagination from django_elasticsearch_dsl_drf.pagination import PageNumberPagination as ESPagination - class ProjectPageNumberPagination(PageNumberPagination): """Customized pagination class.""" @@ -65,6 +64,23 @@ class ESDocumentPagination(ESPagination): return None return self.page.previous_page_number() + def get_facets(self, page=None): + """Get facets. + + :param page: + :return: + """ + if page is None: + page = self.page + + if hasattr(self, 'facets_computed'): + ret = {} + for filter_field, bucket_data in self.facets_computed.items(): + ret.update({filter_field: bucket_data.__dict__['_d_']}) + return ret + elif hasattr(page, 'facets') and hasattr(page.facets, '_d_'): + return page.facets._d_ + class EstablishmentPortionPagination(ProjectMobilePagination): """