misc: add ilike operator (#74026) #1088

Merged
fpeters merged 1 commits from wip/74026-like-operator into main 2024-03-15 07:22:01 +01:00
6 changed files with 58 additions and 6 deletions

View File

@ -1365,6 +1365,9 @@ def test_api_list_formdata_string_filter(pub, local_user):
('existing', 'on', 3),
('between', 'FOO 1|FOO 2', 1),
('between', 'FOO 2|FOO 1', 1),
('icontains', 'FOO', 3),
('icontains', 'foo', 3),
('icontains', '2', 1),
]
for operator, value, result in params:
resp = get_app(pub).get(
@ -1541,6 +1544,9 @@ def test_api_list_formdata_text_filter(pub, local_user):
('existing', 'on', 3),
('between', 'FOO 1|FOO 2', 1),
('between', 'FOO 2|FOO 1', 1),
('icontains', 'FOO', 3),
('icontains', 'foo', 3),
('icontains', '2', 1),
]
for operator, value, result in params:
resp = get_app(pub).get(
@ -2010,6 +2016,8 @@ def test_api_list_formdata_email_filter(pub, local_user):
('not_in', 'a@localhost|b@localhost', 1),
('absent', 'on', 2),
('existing', 'on', 3),
('icontains', 'A@LOCAL', 1),
('icontains', 'C@LOCAL', 0),
]
for operator, value, result in params:
resp = get_app(pub).get(
@ -2306,6 +2314,8 @@ def test_api_list_formdata_block_field_filter(pub, local_user):
('existing', 'on', 12),
('between', 'plop1|plop5', 7),
('between', 'plop5|plop1', 7),
('icontains', 'PLOP', 12),
('icontains', 'LOP1', 4), # plop1 (twice), plop10, plop11
]
for operator, value, result in params:
resp = get_app(pub).get(

View File

@ -1048,6 +1048,7 @@ def test_backoffice_string_filter(pub):
'not_in',
'absent',
'existing',
'icontains',
]
resp.forms['listing-settings']['filter-4-value'].value = 'a'
@ -1167,6 +1168,7 @@ def test_backoffice_text_filter(pub):
'not_in',
'absent',
'existing',
'icontains',
]
resp.forms['listing-settings']['filter-4-value'].value = 'a'
@ -1227,6 +1229,7 @@ def test_backoffice_email_filter(pub):
'not_in',
'absent',
'existing',
'icontains',
]
resp.forms['listing-settings']['filter-4-value'].value = 'a@localhost'
@ -1245,6 +1248,12 @@ def test_backoffice_email_filter(pub):
assert resp.text.count('>a@localhost</') > 0
assert resp.text.count('>b@localhost</') == 0
resp.forms['listing-settings']['filter-4-value'].value = 'a@local'
resp.forms['listing-settings']['filter-4-operator'].value = 'icontains'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('>a@localhost</') > 0
assert resp.text.count('>b@localhost</') == 0
def test_backoffice_date_filter(pub):
pub.user_class.wipe()
@ -1761,6 +1770,7 @@ def test_backoffice_block_field_filter(pub):
'not_in',
'absent',
'existing',
'icontains',
]
resp.forms['listing-settings']['filter-0-1-value'].value = 'plop0'
resp = resp.forms['listing-settings'].submit()
@ -1879,6 +1889,7 @@ def test_backoffice_block_field_filter(pub):
'not_in',
'absent',
'existing',
'icontains',
]
resp.forms['listing-settings']['filter-0-5-value'].value = 'a@localhost'
resp = resp.forms['listing-settings'].submit()
@ -1893,6 +1904,10 @@ def test_backoffice_block_field_filter(pub):
resp.forms['listing-settings']['filter-0-5-operator'].value = 'ne'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<tr') == 1 + 4
resp.forms['listing-settings']['filter-0-5-value'].value = '@localhost'
resp.forms['listing-settings']['filter-0-5-operator'].value = 'icontains'
resp = resp.forms['listing-settings'].submit()
assert resp.text.count('<tr') == 1 + 10
# mix
resp = app.get('/backoffice/management/form-title/')

View File

@ -4236,6 +4236,8 @@ def test_formdata_filtering_on_fields(pub):
('between', 'plop5|plop1', '4'),
('between', ['plop1', 'plop5'], '4'),
('between', ['plop5', 'plop1'], '4'),
('icontains', 'plop', '10'),
('icontains', 'PLOP', '10'),
]
for operator, value, result in params:
context['value'] = None
@ -4494,6 +4496,8 @@ def test_formdata_filtering_on_fields(pub):
('not_in', 'a@localhost|b@localhost', '1'),
('absent', '', '2'),
('existing', '', '10'),
('icontains', 'A@local', '5'),
('icontains', '@LOCAL', '10'),
]
for operator, value, result in params:
if value:
@ -4543,6 +4547,8 @@ def test_formdata_filtering_on_fields(pub):
('between', 'plop5|plop1', '4'),
('between', ['plop1', 'plop5'], '4'),
('between', ['plop5', 'plop1'], '4'),
('icontains', 'plop', '10'),
('icontains', 'PLOP', '10'),
]
for operator, value, result in params:
context['value'] = None

View File

@ -922,6 +922,11 @@ def between(queryset):
return queryset.apply_between()
@register_queryset_filter(name='icontains', attr='apply_icontains')
def icontains(queryset):
return queryset.apply_icontains()
@register.filter
def count(queryset):
if hasattr(queryset, '__len__'):

View File

@ -25,7 +25,7 @@ from wcs.qommon import misc
def like_escape(value):
value = value.replace('\\', '\\\\')
value = str(value or '').replace('\\', '\\\\')
value = value.replace('_', '\\_')
value = value.replace('%', '\\%')
return value
@ -336,13 +336,12 @@ class Intersects(Criteria):
class ILike(Criteria):
sql_op = 'ILIKE'
def __init__(self, attribute, value, **kwargs):
super().__init__(attribute, value, **kwargs)
self.value = '%' + like_escape(self.value) + '%'
def as_sql(self):
return '%s ILIKE %%(c%s)s' % (self.attribute, id(self.value))
class FtsMatch(Criteria):
def __init__(self, value, extra_normalize=True, **kwargs):

View File

@ -30,6 +30,7 @@ from wcs.sql_criterias import (
Equal,
Greater,
GreaterOrEqual,
ILike,
Less,
LessOrEqual,
Not,
@ -303,9 +304,21 @@ class LazyFormDefObjectsManager:
('in', _('in')),
('not_in', _('not in')),
]
text_operators = [
('icontains', _('contains')),
]
if field.key == 'internal-id':
return equality_operators + comparison_operators
if field.key in ['date', 'item', 'items', 'string', 'text', 'numeric']:
if field.key in ['string', 'text']:
return (
equality_operators
+ comparison_operators
+ more_comparison_operators
+ in_operators
+ empty_operators
+ text_operators
)
if field.key in ['date', 'item', 'items', 'numeric']:
return (
equality_operators
+ comparison_operators
@ -316,7 +329,7 @@ class LazyFormDefObjectsManager:
if field.key == 'bool':
return equality_operators + empty_operators
if field.key == 'email':
return equality_operators + in_operators + empty_operators
return equality_operators + in_operators + empty_operators + text_operators
return None
def format_value(self, op, value, field):
@ -412,6 +425,7 @@ class LazyFormDefObjectsManager:
'gt': Greater,
'gte': GreaterOrEqual,
'in': Contains,
'icontains': ILike,
}
if isinstance(value, list) and op in ['eq', 'ne']:
@ -588,6 +602,9 @@ class LazyFormDefObjectsManager:
def apply_between(self):
return self.apply_op('between')
def apply_icontains(self):
return self.apply_op('icontains')
def getlist(self, key):
return LazyList(self, key)