Source code for django_elasticsearch_dsl_drf.filter_backends.ordering.common

"""
Ordering backend.
"""

from six import string_types

from rest_framework.filters import BaseFilterBackend
from rest_framework.settings import api_settings

from ...compat import coreapi
from ...compat import coreschema
from ...compat import nested_sort_entry

__title__ = 'django_elasticsearch_dsl_drf.filter_backends.ordering.common'
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__copyright__ = '2017-2019 Artur Barseghyan'
__license__ = 'GPL 2.0/LGPL 2.1'
__all__ = (
    'DefaultOrderingFilterBackend',
    'OrderingFilterBackend',
)


class OrderingMixin(object):
    @classmethod
    def prepare_ordering_fields(cls, view):
        """Prepare ordering fields.

        :param view: View.
        :type view: rest_framework.viewsets.ReadOnlyModelViewSet
        :return: Ordering options.
        :rtype: dict
        """
        ordering_fields = view.ordering_fields.copy()
        for field, options in ordering_fields.items():
            if options is None or isinstance(options, string_types):
                ordering_fields[field] = {
                    'field': options or field
                }
            elif 'field' not in ordering_fields[field]:
                ordering_fields[field]['field'] = field
        return ordering_fields

    @classmethod
    def transform_ordering_params(cls, ordering_params, ordering_fields):
        """Transform ordering fields to elasticsearch-dsl Search.sort()
         dictionary parameters.

        :param ordering_params: List of fields to order by.
        :param ordering_fields: Prepared ordering fields
        :type: list of str
        :type: dict
        :return: Ordering parameters.
        :rtype: list
        """
        _ordering_params = []
        for ordering_param in ordering_params:
            key = ordering_param.lstrip('-')
            direction = 'desc' if ordering_param.startswith('-') else 'asc'
            if key in ordering_fields:
                field = ordering_fields[key]
                entry = {
                    field['field']: {
                        'order': direction,
                    }
                }
                if 'path' in field:
                    entry[field['field']].update(
                        nested_sort_entry(field['path']))
                _ordering_params.append(entry)
        return _ordering_params


[docs]class OrderingFilterBackend(BaseFilterBackend, OrderingMixin): """Ordering filter backend for Elasticsearch. Example: >>> from django_elasticsearch_dsl_drf.filter_backends import ( >>> OrderingFilterBackend >>> ) >>> from django_elasticsearch_dsl_drf.viewsets import ( >>> BaseDocumentViewSet, >>> ) >>> >>> # Local article document definition >>> from .documents import ArticleDocument >>> >>> # Local article document serializer >>> from .serializers import ArticleDocumentSerializer >>> >>> class ArticleDocumentView(BaseDocumentViewSet): >>> >>> document = ArticleDocument >>> serializer_class = ArticleDocumentSerializer >>> filter_backends = [OrderingFilterBackend,] >>> ordering_fields = { >>> 'id': None, >>> 'title': 'title.raw', >>> 'date_submitted': 'date_submitted', >>> 'continent': { >>> 'field': 'continent.name.raw', >>> 'path': 'continent', >>> } >>> 'country': { >>> 'field': 'continent.country.name.raw', >>> 'path': 'continent.country', >>> } >>> 'city': { >>> 'field': 'continent.country.city.name.raw', >>> 'path': 'continent.country.city', >>> } >>> } >>> ordering = ('id', 'title',) """ ordering_param = api_settings.ORDERING_PARAM
[docs] def get_ordering_query_params(self, request, view): """Get ordering query params. :param request: Django REST framework request. :param view: View. :type request: rest_framework.request.Request :type view: rest_framework.viewsets.ReadOnlyModelViewSet :return: Ordering params to be used for ordering. :rtype: list """ # TODO: Support `mode` argument. query_params = request.query_params.copy() ordering_query_params = query_params.getlist(self.ordering_param, []) # ordering_fields is always dict ordering_fields = self.prepare_ordering_fields(view) # This is no longer needed. If you want to have a fallback, make use # of ``DefaultOrderingFilterBackend``. # # If no valid ordering params specified, fall back to `view.ordering` # if not __ordering_params: # return self.get_default_ordering_params(view) return self.transform_ordering_params(ordering_query_params, ordering_fields)
# @classmethod # def get_default_ordering_params(cls, view): # """Get the default ordering params for the view. # # :param view: View. # :type view: rest_framework.viewsets.ReadOnlyModelViewSet # :return: Ordering params to be used for ordering. # :rtype: list # """ # ordering = getattr(view, 'ordering', None) # if isinstance(ordering, string_types): # return [ordering] # return ordering
[docs] def filter_queryset(self, request, queryset, view): """Filter the queryset. :param request: Django REST framework request. :param queryset: Base queryset. :param view: View. :type request: rest_framework.request.Request :type queryset: elasticsearch_dsl.search.Search :type view: rest_framework.viewsets.ReadOnlyModelViewSet :return: Updated queryset. :rtype: elasticsearch_dsl.search.Search """ ordering_query_params = self.get_ordering_query_params(request, view) if ordering_query_params: return queryset.sort(*ordering_query_params) return queryset
[docs] def get_schema_fields(self, view): assert coreapi is not None, 'coreapi must be installed to ' \ 'use `get_schema_fields()`' assert coreschema is not None, 'coreschema must be installed to ' \ 'use `get_schema_fields()`' return [ coreapi.Field( name=self.ordering_param, required=False, location='query', schema=coreschema.String( title='Ordering', description='Which field from to use when ordering the ' 'results.' ) ) ]
[docs]class DefaultOrderingFilterBackend(BaseFilterBackend, OrderingMixin): """Default ordering filter backend for Elasticsearch. Make sure this is your last ordering backend. Example: >>> from django_elasticsearch_dsl_drf.filter_backends import ( >>> DefaultOrderingFilterBackend, >>> OrderingFilterBackend >>> ) >>> from django_elasticsearch_dsl_drf.viewsets import ( >>> BaseDocumentViewSet, >>> ) >>> >>> # Local article document definition >>> from .documents import ArticleDocument >>> >>> # Local article document serializer >>> from .serializers import ArticleDocumentSerializer >>> >>> class ArticleDocumentView(BaseDocumentViewSet): >>> >>> document = ArticleDocument >>> serializer_class = ArticleDocumentSerializer >>> filter_backends = [ >>> DefaultOrderingFilterBackend, >>> OrderingFilterBackend, >>> ] >>> ordering_fields = { >>> 'id': None, >>> 'title': 'title.raw', >>> 'date_submitted': 'date_submitted', >>> 'continent': { >>> 'field': 'continent.name.raw', >>> 'path': 'continent', >>> } >>> 'country': { >>> 'field': 'continent.country.name.raw', >>> 'path': 'continent.country', >>> } >>> 'city': { >>> 'field': 'continent.country.city.name.raw', >>> 'path': 'continent.country.city', >>> } >>> } >>> ordering = 'city' """ ordering_param = api_settings.ORDERING_PARAM
[docs] def get_ordering_query_params(self, request, view): """Get ordering query params. :param request: Django REST framework request. :param view: View. :type request: rest_framework.request.Request :type view: rest_framework.viewsets.ReadOnlyModelViewSet :return: Ordering params to be used for ordering. :rtype: list """ query_params = request.query_params.copy() ordering_query_params = query_params.getlist(self.ordering_param, []) ordering_params_present = False # Remove invalid ordering query params for query_param in ordering_query_params: __key = query_param.lstrip('-') if __key in view.ordering_fields: ordering_params_present = True break # If no valid ordering params specified, fall back to `view.ordering` if not ordering_params_present: return self.get_default_ordering_params(view) return {}
[docs] @classmethod def get_default_ordering_params(cls, view): """Get the default ordering params for the view. :param view: View. :type view: rest_framework.viewsets.ReadOnlyModelViewSet :return: Ordering params to be used for ordering. :rtype: list """ ordering = getattr(view, 'ordering', None) if isinstance(ordering, string_types): ordering = [ordering] # For backwards compatibility require # default ordering to be keys in ordering_fields not field value # in order to be properly transformed if ordering is not None \ and hasattr(view, 'ordering_fields') \ and view.ordering_fields is not None \ and all(field.lstrip('-') in view.ordering_fields for field in ordering): return cls.transform_ordering_params( ordering, cls.prepare_ordering_fields(view) ) return ordering
[docs] def filter_queryset(self, request, queryset, view): """Filter the queryset. :param request: Django REST framework request. :param queryset: Base queryset. :param view: View. :type request: rest_framework.request.Request :type queryset: elasticsearch_dsl.search.Search :type view: rest_framework.viewsets.ReadOnlyModelViewSet :return: Updated queryset. :rtype: elasticsearch_dsl.search.Search """ ordering_query_params = self.get_ordering_query_params(request, view) if ordering_query_params: return queryset.sort(*ordering_query_params) return queryset