mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-24 03:05:12 -05:00
fix: Only fetch recipes with a household id (#6773)
This commit is contained in:
@@ -101,4 +101,14 @@ const { store: tags } = isOwnGroup.value ? useTagStore() : usePublicTagStore(gro
|
||||
const { store: tools } = isOwnGroup.value ? useToolStore() : usePublicToolStore(groupSlug.value);
|
||||
const { store: foods } = isOwnGroup.value ? useFoodStore() : usePublicFoodStore(groupSlug.value);
|
||||
const { store: households } = isOwnGroup.value ? useHouseholdStore() : usePublicHouseholdStore(groupSlug.value);
|
||||
|
||||
watch(
|
||||
households,
|
||||
() => {
|
||||
// if exactly one household exists, then we shouldn't be filtering by household
|
||||
if (households.value.length == 1) {
|
||||
selectedHouseholds.value = [];
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -9,6 +9,7 @@ from typing import Any
|
||||
from fastapi import HTTPException
|
||||
from pydantic import UUID4, BaseModel
|
||||
from sqlalchemy import ColumnElement, Select, case, delete, func, nulls_first, nulls_last, select
|
||||
from sqlalchemy.ext.associationproxy import AssociationProxyInstance
|
||||
from sqlalchemy.orm import InstrumentedAttribute
|
||||
from sqlalchemy.orm.session import Session
|
||||
from sqlalchemy.sql import sqltypes
|
||||
@@ -77,6 +78,13 @@ class RepositoryGeneric[Schema: MealieModel, Model: SqlAlchemyBase]:
|
||||
|
||||
def _query(self, override_schema: type[MealieModel] | None = None, with_options=True):
|
||||
q = select(self.model)
|
||||
|
||||
try:
|
||||
if isinstance(self.model.household_id, AssociationProxyInstance):
|
||||
q.filter(self.model.household_id.is_not(None))
|
||||
except (AttributeError, NotImplementedError):
|
||||
pass
|
||||
|
||||
if with_options:
|
||||
schema = override_schema or self.schema
|
||||
return q.options(*schema.loader_options())
|
||||
@@ -87,6 +95,7 @@ class RepositoryGeneric[Schema: MealieModel, Model: SqlAlchemyBase]:
|
||||
dct = {}
|
||||
if self.group_id:
|
||||
dct["group_id"] = self.group_id
|
||||
|
||||
if self.household_id:
|
||||
dct["household_id"] = self.household_id
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ from mealie.db.models.users.user_to_recipe import UserToRecipe
|
||||
from mealie.db.models.users.users import User
|
||||
from mealie.schema.cookbook.cookbook import ReadCookBook
|
||||
from mealie.schema.recipe import Recipe
|
||||
from mealie.schema.recipe.recipe import RecipeCategory, RecipePagination, RecipeSummary, create_recipe_slug
|
||||
from mealie.schema.recipe.recipe import RecipePagination, RecipeSummary, create_recipe_slug
|
||||
from mealie.schema.recipe.recipe_ingredient import IngredientFood
|
||||
from mealie.schema.recipe.recipe_suggestion import RecipeSuggestionQuery, RecipeSuggestionResponseItem
|
||||
from mealie.schema.recipe.recipe_tool import RecipeToolOut
|
||||
@@ -214,7 +214,7 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
|
||||
) -> RecipePagination:
|
||||
# Copy this, because calling methods (e.g. tests) might rely on it not getting mutated
|
||||
pagination_result = pagination.model_copy()
|
||||
q = sa.select(self.model)
|
||||
q = sa.select(self.model).filter(self.model.household_id.is_not(None))
|
||||
|
||||
fltr = self._filter_builder()
|
||||
q = q.filter_by(**fltr)
|
||||
@@ -271,24 +271,6 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
|
||||
items=items,
|
||||
)
|
||||
|
||||
def get_by_categories(self, categories: list[RecipeCategory]) -> list[RecipeSummary]:
|
||||
"""
|
||||
get_by_categories returns all the Recipes that contain every category provided in the list
|
||||
"""
|
||||
|
||||
ids = [x.id for x in categories]
|
||||
stmt = (
|
||||
sa.select(RecipeModel)
|
||||
.join(RecipeModel.recipe_category)
|
||||
.filter(RecipeModel.recipe_category.any(Category.id.in_(ids)))
|
||||
)
|
||||
if self.group_id:
|
||||
stmt = stmt.filter(RecipeModel.group_id == self.group_id)
|
||||
if self.household_id:
|
||||
stmt = stmt.filter(RecipeModel.household_id == self.household_id)
|
||||
|
||||
return [RecipeSummary.model_validate(x) for x in self.session.execute(stmt).unique().scalars().all()]
|
||||
|
||||
def _build_recipe_filter(
|
||||
self,
|
||||
categories: list[UUID4] | None = None,
|
||||
@@ -334,7 +316,9 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
|
||||
return fltr
|
||||
|
||||
def get_random(self, limit=1) -> list[Recipe]:
|
||||
stmt = sa.select(RecipeModel).order_by(sa.func.random()).limit(limit) # Postgres and SQLite specific
|
||||
stmt = (
|
||||
sa.select(RecipeModel).filter(RecipeModel.household_id.is_not(None)).order_by(sa.func.random()).limit(limit)
|
||||
) # Postgres and SQLite specific
|
||||
if self.group_id:
|
||||
stmt = stmt.filter(RecipeModel.group_id == self.group_id)
|
||||
if self.household_id:
|
||||
@@ -405,7 +389,7 @@ class RepositoryRecipes(HouseholdRepositoryGeneric[Recipe, RecipeModel]):
|
||||
ingredients_alias = orm.aliased(RecipeIngredientModel)
|
||||
tools_alias = orm.aliased(Tool)
|
||||
|
||||
q = sa.select(self.model)
|
||||
q = sa.select(self.model).filter(self.model.household_id.is_not(None))
|
||||
fltr = self._filter_builder()
|
||||
q = q.filter_by(**fltr)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from functools import cached_property
|
||||
from fastapi import APIRouter, Depends
|
||||
from pydantic import UUID4, BaseModel, ConfigDict
|
||||
|
||||
from mealie.repos.all_repositories import get_repositories
|
||||
from mealie.routes._base import BaseCrudController, controller
|
||||
from mealie.routes._base.mixins import HttpRepo
|
||||
from mealie.schema import mapper
|
||||
@@ -123,9 +124,15 @@ class RecipeCategoryController(BaseCrudController):
|
||||
def get_one_by_slug(self, category_slug: str):
|
||||
"""Returns a category object with the associated recieps relating to the category"""
|
||||
category: RecipeCategory = self.mixins.get_one(category_slug, "slug")
|
||||
|
||||
group_recipes = get_repositories(self.repos.session, group_id=self.group_id, household_id=None).recipes
|
||||
recipe_data = group_recipes.page_all(
|
||||
PaginationQuery(per_page=-1, query_filter=f'recipe_category.id IN ["{category.id}"]')
|
||||
)
|
||||
|
||||
return RecipeCategoryResponse.model_construct(
|
||||
id=category.id,
|
||||
slug=category.slug,
|
||||
name=category.name,
|
||||
recipes=self.repos.recipes.get_by_categories([category]),
|
||||
recipes=recipe_data.items,
|
||||
)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from typing import cast
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
@@ -7,11 +6,10 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from mealie.repos.all_repositories import get_repositories
|
||||
from mealie.repos.repository_factory import AllRepositories
|
||||
from mealie.repos.repository_recipes import RepositoryRecipes
|
||||
from mealie.schema.household.household import HouseholdCreate, HouseholdRecipeCreate
|
||||
from mealie.schema.recipe import RecipeIngredient, SaveIngredientFood
|
||||
from mealie.schema.recipe.recipe import Recipe, RecipeCategory, RecipeSummary
|
||||
from mealie.schema.recipe.recipe_category import CategoryOut, CategorySave, TagSave
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.recipe.recipe_category import CategorySave, TagSave
|
||||
from mealie.schema.recipe.recipe_tool import RecipeToolSave
|
||||
from mealie.schema.response import OrderDirection, PaginationQuery
|
||||
from mealie.schema.user.user import GroupBase, UserRatingCreate
|
||||
@@ -137,120 +135,6 @@ def search_recipes(unique_db: AllRepositories, unique_ids: tuple[str, str, str])
|
||||
return unique_db.recipes.create_many(recipes)
|
||||
|
||||
|
||||
def test_recipe_repo_get_by_categories_basic(unique_user: TestUser):
|
||||
database = unique_user.repos
|
||||
|
||||
# Bootstrap the database with categories
|
||||
slug1, slug2, slug3 = (random_string(10) for _ in range(3))
|
||||
|
||||
categories: list[CategoryOut | CategorySave] = [
|
||||
CategorySave(group_id=unique_user.group_id, name=slug1, slug=slug1),
|
||||
CategorySave(group_id=unique_user.group_id, name=slug2, slug=slug2),
|
||||
CategorySave(group_id=unique_user.group_id, name=slug3, slug=slug3),
|
||||
]
|
||||
|
||||
created_categories: list[CategoryOut] = []
|
||||
|
||||
for category in categories:
|
||||
model = database.categories.create(category)
|
||||
created_categories.append(model)
|
||||
|
||||
# Bootstrap the database with recipes
|
||||
recipes: list[Recipe | RecipeSummary] = []
|
||||
|
||||
for idx in range(15):
|
||||
if idx % 3 == 0:
|
||||
category = created_categories[0]
|
||||
elif idx % 3 == 1:
|
||||
category = created_categories[1]
|
||||
else:
|
||||
category = created_categories[2]
|
||||
|
||||
recipes.append(
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=random_string(),
|
||||
recipe_category=[category],
|
||||
),
|
||||
)
|
||||
|
||||
created_recipes = []
|
||||
|
||||
for recipe in recipes:
|
||||
models = database.recipes.create(cast(Recipe, recipe))
|
||||
created_recipes.append(models)
|
||||
|
||||
# Get all recipes by category
|
||||
|
||||
for category in created_categories:
|
||||
repo: RepositoryRecipes = database.recipes
|
||||
recipes = repo.get_by_categories([cast(RecipeCategory, category)])
|
||||
|
||||
assert len(recipes) == 5
|
||||
|
||||
for recipe in recipes:
|
||||
assert recipe.recipe_category is not None
|
||||
found_cat = recipe.recipe_category[0]
|
||||
|
||||
assert found_cat.name == category.name
|
||||
assert found_cat.slug == category.slug
|
||||
assert found_cat.id == category.id
|
||||
|
||||
|
||||
def test_recipe_repo_get_by_categories_multi(unique_user: TestUser):
|
||||
database = unique_user.repos
|
||||
slug1, slug2 = (random_string(10) for _ in range(2))
|
||||
|
||||
categories = [
|
||||
CategorySave(group_id=unique_user.group_id, name=slug1, slug=slug1),
|
||||
CategorySave(group_id=unique_user.group_id, name=slug2, slug=slug2),
|
||||
]
|
||||
|
||||
created_categories = []
|
||||
known_category_ids = []
|
||||
|
||||
for category in categories:
|
||||
model = database.categories.create(category)
|
||||
created_categories.append(model)
|
||||
known_category_ids.append(model.id)
|
||||
|
||||
# Bootstrap the database with recipes
|
||||
recipes = []
|
||||
|
||||
for _ in range(10):
|
||||
recipes.append(
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=random_string(),
|
||||
recipe_category=created_categories,
|
||||
),
|
||||
)
|
||||
|
||||
# Insert Non-Category Recipes
|
||||
recipes.append(
|
||||
Recipe(
|
||||
user_id=unique_user.user_id,
|
||||
group_id=unique_user.group_id,
|
||||
name=random_string(),
|
||||
)
|
||||
)
|
||||
|
||||
for recipe in recipes:
|
||||
database.recipes.create(recipe)
|
||||
|
||||
# Get all recipes by both categories
|
||||
repo: RepositoryRecipes = database.recipes
|
||||
by_category = repo.get_by_categories(cast(list[RecipeCategory], created_categories))
|
||||
|
||||
assert len(by_category) == 10
|
||||
for recipe_summary in by_category:
|
||||
assert recipe_summary.recipe_category is not None
|
||||
for recipe_category in recipe_summary.recipe_category:
|
||||
assert recipe_category.id in known_category_ids
|
||||
|
||||
|
||||
def test_recipe_repo_pagination_by_categories(unique_user: TestUser):
|
||||
database = unique_user.repos
|
||||
slug1, slug2 = (random_string(10) for _ in range(2))
|
||||
|
||||
Reference in New Issue
Block a user