Support multiple filters of the same type

Closes #288
This commit is contained in:
Simon Willison 2019-04-15 16:44:17 -07:00
commit b495839e60
3 changed files with 62 additions and 42 deletions

View file

@ -221,13 +221,14 @@ class TableView(RowTableShared):
# it can still be queried using ?_col__exact=blah # it can still be queried using ?_col__exact=blah
special_args = {} special_args = {}
special_args_lists = {} special_args_lists = {}
other_args = {} other_args = []
for key, value in args.items(): for key, value in args.items():
if key.startswith("_") and "__" not in key: if key.startswith("_") and "__" not in key:
special_args[key] = value[0] special_args[key] = value[0]
special_args_lists[key] = value special_args_lists[key] = value
else: else:
other_args[key] = value[0] for v in value:
other_args.append((key, v))
# Handle ?_filter_column and redirect, if present # Handle ?_filter_column and redirect, if present
redirect_params = filters_should_redirect(special_args) redirect_params = filters_should_redirect(special_args)
@ -255,7 +256,7 @@ class TableView(RowTableShared):
table_metadata = self.ds.table_metadata(database, table) table_metadata = self.ds.table_metadata(database, table)
units = table_metadata.get("units", {}) units = table_metadata.get("units", {})
filters = Filters(sorted(other_args.items()), units, ureg) filters = Filters(sorted(other_args), units, ureg)
where_clauses, params = filters.build_where_clauses(table) where_clauses, params = filters.build_where_clauses(table)
extra_wheres_for_ui = [] extra_wheres_for_ui = []

View file

@ -903,6 +903,16 @@ def test_table_filter_queries(app_client, path, expected_rows):
assert expected_rows == response.json['rows'] assert expected_rows == response.json['rows']
def test_table_filter_queries_multiple_of_same_type(app_client):
response = app_client.get(
"/fixtures/simple_primary_key.json?content__not=world&content__not=hello"
)
assert [
['3', ''],
['4', 'RENDER_CELL_DEMO']
] == response.json['rows']
@pytest.mark.skipif( @pytest.mark.skipif(
not detect_json1(), not detect_json1(),
reason="Requires the SQLite json1 module" reason="Requires the SQLite json1 module"

View file

@ -4,88 +4,97 @@ import pytest
@pytest.mark.parametrize('args,expected_where,expected_params', [ @pytest.mark.parametrize('args,expected_where,expected_params', [
( (
{ (
'name_english__contains': 'foo', ('name_english__contains', 'foo'),
}, ),
['"name_english" like :p0'], ['"name_english" like :p0'],
['%foo%'] ['%foo%']
), ),
( (
{ (
'foo': 'bar', ('foo', 'bar'),
'bar__contains': 'baz', ('bar__contains', 'baz'),
}, ),
['"bar" like :p0', '"foo" = :p1'], ['"bar" like :p0', '"foo" = :p1'],
['%baz%', 'bar'] ['%baz%', 'bar']
), ),
( (
{ (
'foo__startswith': 'bar', ('foo__startswith', 'bar'),
'bar__endswith': 'baz', ('bar__endswith', 'baz'),
}, ),
['"bar" like :p0', '"foo" like :p1'], ['"bar" like :p0', '"foo" like :p1'],
['%baz', 'bar%'] ['%baz', 'bar%']
), ),
( (
{ (
'foo__lt': '1', ('foo__lt', '1'),
'bar__gt': '2', ('bar__gt', '2'),
'baz__gte': '3', ('baz__gte', '3'),
'bax__lte': '4', ('bax__lte', '4'),
}, ),
['"bar" > :p0', '"bax" <= :p1', '"baz" >= :p2', '"foo" < :p3'], ['"bar" > :p0', '"bax" <= :p1', '"baz" >= :p2', '"foo" < :p3'],
[2, 4, 3, 1] [2, 4, 3, 1]
), ),
( (
{ (
'foo__like': '2%2', ('foo__like', '2%2'),
'zax__glob': '3*', ('zax__glob', '3*'),
}, ),
['"foo" like :p0', '"zax" glob :p1'], ['"foo" like :p0', '"zax" glob :p1'],
['2%2', '3*'] ['2%2', '3*']
), ),
# Multiple like arguments:
( (
{ (
'foo__isnull': '1', ('foo__like', '2%2'),
'baz__isnull': '1', ('foo__like', '3%3'),
'bar__gt': '10' ),
}, ['"foo" like :p0', '"foo" like :p1'],
['2%2', '3%3']
),
(
(
('foo__isnull', '1'),
('baz__isnull', '1'),
('bar__gt', '10'),
),
['"bar" > :p0', '"baz" is null', '"foo" is null'], ['"bar" > :p0', '"baz" is null', '"foo" is null'],
[10] [10]
), ),
( (
{ (
'foo__in': '1,2,3', ('foo__in', '1,2,3'),
}, ),
['foo in (:p0, :p1, :p2)'], ['foo in (:p0, :p1, :p2)'],
["1", "2", "3"] ["1", "2", "3"]
), ),
# date # date
( (
{ (
"foo__date": "1988-01-01", ("foo__date", "1988-01-01"),
}, ),
["date(foo) = :p0"], ["date(foo) = :p0"],
["1988-01-01"] ["1988-01-01"]
), ),
# JSON array variants of __in (useful for unexpected characters) # JSON array variants of __in (useful for unexpected characters)
( (
{ (
'foo__in': '[1,2,3]', ('foo__in', '[1,2,3]'),
}, ),
['foo in (:p0, :p1, :p2)'], ['foo in (:p0, :p1, :p2)'],
[1, 2, 3] [1, 2, 3]
), ),
( (
{ (
'foo__in': '["dog,cat", "cat[dog]"]', ('foo__in', '["dog,cat", "cat[dog]"]'),
}, ),
['foo in (:p0, :p1)'], ['foo in (:p0, :p1)'],
["dog,cat", "cat[dog]"] ["dog,cat", "cat[dog]"]
), ),
]) ])
def test_build_where(args, expected_where, expected_params): def test_build_where(args, expected_where, expected_params):
f = Filters(sorted(args.items())) f = Filters(sorted(args))
sql_bits, actual_params = f.build_where_clauses("table") sql_bits, actual_params = f.build_where_clauses("table")
assert expected_where == sql_bits assert expected_where == sql_bits
assert { assert {