2025-10-19 09:24:23 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
|
from typing import ClassVar, List, Optional
|
|
|
|
|
|
2025-11-01 08:58:33 +00:00
|
|
|
from pydantic import Field, model_validator
|
2025-10-19 09:24:23 +00:00
|
|
|
|
|
|
|
|
from common import ApiModel
|
|
|
|
|
from ingredients import Ingredient
|
2025-11-01 08:58:33 +00:00
|
|
|
from api.dtos import MemberRef
|
2025-10-19 09:24:23 +00:00
|
|
|
from recipes import Recipe
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MealRecipe(ApiModel):
|
|
|
|
|
meal_id: int
|
|
|
|
|
recipe_id: int
|
|
|
|
|
servings: float
|
|
|
|
|
|
|
|
|
|
recipe: Optional[Recipe] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Meal(ApiModel):
|
|
|
|
|
KEYS: ClassVar[List[str]] = ["id", "suggested_date", "consumed_date", "purchase_date"]
|
|
|
|
|
id: int = -1
|
|
|
|
|
suggested_date: datetime.datetime
|
|
|
|
|
consumed_date: Optional[datetime.datetime] = None
|
|
|
|
|
|
2025-11-01 08:58:33 +00:00
|
|
|
chefs: List[MemberRef] = Field(default_factory=list)
|
|
|
|
|
cleanup: List[MemberRef] = Field(default_factory=list)
|
|
|
|
|
consumers: List[MemberRef] = Field(default_factory=list)
|
2025-10-19 09:24:23 +00:00
|
|
|
recipes: List[MealRecipe] = Field(default_factory=list)
|
|
|
|
|
extra_ingredients: List[Ingredient] = Field(default_factory=list)
|
|
|
|
|
|
|
|
|
|
# Set from shopping list
|
|
|
|
|
purchase_date: Optional[datetime.datetime] = None
|
2025-11-01 08:58:33 +00:00
|
|
|
|
|
|
|
|
@model_validator(mode="before")
|
|
|
|
|
@classmethod
|
|
|
|
|
def _coerce_members(cls, data: dict) -> dict:
|
|
|
|
|
# Accept legacy Person objects in tests by coercing to MemberRef
|
|
|
|
|
def to_member_ref(x):
|
|
|
|
|
# Already a mapping suitable for MemberRef or an instance with id/display_name
|
|
|
|
|
if isinstance(x, dict):
|
|
|
|
|
if "id" in x and ("display_name" in x or "displayName" in x or "name" in x):
|
|
|
|
|
# normalize display_name key
|
|
|
|
|
if "display_name" not in x:
|
|
|
|
|
dn = x.get("displayName") or x.get("name")
|
|
|
|
|
x = {**x, "display_name": dn}
|
|
|
|
|
return x
|
|
|
|
|
return x
|
|
|
|
|
# Object with attributes
|
|
|
|
|
pid = getattr(x, "id", None)
|
|
|
|
|
dname = getattr(x, "display_name", None) or getattr(x, "name", None)
|
|
|
|
|
if isinstance(pid, int) and dname:
|
|
|
|
|
return {"id": pid, "display_name": dname}
|
|
|
|
|
return x
|
|
|
|
|
|
|
|
|
|
if isinstance(data, dict):
|
|
|
|
|
for key in ("chefs", "cleanup", "consumers"):
|
|
|
|
|
val = data.get(key)
|
|
|
|
|
if isinstance(val, list):
|
|
|
|
|
data[key] = [to_member_ref(v) for v in val]
|
|
|
|
|
return data
|