From b29813b6ac8f47bdbfa708cdf9b7a4454624c340 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 30 Jun 2020 21:03:18 +0200 Subject: [PATCH] misc: implement customized date search for birthdate attribute (#44656) --- src/authentic2/attribute_kinds.py | 16 +++++++++++++++- src/authentic2/custom_user/managers.py | 17 +++++++++++++++++ tests/test_api.py | 14 ++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/authentic2/attribute_kinds.py b/src/authentic2/attribute_kinds.py index a1b126ec..708b939b 100644 --- a/src/authentic2/attribute_kinds.py +++ b/src/authentic2/attribute_kinds.py @@ -25,10 +25,11 @@ from itertools import chain from django import forms from django.core.exceptions import ValidationError from django.core.validators import RegexValidator -from django.utils import six +from django.utils import six, formats from django.utils.translation import ugettext_lazy as _, pgettext_lazy from django.utils import html from django.core.files.storage import default_storage +from django.db.models import query from django.utils.functional import keep_lazy @@ -207,6 +208,18 @@ def profile_attributes_ng_serialize(ctx, value): return None +def date_free_text_search(term): + for date_format in formats.get_format('DATE_INPUT_FORMATS'): + try: + date = datetime.datetime.strptime(term, date_format).date() + break + except (ValueError, TypeError): + pass + else: + return None + return query.Q(attribute_values__content__exact=date.isoformat()) + + DEFAULT_ALLOW_BLANK = True DEFAULT_MAX_LENGTH = 256 @@ -251,6 +264,7 @@ DEFAULT_ATTRIBUTE_KINDS = [ 'serialize': lambda x: x and x.isoformat(), 'deserialize': lambda x: x and datetime.datetime.strptime(x, '%Y-%m-%d').date(), 'rest_framework_field_class': BirthdateRestField, + 'free_text_search': date_free_text_search, }, { 'label': _('french postcode'), diff --git a/src/authentic2/custom_user/managers.py b/src/authentic2/custom_user/managers.py index e40e8458..a00641af 100644 --- a/src/authentic2/custom_user/managers.py +++ b/src/authentic2/custom_user/managers.py @@ -37,6 +37,23 @@ class UserQuerySet(models.QuerySet): searchable_attributes = Attribute.objects.filter(searchable=True) queries = [] for term in terms: + q = None + + specific_queries = [] + for a in searchable_attributes: + kind = a.get_kind() + free_text_search_function = kind.get('free_text_search') + if free_text_search_function: + q = free_text_search_function(term) + if q is not None: + specific_queries.append(q & models.query.Q(attribute_values__attribute=a)) + + # if the term is recognized by some specific attribute type, like a + # date, does not use the later generic matcher + if specific_queries is not None: + queries.append(six.moves.reduce(models.query.Q.__or__, specific_queries)) + continue + q = ( models.query.Q(username__icontains=term) | models.query.Q(first_name__icontains=term) diff --git a/tests/test_api.py b/tests/test_api.py index 6a523fc0..db7736f0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1703,3 +1703,17 @@ def test_filter_users_by_last_modification(app, admin, simple_user, freezer): assert len(resp.json['results']) == 2 resp = app.get('/api/users/', params={'modified__lt': '2019-10-27T02:58:07'}) assert len(resp.json['results']) == 0 + + +def test_free_text_search(app, admin, settings): + settings.LANGUAGE_CODE = 'fr' # use fr date format + + app.authorization = ('Basic', (admin.username, admin.username)) + Attribute.objects.create(kind='birthdate', name='birthdate', label='birthdate', required=False, searchable=True) + + user = User.objects.create() + user.attributes.birthdate = datetime.date(1982, 2, 10) + + resp = app.get('/api/users/?q=10/02/1982') + assert len(resp.json['results']) == 1 + assert resp.json['results'][0]['id'] == user.id -- 2.26.2