Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions backend/src/baserow/contrib/database/fields/registries.py
Original file line number Diff line number Diff line change
Expand Up @@ -1674,6 +1674,19 @@ def check_can_filter_by(self, field: Field) -> bool:

return len(compatible_vft) > 0

def get_compatible_filter_field_type(self, field: Field) -> "FieldType":
"""
Returns the canonical field type to use when determining compatibility with
view filters. By default this returns self, but field types that should be
treated as another field type for filtering can override this and return that
other field type instance.

:param field: The concrete field instance being checked.
:return: The FieldType that should be used for filter compatibility.
"""

return self

def check_can_order_by(self, field: Field, sort_type: str) -> bool:
"""
Override this method if this field type can sometimes be ordered or sometimes
Expand Down
20 changes: 12 additions & 8 deletions backend/src/baserow/contrib/database/views/registries.py
Original file line number Diff line number Diff line change
Expand Up @@ -921,10 +921,11 @@ def get_filter(self, field_name, value):

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

def default_filter_on_exception(self):
Expand Down Expand Up @@ -1000,9 +1001,9 @@ def field_is_compatible(self, field: "Field") -> bool:
Given a particular instance of a field returns a list of Type[FieldType] which
are compatible with this particular field type.

Works by checking the field_type against this view filters list of compatible
field types or compatibility checking functions defined in
self.allowed_field_types.
Works by checking the field's canonical filter type (as returned by
`FieldType.get_compatible_filter_field_type(field)`) against this filter's
`compatible_field_types`.

:param field: The field to check.
:return: True if the field is compatible, False otherwise.
Expand All @@ -1011,9 +1012,12 @@ def field_is_compatible(self, field: "Field") -> bool:
from baserow.contrib.database.fields.registries import field_type_registry

field_type = field_type_registry.get_by_model(field.specific_class)
# Allow field types to map themselves to another field type for filter
# compatibility checks.
compatible_field_type = field_type.get_compatible_filter_field_type(field)

return any(
callable(t) and t(field) or t == field_type.type
(callable(t) and t(field)) or t == compatible_field_type.type
for t in self.compatible_field_types
)

Expand Down
9 changes: 6 additions & 3 deletions backend/src/baserow/contrib/database/views/view_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1104,7 +1104,8 @@ def get_filter(self, field_name, value, model_field, field):
return Q()

field_type = field_type_registry.get_by_model(field)
filter_function = self.filter_functions[field_type.type]
effective_field_type = field_type.get_compatible_filter_field_type(field)
filter_function = self.filter_functions[effective_field_type.type]
return filter_function(field_name, value, model_field, field)

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

field_type = field_type_registry.get_by_model(field)
filter_function = self.filter_functions[field_type.type]
effective_field_type = field_type.get_compatible_filter_field_type(field)
filter_function = self.filter_functions[effective_field_type.type]
return filter_function(field_name, option_ids, model_field, field)

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

field_type = field_type_registry.get_by_model(field)
filter_function = self.filter_functions[field_type.type]
effective_field_type = field_type.get_compatible_filter_field_type(field)
filter_function = self.filter_functions[effective_field_type.type]
return filter_function(field_name, option_ids, model_field, field)

def set_import_serialized_value(self, value: str | None, id_mapping: dict) -> str:
Expand Down
8 changes: 8 additions & 0 deletions backend/src/baserow/core/formula/argument_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,11 @@ def test(self, value):

def parse(self, value):
return ensure_string(value)


class AnyBaserowRuntimeFormulaArgumentType(BaserowRuntimeFormulaArgumentType):
def test(self, value):
return True

def parse(self, value):
return value
9 changes: 5 additions & 4 deletions backend/src/baserow/core/formula/runtime_formula_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from baserow.core.formula.argument_types import (
AddableBaserowRuntimeFormulaArgumentType,
AnyBaserowRuntimeFormulaArgumentType,
BooleanBaserowRuntimeFormulaArgumentType,
DateTimeBaserowRuntimeFormulaArgumentType,
DictBaserowRuntimeFormulaArgumentType,
Expand Down Expand Up @@ -113,8 +114,8 @@ def execute(self, context: FormulaContext, args: FormulaArgs):
class RuntimeEqual(RuntimeFormulaFunction):
type = "equal"
args = [
NumberBaserowRuntimeFormulaArgumentType(),
NumberBaserowRuntimeFormulaArgumentType(),
AnyBaserowRuntimeFormulaArgumentType(),
AnyBaserowRuntimeFormulaArgumentType(),
]

def validate_number_of_args(self, args):
Expand All @@ -127,8 +128,8 @@ def execute(self, context: FormulaContext, args: FormulaArgs):
class RuntimeNotEqual(RuntimeFormulaFunction):
type = "not_equal"
args = [
NumberBaserowRuntimeFormulaArgumentType(),
NumberBaserowRuntimeFormulaArgumentType(),
AnyBaserowRuntimeFormulaArgumentType(),
AnyBaserowRuntimeFormulaArgumentType(),
]

def validate_number_of_args(self, args):
Expand Down
10 changes: 8 additions & 2 deletions backend/tests/baserow/contrib/database/field/test_field_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,6 @@
"is_even_and_whole",
],
"password": ["empty", "not_empty"],
"ai": [],
}


Expand Down Expand Up @@ -1216,7 +1215,14 @@ def test_field_type_prepare_db_value_with_invalid_values(data_fixture):
field_type.prepare_value_for_db(field, test_payload)


@pytest.mark.parametrize("field_type", field_type_registry.get_all())
@pytest.mark.parametrize(
"field_type",
[
ft
for ft in field_type_registry.get_all()
if ft.type in COMPATIBLE_FIELD_TYPE_VIEW_FILTER_TYPES
],
)
@patch("baserow.contrib.database.fields.registries.FieldTypeRegistry.get_by_model")
def test_field_type_check_can_filter_by(mock_get_by_model, field_type):
mock_get_by_model.return_value = field_type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,7 @@ def reset_metadata(schema, field_name):
"default": None,
"searchable": True,
"sortable": True,
"filterable": False,
"filterable": True,
"original_type": "ai",
"metadata": {},
"type": "string",
Expand All @@ -1060,7 +1060,7 @@ def reset_metadata(schema, field_name):
"default": None,
"searchable": True,
"sortable": True,
"filterable": False,
"filterable": True,
"original_type": "ai",
"metadata": {},
"properties": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"type": "feature",
"message": "Add filters support for AI field",
"domain": "database",
"issue_number": 3801,
"bullet_points": [],
"created_at": "2025-10-23"
}
12 changes: 12 additions & 0 deletions premium/backend/src/baserow_premium/fields/field_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,18 @@ def contains_query(self, field_name, value, model_field, field):
baserow_field_type = self.get_baserow_field_type(field)
return baserow_field_type.contains_query(field_name, value, model_field, field)

def parse_filter_value(self, field, model_field, value):
baserow_field_type = self.get_baserow_field_type(field)
return baserow_field_type.parse_filter_value(field, model_field, value)

def get_compatible_filter_field_type(self, field):
"""
For AI fields, return the underlying core field type used for filtering.
"""

ai_field_instance = field.specific if hasattr(field, "specific") else field
return self.get_baserow_field_type(ai_field_instance)

def contains_word_query(self, field_name, value, model_field, field):
baserow_field_type = self.get_baserow_field_type(field)
return baserow_field_type.contains_word_query(
Expand Down
Loading
Loading