mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-24 07:39:41 +02:00
feat: Query Filter Builder for Cookbooks and Meal Plans (#4346)
This commit is contained in:
parent
2a9a6fa5e6
commit
b8e62ab8dd
47 changed files with 2043 additions and 440 deletions
|
@ -1,16 +1,17 @@
|
|||
from typing import Annotated
|
||||
|
||||
from pydantic import UUID4, ConfigDict, Field, field_validator
|
||||
import sqlalchemy as sa
|
||||
from pydantic import UUID4, ConfigDict, Field, ValidationInfo, field_validator
|
||||
from slugify import slugify
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.core.root_logger import get_logger
|
||||
from mealie.db.models.recipe import RecipeModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.recipe.recipe import RecipeSummary, RecipeTool
|
||||
from mealie.schema.recipe.recipe import RecipeSummary
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
from mealie.schema.response.query_filter import QueryFilterBuilder, QueryFilterJSON
|
||||
|
||||
from ...db.models.household import CookBook
|
||||
from ..recipe.recipe_category import CategoryBase, TagBase
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
class CreateCookBook(MealieModel):
|
||||
|
@ -19,12 +20,7 @@ class CreateCookBook(MealieModel):
|
|||
slug: Annotated[str | None, Field(validate_default=True)] = None
|
||||
position: int = 1
|
||||
public: Annotated[bool, Field(validate_default=True)] = False
|
||||
categories: list[CategoryBase] = []
|
||||
tags: list[TagBase] = []
|
||||
tools: list[RecipeTool] = []
|
||||
require_all_categories: bool = True
|
||||
require_all_tags: bool = True
|
||||
require_all_tools: bool = True
|
||||
query_filter_string: str = ""
|
||||
|
||||
@field_validator("public", mode="before")
|
||||
def validate_public(public: bool | None) -> bool:
|
||||
|
@ -42,6 +38,19 @@ class CreateCookBook(MealieModel):
|
|||
|
||||
return name
|
||||
|
||||
@field_validator("query_filter_string")
|
||||
def validate_query_filter_string(value: str) -> str:
|
||||
# The query filter builder does additional validations while building the
|
||||
# database query, so we make sure constructing the query is successful
|
||||
builder = QueryFilterBuilder(value)
|
||||
|
||||
try:
|
||||
builder.filter_query(sa.select(RecipeModel), RecipeModel)
|
||||
except Exception as e:
|
||||
raise ValueError("Invalid query filter string") from e
|
||||
|
||||
return value
|
||||
|
||||
|
||||
class SaveCookBook(CreateCookBook):
|
||||
group_id: UUID4
|
||||
|
@ -53,14 +62,24 @@ class UpdateCookBook(SaveCookBook):
|
|||
|
||||
|
||||
class ReadCookBook(UpdateCookBook):
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
categories: list[CategoryBase] = []
|
||||
query_filter: Annotated[QueryFilterJSON, Field(validate_default=True)] = None # type: ignore
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@classmethod
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [joinedload(CookBook.categories), joinedload(CookBook.tags), joinedload(CookBook.tools)]
|
||||
@field_validator("query_filter_string")
|
||||
def validate_query_filter_string(value: str) -> str:
|
||||
# Skip validation since we are not updating the query filter string
|
||||
return value
|
||||
|
||||
@field_validator("query_filter", mode="before")
|
||||
def validate_query_filter(cls, _, info: ValidationInfo) -> QueryFilterJSON:
|
||||
try:
|
||||
query_filter_string: str = info.data.get("query_filter_string") or ""
|
||||
builder = QueryFilterBuilder(query_filter_string)
|
||||
return builder.as_json_model()
|
||||
except Exception:
|
||||
logger.exception(f"Invalid query filter string: {query_filter_string}")
|
||||
return QueryFilterJSON()
|
||||
|
||||
|
||||
class CookBookPagination(PaginationBase):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue