1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-07-19 05:09:40 +02:00

fix: Case Insensitive Query Filters (#5162)

This commit is contained in:
Michael Genson 2025-03-10 05:56:12 -05:00 committed by GitHub
parent 4ecfd8ec78
commit ad59e653da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 43 additions and 2 deletions

View file

@ -180,6 +180,9 @@ class QueryFilterBuilderComponent:
if v is None:
continue
if isinstance(model_attr_type, sqltypes.String):
sanitized_values[i] = v.lower()
if self.relationship is RelationalKeyword.LIKE or self.relationship is RelationalKeyword.NOT_LIKE:
if not isinstance(model_attr_type, sqltypes.String):
raise ValueError(
@ -336,6 +339,9 @@ class QueryFilterBuilder:
def _get_filter_element(
component: QueryFilterBuilderComponent, model, model_attr, model_attr_type
) -> sa.ColumnElement:
if isinstance(model_attr_type, sqltypes.String):
model_attr = sa.func.lower(model_attr)
# Keywords
if component.relationship is RelationalKeyword.IS:
element = model_attr.is_(component.validate(model_attr_type))
@ -351,9 +357,9 @@ class QueryFilterBuilder:
for v in component.validate(model_attr_type):
element = sa.and_(element, primary_model_attr.any(model_attr == v))
elif component.relationship is RelationalKeyword.LIKE:
element = model_attr.like(component.validate(model_attr_type))
element = model_attr.ilike(component.validate(model_attr_type))
elif component.relationship is RelationalKeyword.NOT_LIKE:
element = model_attr.not_like(component.validate(model_attr_type))
element = model_attr.not_ilike(component.validate(model_attr_type))
# Operators
elif component.relationship is RelationalOperator.EQ:

View file

@ -216,6 +216,29 @@ def test_pagination_filter_basic(query_units: tuple[RepositoryUnit, IngredientUn
assert unit_results[0].id == unit_2.id
def test_pagination_filter_string_case_insensitive(
query_units: tuple[RepositoryUnit, IngredientUnit, IngredientUnit, IngredientUnit],
):
units_repo, *units = query_units
target_unit = random.choice(units)
query = PaginationQuery(page=1, per_page=-1, query_filter=f'name="{target_unit.name.upper()}"')
unit_results = units_repo.page_all(query).items
assert len(unit_results) == 1
assert unit_results[0].id == target_unit.id
upper_unit = units_repo.create(
SaveIngredientUnit(name="mIxEd-CaSe uNiT", group_id=units_repo.group_id, use_abbreviation=True)
)
try:
query = PaginationQuery(page=1, per_page=-1, query_filter=f'name="{upper_unit.name.lower()}"')
unit_results = units_repo.page_all(query).items
assert len(unit_results) == 1
assert unit_results[0].id == upper_unit.id
finally:
units_repo.delete(upper_unit.id)
def test_pagination_filter_null(unique_user: TestUser):
database = unique_user.repos
recipe_not_made_1 = database.recipes.create(
@ -425,6 +448,18 @@ def test_pagination_filter_like(query_units: tuple[RepositoryUnit, IngredientUni
assert unit_3.id in result_ids
def test_pagination_filter_like_case_insensitive(
query_units: tuple[RepositoryUnit, IngredientUnit, IngredientUnit, IngredientUnit],
):
units_repo, unit_1, *_ = query_units
query = PaginationQuery(page=1, per_page=-1, query_filter=r'name LIKE "%EST UNIT 1%"')
unit_results = units_repo.page_all(query).items
assert len(unit_results) == 1
assert unit_results[0].id == unit_1.id
def test_pagination_filter_keyword_namespace_conflict(unique_user: TestUser):
database = unique_user.repos
recipe_rating_1 = database.recipes.create(