Source code for invenio_records_rest.sorter

# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016-2018 CERN.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.

r"""Sorter factories for REST API.

The default sorter factory allows you to define possible sort options in
the :data:`invenio_records_rest.config.RECORDS_REST_SORT_OPTIONS`
configuration variable. The sort options are defined per index alias
(e.g. ``records``). If more fine grained control is needed a custom sorter
factory can be provided to Records-REST instead.

See the search engine reference manual for full details of sorting capabilities:
https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-sort.html
"""

import copy

import six
from flask import current_app, request


[docs]def geolocation_sort(field_name, argument, unit, mode=None, distance_type=None): """Sort field factory for geo-location based sorting. :param argument: Name of URL query string field to parse pin location from. Multiple locations can be provided. Each location can be either a string "latitude,longitude" or a geohash. :param unit: Distance unit (e.g. km). :param mode: Sort mode (avg, min, max). :param distance_type: Distance calculation mode. :returns: Function that returns geolocation sort field. """ def inner(asc): locations = request.values.getlist(argument, type=str) field = { "_geo_distance": { field_name: locations, "order": "asc" if asc else "desc", "unit": unit, } } if mode: field["_geo_distance"]["mode"] = mode if distance_type: field["_geo_distance"]["distance_type"] = distance_type return field return inner
[docs]def parse_sort_field(field_value): """Parse a URL field. :param field_value: Field value (e.g. 'key' or '-key'). :returns: Tuple of (field, ascending) as string and boolean. """ if field_value.startswith("-"): return (field_value[1:], False) return (field_value, True)
[docs]def reverse_order(order_value): """Reserve ordering of order value (asc or desc). :param order_value: Either the string ``asc`` or ``desc``. :returns: Reverse sort order of order value. """ if order_value == "desc": return "asc" elif order_value == "asc": return "desc" return None
[docs]def eval_field(field, asc): """Evaluate a field for sorting purpose. :param field: Field definition (string, dict or callable). :param asc: ``True`` if order is ascending, ``False`` if descending. :returns: Dictionary with the sort field query. """ if isinstance(field, dict): if asc: return field else: # Field should only have one key and must have an order subkey. field = copy.deepcopy(field) key = list(field.keys())[0] field[key]["order"] = reverse_order(field[key]["order"]) return field elif callable(field): return field(asc) else: key, key_asc = parse_sort_field(field) if not asc: key_asc = not key_asc return {key: {"order": "asc" if key_asc else "desc"}}
[docs]def default_sorter_factory(search, index): """Default sort query factory. :param query: Search query. :param index: Index to search in. :returns: Tuple of (query, URL arguments). """ sort_arg_name = "sort" urlfield = request.values.get(sort_arg_name, "", type=str) # Get default sorting if sort is not specified. if not urlfield: # cast to six.text_type to handle unicodes in Python 2 has_query = request.values.get("q", type=six.text_type) urlfield = ( current_app.config["RECORDS_REST_DEFAULT_SORT"] .get(index, {}) .get("query" if has_query else "noquery", "") ) # Parse sort argument key, asc = parse_sort_field(urlfield) # Get sort options sort_options = ( current_app.config["RECORDS_REST_SORT_OPTIONS"].get(index, {}).get(key) ) if sort_options is None: return (search, {}) # Get fields to sort query by search = search.sort(*[eval_field(f, asc) for f in sort_options["fields"]]) return (search, {sort_arg_name: urlfield})