Skip to content

Commit 4b93d37

Browse files
authored
Add filters support for AI field (baserow#4166)
Add filters support for AI Field
1 parent bd924c5 commit 4b93d37

File tree

14 files changed

+1013
-23
lines changed

14 files changed

+1013
-23
lines changed

backend/src/baserow/contrib/database/fields/registries.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,6 +1674,19 @@ def check_can_filter_by(self, field: Field) -> bool:
16741674

16751675
return len(compatible_vft) > 0
16761676

1677+
def get_compatible_filter_field_type(self, field: Field) -> "FieldType":
1678+
"""
1679+
Returns the canonical field type to use when determining compatibility with
1680+
view filters. By default this returns self, but field types that should be
1681+
treated as another field type for filtering can override this and return that
1682+
other field type instance.
1683+
1684+
:param field: The concrete field instance being checked.
1685+
:return: The FieldType that should be used for filter compatibility.
1686+
"""
1687+
1688+
return self
1689+
16771690
def check_can_order_by(self, field: Field, sort_type: str) -> bool:
16781691
"""
16791692
Override this method if this field type can sometimes be ordered or sometimes

backend/src/baserow/contrib/database/views/registries.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -921,10 +921,11 @@ def get_filter(self, field_name, value):
921921

922922
compatible_field_types: List[Union[str, Callable[["Field"], bool]]] = []
923923
"""
924-
Defines which field types are compatible with the filter. Only the supported ones
925-
can be used in combination with the field. The values in this list can either be
926-
the literal field_type.type string, or a callable which takes the field being
927-
checked and returns True if compatible or False if not.
924+
Defines which field types are compatible with the filter. The values in this list
925+
can either be the literal `FieldType.type` string, or a callable taking the
926+
concrete field and returning True/False. When checking compatibility the
927+
`FieldType.get_compatible_filter_field_type(field)` indirection is applied first,
928+
allowing field types to alias themselves to another type for filtering purposes.
928929
"""
929930

930931
def default_filter_on_exception(self):
@@ -1000,9 +1001,9 @@ def field_is_compatible(self, field: "Field") -> bool:
10001001
Given a particular instance of a field returns a list of Type[FieldType] which
10011002
are compatible with this particular field type.
10021003
1003-
Works by checking the field_type against this view filters list of compatible
1004-
field types or compatibility checking functions defined in
1005-
self.allowed_field_types.
1004+
Works by checking the field's canonical filter type (as returned by
1005+
`FieldType.get_compatible_filter_field_type(field)`) against this filter's
1006+
`compatible_field_types`.
10061007
10071008
:param field: The field to check.
10081009
:return: True if the field is compatible, False otherwise.
@@ -1011,9 +1012,12 @@ def field_is_compatible(self, field: "Field") -> bool:
10111012
from baserow.contrib.database.fields.registries import field_type_registry
10121013

10131014
field_type = field_type_registry.get_by_model(field.specific_class)
1015+
# Allow field types to map themselves to another field type for filter
1016+
# compatibility checks.
1017+
compatible_field_type = field_type.get_compatible_filter_field_type(field)
10141018

10151019
return any(
1016-
callable(t) and t(field) or t == field_type.type
1020+
(callable(t) and t(field)) or t == compatible_field_type.type
10171021
for t in self.compatible_field_types
10181022
)
10191023

backend/src/baserow/contrib/database/views/view_filters.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,8 @@ def get_filter(self, field_name, value, model_field, field):
11041104
return Q()
11051105

11061106
field_type = field_type_registry.get_by_model(field)
1107-
filter_function = self.filter_functions[field_type.type]
1107+
effective_field_type = field_type.get_compatible_filter_field_type(field)
1108+
filter_function = self.filter_functions[effective_field_type.type]
11081109
return filter_function(field_name, value, model_field, field)
11091110

11101111
def set_import_serialized_value(self, value, id_mapping):
@@ -1160,7 +1161,8 @@ def get_filter(self, field_name, value: str, model_field, field):
11601161
return self.default_filter_on_exception()
11611162

11621163
field_type = field_type_registry.get_by_model(field)
1163-
filter_function = self.filter_functions[field_type.type]
1164+
effective_field_type = field_type.get_compatible_filter_field_type(field)
1165+
filter_function = self.filter_functions[effective_field_type.type]
11641166
return filter_function(field_name, option_ids, model_field, field)
11651167

11661168
def set_import_serialized_value(self, value: str | None, id_mapping: dict) -> str:
@@ -1419,7 +1421,8 @@ def get_filter(self, field_name, value: str, model_field, field):
14191421
return self.default_filter_on_exception()
14201422

14211423
field_type = field_type_registry.get_by_model(field)
1422-
filter_function = self.filter_functions[field_type.type]
1424+
effective_field_type = field_type.get_compatible_filter_field_type(field)
1425+
filter_function = self.filter_functions[effective_field_type.type]
14231426
return filter_function(field_name, option_ids, model_field, field)
14241427

14251428
def set_import_serialized_value(self, value: str | None, id_mapping: dict) -> str:

backend/tests/baserow/contrib/database/field/test_field_types.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,6 @@
304304
"is_even_and_whole",
305305
],
306306
"password": ["empty", "not_empty"],
307-
"ai": [],
308307
}
309308

310309

@@ -1216,7 +1215,14 @@ def test_field_type_prepare_db_value_with_invalid_values(data_fixture):
12161215
field_type.prepare_value_for_db(field, test_payload)
12171216

12181217

1219-
@pytest.mark.parametrize("field_type", field_type_registry.get_all())
1218+
@pytest.mark.parametrize(
1219+
"field_type",
1220+
[
1221+
ft
1222+
for ft in field_type_registry.get_all()
1223+
if ft.type in COMPATIBLE_FIELD_TYPE_VIEW_FILTER_TYPES
1224+
],
1225+
)
12201226
@patch("baserow.contrib.database.fields.registries.FieldTypeRegistry.get_by_model")
12211227
def test_field_type_check_can_filter_by(mock_get_by_model, field_type):
12221228
mock_get_by_model.return_value = field_type

backend/tests/baserow/contrib/integrations/local_baserow/test_service_types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ def reset_metadata(schema, field_name):
10501050
"default": None,
10511051
"searchable": True,
10521052
"sortable": True,
1053-
"filterable": False,
1053+
"filterable": True,
10541054
"original_type": "ai",
10551055
"metadata": {},
10561056
"type": "string",
@@ -1060,7 +1060,7 @@ def reset_metadata(schema, field_name):
10601060
"default": None,
10611061
"searchable": True,
10621062
"sortable": True,
1063-
"filterable": False,
1063+
"filterable": True,
10641064
"original_type": "ai",
10651065
"metadata": {},
10661066
"properties": {
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"type": "feature",
3+
"message": "Add filters support for AI field",
4+
"domain": "database",
5+
"issue_number": 3801,
6+
"bullet_points": [],
7+
"created_at": "2025-10-23"
8+
}

premium/backend/src/baserow_premium/fields/field_types.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,18 @@ def contains_query(self, field_name, value, model_field, field):
166166
baserow_field_type = self.get_baserow_field_type(field)
167167
return baserow_field_type.contains_query(field_name, value, model_field, field)
168168

169+
def parse_filter_value(self, field, model_field, value):
170+
baserow_field_type = self.get_baserow_field_type(field)
171+
return baserow_field_type.parse_filter_value(field, model_field, value)
172+
173+
def get_compatible_filter_field_type(self, field):
174+
"""
175+
For AI fields, return the underlying core field type used for filtering.
176+
"""
177+
178+
ai_field_instance = field.specific if hasattr(field, "specific") else field
179+
return self.get_baserow_field_type(ai_field_instance)
180+
169181
def contains_word_query(self, field_name, value, model_field, field):
170182
baserow_field_type = self.get_baserow_field_type(field)
171183
return baserow_field_type.contains_word_query(

0 commit comments

Comments
 (0)