mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-11 02:13:12 -05:00
@@ -1,11 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from numbers import Number
|
||||
from pathlib import Path
|
||||
from typing import Any, ClassVar
|
||||
from typing import Annotated, Any, ClassVar
|
||||
from uuid import uuid4
|
||||
|
||||
from pydantic import UUID4, BaseModel, Field, validator
|
||||
from pydantic import UUID4, BaseModel, ConfigDict, Field, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
from slugify import slugify
|
||||
from sqlalchemy import Select, desc, func, or_, select, text
|
||||
from sqlalchemy.orm import Session, joinedload, selectinload
|
||||
@@ -22,7 +24,6 @@ from ...db.models.recipe import (
|
||||
RecipeInstruction,
|
||||
RecipeModel,
|
||||
)
|
||||
from ..getter_dict import ExtrasGetterDict
|
||||
from .recipe_asset import RecipeAsset
|
||||
from .recipe_comments import RecipeCommentOut
|
||||
from .recipe_notes import RecipeNote
|
||||
@@ -39,9 +40,7 @@ class RecipeTag(MealieModel):
|
||||
slug: str
|
||||
|
||||
_searchable_properties: ClassVar[list[str]] = ["name"]
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class RecipeTagPagination(PaginationBase):
|
||||
@@ -80,16 +79,16 @@ class CreateRecipe(MealieModel):
|
||||
|
||||
|
||||
class RecipeSummary(MealieModel):
|
||||
id: UUID4 | None
|
||||
id: UUID4 | None = None
|
||||
_normalize_search: ClassVar[bool] = True
|
||||
|
||||
user_id: UUID4 = Field(default_factory=uuid4)
|
||||
group_id: UUID4 = Field(default_factory=uuid4)
|
||||
user_id: UUID4 = Field(default_factory=uuid4, validate_default=True)
|
||||
group_id: UUID4 = Field(default_factory=uuid4, validate_default=True)
|
||||
|
||||
name: str | None
|
||||
slug: str = ""
|
||||
image: Any | None
|
||||
recipe_yield: str | None
|
||||
name: str | None = None
|
||||
slug: Annotated[str, Field(validate_default=True)] = ""
|
||||
image: Any | None = None
|
||||
recipe_yield: str | None = None
|
||||
|
||||
total_time: str | None = None
|
||||
prep_time: str | None = None
|
||||
@@ -97,21 +96,28 @@ class RecipeSummary(MealieModel):
|
||||
perform_time: str | None = None
|
||||
|
||||
description: str | None = ""
|
||||
recipe_category: list[RecipeCategory] | None = []
|
||||
tags: list[RecipeTag] | None = []
|
||||
recipe_category: Annotated[list[RecipeCategory] | None, Field(validate_default=True)] | None = []
|
||||
tags: Annotated[list[RecipeTag] | None, Field(validate_default=True)] = []
|
||||
tools: list[RecipeTool] = []
|
||||
rating: int | None
|
||||
rating: int | None = None
|
||||
org_url: str | None = Field(None, alias="orgURL")
|
||||
|
||||
date_added: datetime.date | None
|
||||
date_updated: datetime.datetime | None
|
||||
date_added: datetime.date | None = None
|
||||
date_updated: datetime.datetime | None = None
|
||||
|
||||
created_at: datetime.datetime | None
|
||||
update_at: datetime.datetime | None
|
||||
last_made: datetime.datetime | None
|
||||
created_at: datetime.datetime | None = None
|
||||
update_at: datetime.datetime | None = None
|
||||
last_made: datetime.datetime | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
@field_validator("recipe_yield", "total_time", "prep_time", "cook_time", "perform_time", mode="before")
|
||||
def clean_strings(val: Any):
|
||||
if val is None:
|
||||
return val
|
||||
if isinstance(val, Number):
|
||||
return str(val)
|
||||
|
||||
return val
|
||||
|
||||
|
||||
class RecipePagination(PaginationBase):
|
||||
@@ -119,9 +125,9 @@ class RecipePagination(PaginationBase):
|
||||
|
||||
|
||||
class Recipe(RecipeSummary):
|
||||
recipe_ingredient: list[RecipeIngredient] = []
|
||||
recipe_ingredient: Annotated[list[RecipeIngredient], Field(validate_default=True)] = []
|
||||
recipe_instructions: list[RecipeStep] | None = []
|
||||
nutrition: Nutrition | None
|
||||
nutrition: Nutrition | None = None
|
||||
|
||||
# Mealie Specific
|
||||
settings: RecipeSettings | None = None
|
||||
@@ -175,13 +181,11 @@ class Recipe(RecipeSummary):
|
||||
|
||||
return self.image_dir_from_id(self.id)
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
getter_dict = ExtrasGetterDict
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@classmethod
|
||||
def from_orm(cls, obj):
|
||||
recipe = super().from_orm(obj)
|
||||
def model_validate(cls, obj):
|
||||
recipe = super().model_validate(obj)
|
||||
recipe.__post_init__()
|
||||
return recipe
|
||||
|
||||
@@ -198,15 +202,15 @@ class Recipe(RecipeSummary):
|
||||
ingredient.is_food = not ingredient.disable_amount
|
||||
ingredient.display = ingredient._format_display()
|
||||
|
||||
@validator("slug", always=True, pre=True, allow_reuse=True)
|
||||
def validate_slug(slug: str, values): # type: ignore
|
||||
if not values.get("name"):
|
||||
@field_validator("slug", mode="before")
|
||||
def validate_slug(slug: str, info: ValidationInfo):
|
||||
if not info.data.get("name"):
|
||||
return slug
|
||||
|
||||
return slugify(values["name"])
|
||||
return slugify(info.data["name"])
|
||||
|
||||
@validator("recipe_ingredient", always=True, pre=True, allow_reuse=True)
|
||||
def validate_ingredients(recipe_ingredient, values):
|
||||
@field_validator("recipe_ingredient", mode="before")
|
||||
def validate_ingredients(recipe_ingredient):
|
||||
if not recipe_ingredient or not isinstance(recipe_ingredient, list):
|
||||
return recipe_ingredient
|
||||
|
||||
@@ -215,30 +219,37 @@ class Recipe(RecipeSummary):
|
||||
|
||||
return recipe_ingredient
|
||||
|
||||
@validator("tags", always=True, pre=True, allow_reuse=True)
|
||||
def validate_tags(cats: list[Any]): # type: ignore
|
||||
@field_validator("tags", mode="before")
|
||||
def validate_tags(cats: list[Any]):
|
||||
if isinstance(cats, list) and cats and isinstance(cats[0], str):
|
||||
return [RecipeTag(id=uuid4(), name=c, slug=slugify(c)) for c in cats]
|
||||
return cats
|
||||
|
||||
@validator("recipe_category", always=True, pre=True, allow_reuse=True)
|
||||
def validate_categories(cats: list[Any]): # type: ignore
|
||||
@field_validator("recipe_category", mode="before")
|
||||
def validate_categories(cats: list[Any]):
|
||||
if isinstance(cats, list) and cats and isinstance(cats[0], str):
|
||||
return [RecipeCategory(id=uuid4(), name=c, slug=slugify(c)) for c in cats]
|
||||
return cats
|
||||
|
||||
@validator("group_id", always=True, pre=True, allow_reuse=True)
|
||||
@field_validator("group_id", mode="before")
|
||||
def validate_group_id(group_id: Any):
|
||||
if isinstance(group_id, int):
|
||||
return uuid4()
|
||||
return group_id
|
||||
|
||||
@validator("user_id", always=True, pre=True, allow_reuse=True)
|
||||
@field_validator("user_id", mode="before")
|
||||
def validate_user_id(user_id: Any):
|
||||
if isinstance(user_id, int):
|
||||
return uuid4()
|
||||
return user_id
|
||||
|
||||
@field_validator("extras", mode="before")
|
||||
def convert_extras_to_dict(cls, v):
|
||||
if isinstance(v, dict):
|
||||
return v
|
||||
|
||||
return {x.key_name: x.value for x in v} if v else {}
|
||||
|
||||
@classmethod
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [
|
||||
@@ -332,5 +343,5 @@ class RecipeLastMade(BaseModel):
|
||||
|
||||
from mealie.schema.recipe.recipe_ingredient import RecipeIngredient # noqa: E402
|
||||
|
||||
RecipeSummary.update_forward_refs()
|
||||
Recipe.update_forward_refs()
|
||||
RecipeSummary.model_rebuild()
|
||||
Recipe.model_rebuild()
|
||||
|
||||
Reference in New Issue
Block a user