mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-26 09:43:19 -05:00
fix: Include unmade recipes when filtering by last made (#7130)
This commit is contained in:
@@ -79,8 +79,8 @@ This filter will find all foods that are not named "carrot": <br>
|
|||||||
##### Keyword Filters
|
##### Keyword Filters
|
||||||
The API supports many SQL keywords, such as `IS NULL` and `IN`, as well as their negations (e.g. `IS NOT NULL` and `NOT IN`).
|
The API supports many SQL keywords, such as `IS NULL` and `IN`, as well as their negations (e.g. `IS NOT NULL` and `NOT IN`).
|
||||||
|
|
||||||
Here is an example of a filter that returns all recipes where the "last made" value is not null: <br>
|
Here is an example of a filter that returns all shopping list items without a food: <br>
|
||||||
`lastMade IS NOT NULL`
|
`foodId IS NULL`
|
||||||
|
|
||||||
This filter will find all recipes that don't start with the word "Test": <br>
|
This filter will find all recipes that don't start with the word "Test": <br>
|
||||||
`name NOT LIKE "Test%"`
|
`name NOT LIKE "Test%"`
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import re as re
|
import re as re
|
||||||
from collections.abc import Iterable, Sequence
|
from collections.abc import Iterable, Sequence
|
||||||
|
from datetime import UTC, datetime
|
||||||
from random import randint
|
from random import randint
|
||||||
from typing import Self, cast
|
from typing import Self, cast
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
@@ -51,10 +52,13 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def _get_last_made_col_alias(self) -> sa.ColumnElement | None:
|
def _get_last_made_col_alias(self) -> sa.ColumnElement | None:
|
||||||
"""Computed last_made which uses `HouseholdToRecipe.last_made` for the user's household, otherwise None"""
|
"""
|
||||||
|
Computed last_made which uses `HouseholdToRecipe.last_made` for the user's household,
|
||||||
|
otherwise an arbitrarily low date
|
||||||
|
"""
|
||||||
|
|
||||||
user_household_subquery = sa.select(User.household_id).where(User.id == self.user_id).scalar_subquery()
|
user_household_subquery = sa.select(User.household_id).where(User.id == self.user_id).scalar_subquery()
|
||||||
return (
|
last_made_subquery = (
|
||||||
sa.select(HouseholdToRecipe.last_made)
|
sa.select(HouseholdToRecipe.last_made)
|
||||||
.where(
|
.where(
|
||||||
HouseholdToRecipe.recipe_id == self.model.id,
|
HouseholdToRecipe.recipe_id == self.model.id,
|
||||||
@@ -63,6 +67,7 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
|
|||||||
.correlate(self.model)
|
.correlate(self.model)
|
||||||
.scalar_subquery()
|
.scalar_subquery()
|
||||||
)
|
)
|
||||||
|
return sa.func.coalesce(last_made_subquery, datetime(year=1900, month=1, day=1, tzinfo=UTC))
|
||||||
|
|
||||||
def _get_rating_col_alias(self) -> sa.ColumnElement | None:
|
def _get_rating_col_alias(self) -> sa.ColumnElement | None:
|
||||||
"""Computed rating which uses the user's rating if it exists, otherwise falling back to the recipe's rating"""
|
"""Computed rating which uses the user's rating if it exists, otherwise falling back to the recipe's rating"""
|
||||||
|
|||||||
@@ -647,6 +647,47 @@ def test_order_by_last_made(unique_user: TestUser, h2_user: TestUser):
|
|||||||
assert [item.id for item in h2_query.items] == [recipe_2.id, recipe_1.id]
|
assert [item.id for item in h2_query.items] == [recipe_2.id, recipe_1.id]
|
||||||
|
|
||||||
|
|
||||||
|
def test_coalesce_last_made(unique_user: TestUser):
|
||||||
|
dt = datetime.now(UTC)
|
||||||
|
|
||||||
|
made_recipe, unmade_recipe = (
|
||||||
|
unique_user.repos.recipes.create(
|
||||||
|
Recipe(user_id=unique_user.user_id, group_id=unique_user.group_id, name=random_string())
|
||||||
|
)
|
||||||
|
for _ in range(2)
|
||||||
|
)
|
||||||
|
unique_user.repos.household_recipes.create(
|
||||||
|
HouseholdRecipeCreate(recipe_id=made_recipe.id, household_id=unique_user.household_id, last_made=dt)
|
||||||
|
)
|
||||||
|
|
||||||
|
repos = get_repositories(
|
||||||
|
unique_user.repos.session, group_id=unique_user.group_id, household_id=None
|
||||||
|
).recipes.by_user(unique_user.user_id)
|
||||||
|
r = repos.page_all(
|
||||||
|
PaginationQuery(
|
||||||
|
page=1,
|
||||||
|
per_page=-1,
|
||||||
|
order_by="last_made",
|
||||||
|
order_direction=OrderDirection.asc,
|
||||||
|
query_filter=f"id IN [{made_recipe.id}, {unmade_recipe.id}] AND lastMade <= {dt.isoformat()}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert len(r.items) == 2
|
||||||
|
assert [item.id for item in r.items] == [unmade_recipe.id, made_recipe.id]
|
||||||
|
|
||||||
|
r = repos.page_all(
|
||||||
|
PaginationQuery(
|
||||||
|
page=1,
|
||||||
|
per_page=-1,
|
||||||
|
order_by="last_made",
|
||||||
|
order_direction=OrderDirection.desc,
|
||||||
|
query_filter=f"id IN [{made_recipe.id}, {unmade_recipe.id}]",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert len(r.items) == 2
|
||||||
|
assert [item.id for item in r.items] == [made_recipe.id, unmade_recipe.id]
|
||||||
|
|
||||||
|
|
||||||
def test_order_by_rating(user_tuple: tuple[TestUser, TestUser]):
|
def test_order_by_rating(user_tuple: tuple[TestUser, TestUser]):
|
||||||
user_1, user_2 = user_tuple
|
user_1, user_2 = user_tuple
|
||||||
database = user_1.repos
|
database = user_1.repos
|
||||||
|
|||||||
Reference in New Issue
Block a user