Compare commits

...

6 Commits

Author SHA1 Message Date
Michael Genson
216ae8571c fix: Include unmade recipes when filtering by last made (#7130) 2026-02-23 18:34:16 -06:00
renovate[bot]
02d32c8905 fix(deps): update dependency openai to v2.22.0 (#7128)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-23 22:15:25 +00:00
Hayden
7e0d083e77 chore(l10n): New Crowdin updates (#7126) 2026-02-23 18:21:38 +00:00
mealie-actions[bot]
b3cea081fe chore(auto): Update pre-commit hooks (#7122)
Co-authored-by: boc-the-git <3479092+boc-the-git@users.noreply.github.com>
2026-02-23 14:07:02 +00:00
renovate[bot]
d79252752b fix(deps): update dependency fastapi to v0.131.0 (#7113)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-22 22:44:02 +00:00
Hayden
b3c214d102 chore(l10n): New Crowdin updates (#7119) 2026-02-22 22:43:45 +00:00
14 changed files with 196 additions and 150 deletions

View File

@@ -12,7 +12,7 @@ repos:
exclude: ^tests/data/ exclude: ^tests/data/
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version. # Ruff version.
rev: v0.15.1 rev: v0.15.2
hooks: hooks:
- id: ruff - id: ruff
- id: ruff-format - id: ruff-format

View File

@@ -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%"`

View File

@@ -1423,8 +1423,8 @@
"is-greater-than-or-equal-to": "jest większe lub równe", "is-greater-than-or-equal-to": "jest większe lub równe",
"is-less-than": "jest mniejsze niż", "is-less-than": "jest mniejsze niż",
"is-less-than-or-equal-to": "jest mniejsze lub równe", "is-less-than-or-equal-to": "jest mniejsze lub równe",
"is-older-than": "is older than", "is-older-than": "jest starsze niż",
"is-newer-than": "is newer than" "is-newer-than": "jest nowsze niż"
}, },
"relational-keywords": { "relational-keywords": {
"is": "jest", "is": "jest",
@@ -1436,7 +1436,7 @@
"is-not-like": "nie jest jak" "is-not-like": "nie jest jak"
}, },
"dates": { "dates": {
"days-ago": "days ago|day ago|days ago" "days-ago": "dni temu|dzień temu|dni temu"
} }
}, },
"validators": { "validators": {

View File

@@ -212,8 +212,8 @@
"upload-file": "Enviar arquivo", "upload-file": "Enviar arquivo",
"created-on-date": "Criado em {0}", "created-on-date": "Criado em {0}",
"unsaved-changes": "Você possui alterações não salvas. Deseja salvar antes de sair? Ok para salvar, Cancelar para descartar alterações.", "unsaved-changes": "Você possui alterações não salvas. Deseja salvar antes de sair? Ok para salvar, Cancelar para descartar alterações.",
"discard-changes": "Discard Changes", "discard-changes": "Descartar alterações",
"discard-changes-description": "You have unsaved changes. Are you sure you want to discard them?", "discard-changes-description": "Você tem alterações não salvas. Tem certeza de que deseja descartá-las?",
"clipboard-copy-failure": "Falha ao copiar para a área de transferência.", "clipboard-copy-failure": "Falha ao copiar para a área de transferência.",
"confirm-delete-generic-items": "Tem certeza que quer excluir os itens seguintes?", "confirm-delete-generic-items": "Tem certeza que quer excluir os itens seguintes?",
"organizers": "Organizadores", "organizers": "Organizadores",
@@ -644,7 +644,7 @@
"scrape-recipe-website-being-blocked": "Site sendo bloqueado?", "scrape-recipe-website-being-blocked": "Site sendo bloqueado?",
"scrape-recipe-try-importing-raw-html-instead": "Tente importar o HTML ao invés disso.", "scrape-recipe-try-importing-raw-html-instead": "Tente importar o HTML ao invés disso.",
"import-original-keywords-as-tags": "Importar palavras-chave originais como marcadores", "import-original-keywords-as-tags": "Importar palavras-chave originais como marcadores",
"import-original-categories": "Import original categories", "import-original-categories": "Importar categorias originais",
"stay-in-edit-mode": "Permanecer no modo de edição", "stay-in-edit-mode": "Permanecer no modo de edição",
"parse-recipe-ingredients-after-import": "Interpretar os ingredientes da receita após importar", "parse-recipe-ingredients-after-import": "Interpretar os ingredientes da receita após importar",
"import-from-zip": "Importar do .zip", "import-from-zip": "Importar do .zip",
@@ -1423,7 +1423,7 @@
"is-greater-than-or-equal-to": "é maior ou igual a", "is-greater-than-or-equal-to": "é maior ou igual a",
"is-less-than": "é menor que", "is-less-than": "é menor que",
"is-less-than-or-equal-to": "é menor ou igual a", "is-less-than-or-equal-to": "é menor ou igual a",
"is-older-than": "is older than", "is-older-than": "Mais antigo que",
"is-newer-than": "is newer than" "is-newer-than": "is newer than"
}, },
"relational-keywords": { "relational-keywords": {

View File

@@ -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"""

View File

@@ -2494,25 +2494,25 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Backstein Käse", "name": "Backstein Käse",
"plural_name": "brick cheese" "plural_name": "Brick-Käse"
}, },
"quick-melt cheese": { "quick-melt cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "quick-melt cheese", "name": "schnellschmelzender Käse",
"plural_name": "quick-melt cheese" "plural_name": "schnellschmelzender Käse"
}, },
"farmer's cheese": { "farmer's cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "farmer's cheese", "name": "Bauernkäse",
"plural_name": "farmer's cheese" "plural_name": "Bauernkäse"
}, },
"manouri cheese": { "manouri cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "manouri cheese", "name": "manouri cheese",
"plural_name": "manouri cheese" "plural_name": "Manouri se"
}, },
"mimolette cheese": { "mimolette cheese": {
"aliases": [], "aliases": [],
@@ -7670,7 +7670,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "Backpulver", "name": "Backpulver",
"plural_name": "baking powder" "plural_name": "Backpulver"
}, },
"baking soda": { "baking soda": {
"aliases": [], "aliases": [],

View File

@@ -5,7 +5,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "fokhagyma", "name": "fokhagyma",
"plural_name": "garlic" "plural_name": "fokhagyma"
}, },
"onion": { "onion": {
"aliases": [], "aliases": [],
@@ -59,13 +59,13 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "zeller", "name": "zeller",
"plural_name": "celery" "plural_name": "zeller"
}, },
"jalapeño": { "jalapeño": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "jalapeño", "name": "jalapeno",
"plural_name": "jalapeños" "plural_name": "jalapeno"
}, },
"avocado": { "avocado": {
"aliases": [], "aliases": [],
@@ -95,13 +95,13 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "spenót", "name": "spenót",
"plural_name": "spinach" "plural_name": "spenót"
}, },
"sweet corn": { "sweet corn": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "csemegekukorica", "name": "csemegekukorica",
"plural_name": "sweet corn" "plural_name": "csemegekukorica"
}, },
"chile pepper": { "chile pepper": {
"aliases": [ "aliases": [
@@ -121,7 +121,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "brokkoli", "name": "brokkoli",
"plural_name": "broccoli" "plural_name": "brokkoli"
}, },
"heart of palm": { "heart of palm": {
"aliases": [], "aliases": [],
@@ -132,8 +132,8 @@
"baby greens": { "baby greens": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "baby greens", "name": "salátakeverék",
"plural_name": "baby greens" "plural_name": "salátakeverék"
}, },
"pumpkin": { "pumpkin": {
"aliases": [], "aliases": [],
@@ -145,7 +145,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "karfiol", "name": "karfiol",
"plural_name": "cauliflower" "plural_name": "karfiol"
}, },
"cabbage": { "cabbage": {
"aliases": [], "aliases": [],
@@ -156,20 +156,20 @@
"asparagus": { "asparagus": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "asparagus", "name": "spárga",
"plural_name": "asparagus" "plural_name": "spárga"
}, },
"kale": { "kale": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "kelkáposzta", "name": "kelkáposzta",
"plural_name": "kale" "plural_name": "kelkáposzta"
}, },
"arugula": { "arugula": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "rukkola", "name": "rukkola",
"plural_name": "arugula" "plural_name": "rukkola"
}, },
"leek": { "leek": {
"aliases": [], "aliases": [],
@@ -187,7 +187,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "saláta", "name": "saláta",
"plural_name": "lettuce" "plural_name": "fejes saláta"
}, },
"butternut squash": { "butternut squash": {
"aliases": [], "aliases": [],
@@ -198,8 +198,8 @@
"romaine lettuce": { "romaine lettuce": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "romaine lettuce", "name": "római saláta",
"plural_name": "romaine lettuce" "plural_name": "római saláta"
}, },
"beetroot": { "beetroot": {
"aliases": [], "aliases": [],
@@ -217,7 +217,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "édeskömény", "name": "édeskömény",
"plural_name": "fennel" "plural_name": "édeskömény"
}, },
"sun dried tomato": { "sun dried tomato": {
"aliases": [], "aliases": [],
@@ -261,8 +261,8 @@
"mixed greens": { "mixed greens": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mixed greens", "name": "salátakeverék",
"plural_name": "mixed greens" "plural_name": "salátakeverék"
}, },
"parsnip": { "parsnip": {
"aliases": [], "aliases": [],
@@ -279,8 +279,8 @@
"mixed vegetables": { "mixed vegetables": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mixed vegetables", "name": "zöldségkeverék",
"plural_name": "mixed vegetables" "plural_name": "zöldségkeverék"
}, },
"poblano pepper": { "poblano pepper": {
"aliases": [], "aliases": [],
@@ -304,7 +304,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "cayenne bors", "name": "cayenne bors",
"plural_name": "cayenne pepper" "plural_name": "cayenne bors"
}, },
"green tomato": { "green tomato": {
"aliases": [], "aliases": [],
@@ -321,8 +321,8 @@
"iceberg lettuce": { "iceberg lettuce": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "iceberg lettuce", "name": "jégsaláta",
"plural_name": "iceberg lettuce" "plural_name": "jégsaláta"
}, },
"mashed potato": { "mashed potato": {
"aliases": [], "aliases": [],
@@ -340,13 +340,13 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mángold", "name": "mángold",
"plural_name": "chard" "plural_name": "mángold"
}, },
"pimiento": { "pimiento": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "kápia paprika", "name": "kápia paprika",
"plural_name": "pimientos" "plural_name": "kápia paprika"
}, },
"spaghetti squash": { "spaghetti squash": {
"aliases": [], "aliases": [],
@@ -358,7 +358,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "fejes saláta", "name": "fejes saláta",
"plural_name": "butter lettuce" "plural_name": "kötözősaláta"
}, },
"hash brown": { "hash brown": {
"aliases": [], "aliases": [],
@@ -419,8 +419,8 @@
"corn on the cob": { "corn on the cob": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "corn on the cob", "name": "csöves kukorica",
"plural_name": "corn on the cob" "plural_name": "csöves kukorica"
}, },
"radicchio": { "radicchio": {
"aliases": [], "aliases": [],
@@ -438,7 +438,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "brokkolini", "name": "brokkolini",
"plural_name": "tenderstem broccoli" "plural_name": "bébi brokkoli"
}, },
"plantain": { "plantain": {
"aliases": [], "aliases": [],
@@ -450,7 +450,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "saláta", "name": "saláta",
"plural_name": "leaf lettuce" "plural_name": "saláta"
}, },
"pepperoncini": { "pepperoncini": {
"aliases": [], "aliases": [],
@@ -484,11 +484,11 @@
}, },
"maize": { "maize": {
"aliases": [ "aliases": [
"corn husk" "kukoricacsuhé"
], ],
"description": "", "description": "",
"name": "maize", "name": "csemegekukorica",
"plural_name": "maize" "plural_name": "csemegekukorica"
}, },
"collard greens": { "collard greens": {
"aliases": [], "aliases": [],
@@ -3140,13 +3140,13 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "kókusztej", "name": "kókusztej",
"plural_name": "coconut milk" "plural_name": "kókusztej"
}, },
"almond milk": { "almond milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mandulatej", "name": "mandulatej",
"plural_name": "almond milk" "plural_name": "mandulatej"
}, },
"almond butter": { "almond butter": {
"aliases": [], "aliases": [],
@@ -3182,7 +3182,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "szójatej", "name": "szójatej",
"plural_name": "soy milk" "plural_name": "szójatej"
}, },
"extra firm tofu": { "extra firm tofu": {
"aliases": [], "aliases": [],

View File

@@ -487,8 +487,8 @@
"łuska kukurydzy" "łuska kukurydzy"
], ],
"description": "", "description": "",
"name": "maize", "name": "kukurydza",
"plural_name": "maize" "plural_name": "kukurydza"
}, },
"collard greens": { "collard greens": {
"aliases": [], "aliases": [],
@@ -554,7 +554,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sałata masłowa", "name": "sałata masłowa",
"plural_name": "boston lettuce" "plural_name": "sałata masłowa"
}, },
"kohlrabi": { "kohlrabi": {
"aliases": [], "aliases": [],
@@ -598,7 +598,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "surówka z brokułów", "name": "surówka z brokułów",
"plural_name": "broccoli slaw" "plural_name": "surówka z brokułów"
}, },
"arbol chile pepper": { "arbol chile pepper": {
"aliases": [], "aliases": [],
@@ -974,7 +974,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pomelo", "name": "pomelo",
"plural_name": "pomelos" "plural_name": "pomelo"
}, },
"chestnut purée": { "chestnut purée": {
"aliases": [], "aliases": [],

View File

@@ -5,7 +5,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "alho", "name": "alho",
"plural_name": "garlic" "plural_name": "alho"
}, },
"onion": { "onion": {
"aliases": [], "aliases": [],
@@ -59,7 +59,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "salsão", "name": "salsão",
"plural_name": "celery" "plural_name": "aipo"
}, },
"jalapeño": { "jalapeño": {
"aliases": [], "aliases": [],

View File

@@ -101,7 +101,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "milho doce", "name": "milho doce",
"plural_name": "sweet corn" "plural_name": "milho doce"
}, },
"chile pepper": { "chile pepper": {
"aliases": [ "aliases": [
@@ -280,7 +280,7 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mixed vegetables", "name": "mixed vegetables",
"plural_name": "mixed vegetables" "plural_name": "mistura de vegetais"
}, },
"poblano pepper": { "poblano pepper": {
"aliases": [], "aliases": [],
@@ -1671,8 +1671,8 @@
"chokeberry": { "chokeberry": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chokeberry", "name": "arónia",
"plural_name": "chokeberries" "plural_name": "arónias"
}, },
"loganberry": { "loganberry": {
"aliases": [], "aliases": [],
@@ -1717,7 +1717,7 @@
"walnut": { "walnut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "walnut", "name": "noz",
"plural_name": "walnuts" "plural_name": "walnuts"
}, },
"pecan": { "pecan": {
@@ -1783,8 +1783,8 @@
"pumpkin seed": { "pumpkin seed": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pumpkin seed", "name": "semente de abóbora",
"plural_name": "pumpkin seeds" "plural_name": "sementes de abóbora"
}, },
"hazelnut": { "hazelnut": {
"aliases": [], "aliases": [],
@@ -1801,8 +1801,8 @@
"sunflower seed": { "sunflower seed": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sunflower seed", "name": "semente de girassol",
"plural_name": "sunflower seeds" "plural_name": "sementes de girassol"
}, },
"macadamia": { "macadamia": {
"aliases": [], "aliases": [],
@@ -1813,8 +1813,8 @@
"roasted peanut": { "roasted peanut": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "roasted peanut", "name": "amendoim torrado",
"plural_name": "roasted peanuts" "plural_name": "amendoins torrados"
}, },
"chopped nut": { "chopped nut": {
"aliases": [], "aliases": [],
@@ -2003,22 +2003,22 @@
"parmesan cheese": { "parmesan cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "parmesan cheese", "name": "queijo parmesão",
"plural_name": "parmesan cheese" "plural_name": "parmesan cheese"
}, },
"cheddar cheese": { "cheddar cheese": {
"aliases": [ "aliases": [
"cheddar cheese" "queijo cheddar"
], ],
"description": "", "description": "",
"name": "cheddar cheese", "name": "queijo cheddar",
"plural_name": "cheddar cheese" "plural_name": "queijos cheddar"
}, },
"cream cheese": { "cream cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "cream cheese", "name": "queijo creme",
"plural_name": "cream cheese" "plural_name": "queijo creme"
}, },
"sharp cheddar cheese": { "sharp cheddar cheese": {
"aliases": [ "aliases": [
@@ -2031,26 +2031,26 @@
"cheese": { "cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "cheese", "name": "queijo",
"plural_name": "cheese" "plural_name": "queijos"
}, },
"mozzarella cheese": { "mozzarella cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "mozzarella cheese", "name": "queijo mozzarella",
"plural_name": "mozzarella cheese" "plural_name": "queijos mozzarella"
}, },
"feta cheese": { "feta cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "feta cheese", "name": "queijo feta",
"plural_name": "feta cheese" "plural_name": "queijos feta"
}, },
"ricotta cheese": { "ricotta cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "ricotta cheese", "name": "queijo ricotta",
"plural_name": "ricotta cheese" "plural_name": "queijos ricotta"
}, },
"cheddar-jack cheese": { "cheddar-jack cheese": {
"aliases": [], "aliases": [],
@@ -2139,8 +2139,8 @@
"brie cheese": { "brie cheese": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "brie cheese", "name": "queijo brie",
"plural_name": "brie cheese" "plural_name": "queijos brie"
}, },
"paneer cheese": { "paneer cheese": {
"aliases": [], "aliases": [],
@@ -2617,26 +2617,26 @@
"butter": { "butter": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "butter", "name": "manteiga",
"plural_name": "butter" "plural_name": "manteigas"
}, },
"egg": { "egg": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "egg", "name": "ovo",
"plural_name": "eggs" "plural_name": "ovos"
}, },
"milk": { "milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "milk", "name": "leite",
"plural_name": "milk" "plural_name": "leites"
}, },
"heavy cream": { "heavy cream": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "heavy cream", "name": "nata",
"plural_name": "heavy cream" "plural_name": "natas"
}, },
"sour cream": { "sour cream": {
"aliases": [], "aliases": [],
@@ -2691,8 +2691,8 @@
"condensed milk": { "condensed milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "condensed milk", "name": "leite condensado",
"plural_name": "condensed milk" "plural_name": "leites condensados"
}, },
"half and half": { "half and half": {
"aliases": [], "aliases": [],
@@ -2703,20 +2703,20 @@
"sweetened condensed milk": { "sweetened condensed milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "sweetened condensed milk", "name": "leite condensado açucarado",
"plural_name": "sweetened condensed milk" "plural_name": "leites condensados açucarados"
}, },
"ice cream": { "ice cream": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "ice cream", "name": "gelado",
"plural_name": "ice cream" "plural_name": "gelados"
}, },
"margarine": { "margarine": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "margarine", "name": "margarina",
"plural_name": "margarines" "plural_name": "margarinas"
}, },
"creme fraiche": { "creme fraiche": {
"aliases": [], "aliases": [],
@@ -2757,8 +2757,8 @@
"dulce de leche": { "dulce de leche": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "dulce de leche", "name": "doce de leite",
"plural_name": "dulce de leche" "plural_name": "doces de leite"
}, },
"custard": { "custard": {
"aliases": [], "aliases": [],
@@ -2787,8 +2787,8 @@
"chocolate milk": { "chocolate milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chocolate milk", "name": "leite achocolatado",
"plural_name": "chocolate milk" "plural_name": "leites achocolatados"
}, },
"liquid egg substitute": { "liquid egg substitute": {
"aliases": [], "aliases": [],
@@ -3325,8 +3325,8 @@
"vegan sausage": { "vegan sausage": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "vegan sausage", "name": "salsicha vegan",
"plural_name": "vegan sausages" "plural_name": "salsichas vegan"
}, },
"coconut whipped cream": { "coconut whipped cream": {
"aliases": [], "aliases": [],
@@ -3373,8 +3373,8 @@
"nut milk": { "nut milk": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "nut milk", "name": "leite de noz",
"plural_name": "nut milk" "plural_name": "leite de nozes"
}, },
"non-dairy cream": { "non-dairy cream": {
"aliases": [], "aliases": [],
@@ -3744,37 +3744,37 @@
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "bacon", "name": "bacon",
"plural_name": "bacons" "plural_name": "bacon"
}, },
"chopped bacon": { "chopped bacon": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chopped bacon", "name": "bacon",
"plural_name": "chopped bacons" "plural_name": "bacon"
}, },
"ground beef": { "ground beef": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "ground beef", "name": "carne picada",
"plural_name": "ground beefs" "plural_name": "carne picada"
}, },
"beef steak": { "beef steak": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "beef steak", "name": "bife de vaca",
"plural_name": "beef steaks" "plural_name": "bifes de vaca"
}, },
"ham": { "ham": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "ham", "name": "fiambre",
"plural_name": "hams" "plural_name": "fiambres"
}, },
"pork chop": { "pork chop": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "pork chop", "name": "costoleta",
"plural_name": "pork chops" "plural_name": "costoletas"
}, },
"sweet italian sausage": { "sweet italian sausage": {
"aliases": [], "aliases": [],
@@ -3827,8 +3827,8 @@
"chorizo": { "chorizo": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "chorizo", "name": "chouriço",
"plural_name": "chorizoes" "plural_name": "chouriços"
}, },
"pancetta": { "pancetta": {
"aliases": [], "aliases": [],
@@ -3899,8 +3899,8 @@
"deli ham": { "deli ham": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "deli ham", "name": "fiambre",
"plural_name": "deli hams" "plural_name": "fiambres"
}, },
"leg of lamb": { "leg of lamb": {
"aliases": [], "aliases": [],
@@ -10638,8 +10638,8 @@
"olive oil": { "olive oil": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "olive oil", "name": "azeite",
"plural_name": "olive oil" "plural_name": "azeite"
}, },
"vegetable oil": { "vegetable oil": {
"aliases": [], "aliases": [],
@@ -10650,8 +10650,8 @@
"extra virgin olive oil": { "extra virgin olive oil": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "extra virgin olive oil", "name": "azeite extra virgem",
"plural_name": "extra virgin olive oil" "plural_name": "azeite extra virgem"
}, },
"canola oil": { "canola oil": {
"aliases": [], "aliases": [],
@@ -10916,8 +10916,8 @@
"herb-infused olive oil": { "herb-infused olive oil": {
"aliases": [], "aliases": [],
"description": "", "description": "",
"name": "herb-infused olive oil", "name": "azeite de ervas",
"plural_name": "herb-infused olive oil" "plural_name": "azeite de ervas"
}, },
"roasted peanut oil": { "roasted peanut oil": {
"aliases": [], "aliases": [],

View File

@@ -9,7 +9,7 @@
"name": "colher de sopa", "name": "colher de sopa",
"plural_name": "colheres de sopa", "plural_name": "colheres de sopa",
"description": "", "description": "",
"abbreviation": "csp" "abbreviation": "c. sopa"
}, },
"cup": { "cup": {
"name": "chávena", "name": "chávena",
@@ -25,7 +25,7 @@
}, },
"pint": { "pint": {
"name": "pint", "name": "pint",
"plural_name": "pintas", "plural_name": "pints",
"description": "", "description": "",
"abbreviation": "pt" "abbreviation": "pt"
}, },
@@ -139,8 +139,8 @@
"abbreviation": "" "abbreviation": ""
}, },
"sprig": { "sprig": {
"name": "sprig", "name": "ramo",
"plural_name": "sprigs", "plural_name": "ramos",
"description": "", "description": "",
"abbreviation": "" "abbreviation": ""
} }

View File

@@ -17,7 +17,7 @@ dependencies = [
"apprise==1.9.7", "apprise==1.9.7",
"bcrypt==5.0.0", "bcrypt==5.0.0",
"extruct==0.18.0", "extruct==0.18.0",
"fastapi==0.129.1", "fastapi==0.131.0",
"httpx==0.28.1", "httpx==0.28.1",
"lxml==6.0.2", "lxml==6.0.2",
"orjson==3.11.7", "orjson==3.11.7",
@@ -42,7 +42,7 @@ dependencies = [
"pydantic-settings==2.13.1", "pydantic-settings==2.13.1",
"pillow-heif==1.2.1", "pillow-heif==1.2.1",
"pyjwt==2.11.0", "pyjwt==2.11.0",
"openai==2.21.0", "openai==2.22.0",
"typing-extensions==4.15.0", "typing-extensions==4.15.0",
"itsdangerous==2.2.0", "itsdangerous==2.2.0",
"ingredient-parser-nlp==2.5.0", "ingredient-parser-nlp==2.5.0",

View File

@@ -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

16
uv.lock generated
View File

@@ -399,7 +399,7 @@ wheels = [
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.129.1" version = "0.131.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "annotated-doc" }, { name = "annotated-doc" },
@@ -408,9 +408,9 @@ dependencies = [
{ name = "typing-extensions" }, { name = "typing-extensions" },
{ name = "typing-inspection" }, { name = "typing-inspection" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/4aa7f6ce92745458b6ce0acd706dde2ac23a3bf341266b5311c904109f67/fastapi-0.129.1.tar.gz", hash = "sha256:6ccf0eca9644e0d6280115b4fc8281bf55ec5878d4d95572f7b2034ab15708ba", size = 369852, upload-time = "2026-02-21T13:10:03.335Z" } sdist = { url = "https://files.pythonhosted.org/packages/91/32/158cbf685b7d5a26f87131069da286bf10fc9fbf7fc968d169d48a45d689/fastapi-0.131.0.tar.gz", hash = "sha256:6531155e52bee2899a932c746c9a8250f210e3c3303a5f7b9f8a808bfe0548ff", size = 369612, upload-time = "2026-02-22T16:38:11.252Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/01/f9/f15d92bd6035d4f83be8b82dc527a3e7abc87648fda62cf8d1df344410a7/fastapi-0.129.1-py3-none-any.whl", hash = "sha256:022462403bc385b791df418d8f088eb0e8f1fe7cb8f625d682f5e9da6157cc83", size = 103226, upload-time = "2026-02-21T13:10:05.058Z" }, { url = "https://files.pythonhosted.org/packages/ff/94/b58ec24c321acc2ad1327f69b033cadc005e0f26df9a73828c9e9c7db7ce/fastapi-0.131.0-py3-none-any.whl", hash = "sha256:ed0e53decccf4459de78837ce1b867cd04fa9ce4579497b842579755d20b405a", size = 103854, upload-time = "2026-02-22T16:38:09.814Z" },
] ]
[[package]] [[package]]
@@ -910,7 +910,7 @@ requires-dist = [
{ name = "bcrypt", specifier = "==5.0.0" }, { name = "bcrypt", specifier = "==5.0.0" },
{ name = "beautifulsoup4", specifier = "==4.14.3" }, { name = "beautifulsoup4", specifier = "==4.14.3" },
{ name = "extruct", specifier = "==0.18.0" }, { name = "extruct", specifier = "==0.18.0" },
{ name = "fastapi", specifier = "==0.129.1" }, { name = "fastapi", specifier = "==0.131.0" },
{ name = "html2text", specifier = "==2025.4.15" }, { name = "html2text", specifier = "==2025.4.15" },
{ name = "httpx", specifier = "==0.28.1" }, { name = "httpx", specifier = "==0.28.1" },
{ name = "ingredient-parser-nlp", specifier = "==2.5.0" }, { name = "ingredient-parser-nlp", specifier = "==2.5.0" },
@@ -918,7 +918,7 @@ requires-dist = [
{ name = "itsdangerous", specifier = "==2.2.0" }, { name = "itsdangerous", specifier = "==2.2.0" },
{ name = "jinja2", specifier = "==3.1.6" }, { name = "jinja2", specifier = "==3.1.6" },
{ name = "lxml", specifier = "==6.0.2" }, { name = "lxml", specifier = "==6.0.2" },
{ name = "openai", specifier = "==2.21.0" }, { name = "openai", specifier = "==2.22.0" },
{ name = "orjson", specifier = "==3.11.7" }, { name = "orjson", specifier = "==3.11.7" },
{ name = "paho-mqtt", specifier = "==1.6.1" }, { name = "paho-mqtt", specifier = "==1.6.1" },
{ name = "pillow", specifier = "==12.1.1" }, { name = "pillow", specifier = "==12.1.1" },
@@ -1143,7 +1143,7 @@ wheels = [
[[package]] [[package]]
name = "openai" name = "openai"
version = "2.21.0" version = "2.22.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
@@ -1155,9 +1155,9 @@ dependencies = [
{ name = "tqdm" }, { name = "tqdm" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/92/e5/3d197a0947a166649f566706d7a4c8f7fe38f1fa7b24c9bcffe4c7591d44/openai-2.21.0.tar.gz", hash = "sha256:81b48ce4b8bbb2cc3af02047ceb19561f7b1dc0d4e52d1de7f02abfd15aa59b7", size = 644374, upload-time = "2026-02-14T00:12:01.577Z" } sdist = { url = "https://files.pythonhosted.org/packages/73/ed/0a004a42fea6b6f3dd4ab33235183e994a4c7ade214fba10d9494577ec04/openai-2.22.0.tar.gz", hash = "sha256:fc2ea71c79951ac3faf178ff72c766bb4b09c3e9aab277184c5260ab3e94294f", size = 657093, upload-time = "2026-02-23T20:14:31.017Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/56/0a89092a453bb2c676d66abee44f863e742b2110d4dbb1dbcca3f7e5fc33/openai-2.21.0-py3-none-any.whl", hash = "sha256:0bc1c775e5b1536c294eded39ee08f8407656537ccc71b1004104fe1602e267c", size = 1103065, upload-time = "2026-02-14T00:11:59.603Z" }, { url = "https://files.pythonhosted.org/packages/dc/9a/ac24d606ea7e729475100689a1fe8866fe6cbcd0fd9b93dc4b8324be353d/openai-2.22.0-py3-none-any.whl", hash = "sha256:df02cfb731fe312215d046bf1330030e0f4b70a7b880b96992b1517b0b6aced8", size = 1118913, upload-time = "2026-02-23T20:14:29.546Z" },
] ]
[[package]] [[package]]