Skip to content

Commit

Permalink
Fix #1101: Add period filter (#1394)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeanphilippeds authored Oct 31, 2022
1 parent 5b393cc commit 8f38e6f
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 12 deletions.
15 changes: 15 additions & 0 deletions c2corg_api/search/mapping_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ def __init__(self, query_name, *args, **kwargs):
if 'date_range' in kwargs:
self._date_range = kwargs['date_range']
del kwargs['date_range']
if 'period' in kwargs:
self._period = kwargs['period']
del kwargs['period']
if 'date' in kwargs:
self._date = kwargs['date']
del kwargs['date']
Expand Down Expand Up @@ -110,6 +113,18 @@ def __init__(self, query_name, field_date_start, field_date_end,
super(QDateRange, self).__init__(query_name, *args, **kwargs)


class QPeriod(QueryableMixin):
"""Search field for period with two fields (start/end). Used for
`date_start`/`date_end` for outings, regardless of the year.
"""
def __init__(self, query_name, field_date_start, field_date_end,
*args, **kwargs):
self.field_date_start = field_date_start
self.field_date_end = field_date_end
kwargs['period'] = True
super(QPeriod, self).__init__(query_name, *args, **kwargs)


class QDate(QueryableMixin, Date):
"""Search field for date-ranges with a single field. Used for `date` for
images.
Expand Down
4 changes: 3 additions & 1 deletion c2corg_api/search/mappings/outing_mapping.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from c2corg_api.models.outing import OUTING_TYPE, Outing
from c2corg_api.search.mapping import SearchDocument, BaseMeta
from c2corg_api.search.mapping_types import QueryableMixin, QDateRange, \
QInteger, QBoolean, QLong, QEnumArray, QEnumRange
QInteger, QBoolean, QLong, QEnumArray, QEnumRange, QPeriod
from c2corg_api.models.common.sortable_search_attributes import \
sortable_frequentation_types, sortable_condition_ratings, \
sortable_snow_quality_ratings, sortable_snow_quantity_ratings, \
Expand Down Expand Up @@ -156,3 +156,5 @@ def to_search_document(document, index):
SearchOuting)
SearchOuting.queryable_fields['date'] = QDateRange(
'date', 'date_start', 'date_end')
SearchOuting.queryable_fields['period'] = QPeriod(
'period', 'date_start', 'date_end')
59 changes: 51 additions & 8 deletions c2corg_api/search/search_filters.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import logging
import math

from pyproj import Transformer
import re
from c2corg_api.models.outing import OUTING_TYPE
from c2corg_api.search.mapping_types import reserved_query_fields
from datetime import datetime
from functools import partial

from c2corg_api.search import create_search, search_documents, \
get_text_query_on_title
from elasticsearch_dsl.query import Range, Term, Terms, Bool, GeoBoundingBox, \
Missing
from c2corg_api.models.outing import OUTING_TYPE
from c2corg_api.search import (create_search, get_text_query_on_title,
search_documents)
from c2corg_api.search.mapping_types import reserved_query_fields
from elasticsearch_dsl.query import (Bool, GeoBoundingBox, Missing, Range,
Script, Term, Terms)
from pyproj import Transformer

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -85,6 +85,8 @@ def create_filter(field_name, query_term, search_model):
return create_id_filter(field, query_term)
elif hasattr(field, '_date_range') and field._date_range:
return create_date_range_filter(field, query_term)
elif hasattr(field, '_period') and field._period:
return create_period_filter(field, query_term)
elif hasattr(field, '_date') and field._date:
return create_date_filter(field, query_term)
elif hasattr(field, '_integer_range') and field._integer_range:
Expand Down Expand Up @@ -264,6 +266,47 @@ def create_date_range_filter(field, query_term):
]))


def create_period_filter(field, query_term):
"""Creates an ElasticSearch period filter.
This filter type is currently only used for Outing.date_start/date_end.
Valid query terms are:
2022-01-01,2022-01-01
2022-01-01,2022-01-03
Search will ignore the year.
"""
milliseconds_in_one_year = int(365.2425 * 24 * 3600 * 1000)
query_terms = query_term.split(',')
range_values = list(map(parse_date, query_terms))
range_values = [t for t in range_values if t is not None]

def milliseconds_since_first_day_of_year(date):
seconds_since_epoch = datetime.strptime(date, '%Y-%m-%d').timestamp()

return int(1000 * seconds_since_epoch) % milliseconds_in_one_year

n = len(range_values)

if n != 2:
return None

template = "doc['{0}'].value%{2} >= min && doc['{1}'].value%{2} <= max"

return Script(
script=template.format(
field.field_date_end,
field.field_date_start,
milliseconds_in_one_year
),
params={
"min": milliseconds_since_first_day_of_year(range_values[0]),
"max": milliseconds_since_first_day_of_year(range_values[1])
}
)


def create_date_filter(field, query_term):
"""Creates an ElasticSearch date-range filter for a single field.
Expand Down
42 changes: 40 additions & 2 deletions c2corg_api/tests/search/test_search_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from c2corg_api.search.mappings.outing_mapping import SearchOuting
from c2corg_api.search.mappings.waypoint_mapping import SearchWaypoint
from c2corg_api.tests import BaseTestCase
from elasticsearch_dsl.query import Range, Term, Terms, Bool, GeoBoundingBox, \
Missing
from elasticsearch_dsl.query import (Bool, GeoBoundingBox, Missing, Range,
Script, Term, Terms)


class AdvancedSearchTest(BaseTestCase):
Expand Down Expand Up @@ -385,6 +385,44 @@ def test_create_filter_date_range(self):
Range(date_end={'lt': '2016-01-01'})
])))

def test_create_filter_period(self):
self.assertEqual(
create_filter('period', '', SearchOuting),
None)
self.assertEqual(
create_filter('period', 'invalid date', SearchOuting),
None)
self.assertEqual(
create_filter('period', '10-01', SearchOuting),
None)
self.assertEqual(
create_filter('period', '10-01,09-02', SearchOuting),
None)
self.assertEqual(
create_filter('period', '2016-10-01', SearchOuting),
None)
self.assertEqual(
create_filter('period', '2016-10-01, 12-31', SearchOuting),
None)
self.assertEqual(
create_filter('period', '12-31, 2016-10-01', SearchOuting),
None)

script_expected_string = "doc['date_end'].value%31556952000 >= min && doc['date_start'].value%31556952000 <= max" # noqa: E501

self.assertEqual(
create_filter('period', '2016-01-02,2019-01-02', SearchOuting),
Script(
script=script_expected_string,
params={"min": 73008000, "max": 96552000}
))
self.assertEqual(
create_filter('period', '2017-10-04,2016-10-10', SearchOuting),
Script(
script=script_expected_string,
params={"min": 23898456000, "max": 24437808000}
))

def test_create_filter_date(self):
self.assertEqual(
create_filter('idate', '', SearchImage),
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ services:
image: 'docker.io/c2corg/c2corg_es:anon-2018-11-02'
ports:
- 9200:9200
command: -Des.index.number_of_replicas=0 -Des.path.data=/c2corg_anon
command: -Des.index.number_of_replicas=0 -Des.path.data=/c2corg_anon -Des.script.inline=true

redis:
image: 'docker.io/redis:3.2'
Expand Down

0 comments on commit 8f38e6f

Please sign in to comment.