Source code for invenio_records_rest.sorter
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2016 CERN.
#
# Invenio is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Invenio; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# In applying this license, CERN does not
# waive the privileges and immunities granted to it by virtue of its status
# as an Intergovernmental Organization or submit itself to any jurisdiction.
r"""Sorter factory for REST API.
The default sorter factory allows you to define possible sort options in
the ``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 Elasticsearch Reference Manual for full details of sorting capabilities:
https://www.elastic.co/guide/en/elasticsearch/reference/2.x\
/search-request-sort.html
"""
from __future__ import absolute_import, print_function
import copy
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:
has_query = request.values.get('q', type=str)
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})