feat: Add Households to Mealie (#3970)

This commit is contained in:
Michael Genson
2024-08-22 10:14:32 -05:00
committed by GitHub
parent 0c29cef17d
commit eb170cc7e5
315 changed files with 6975 additions and 3577 deletions

View File

@@ -1,158 +1,19 @@
# This file is auto-generated by gen_schema_exports.py
from .group import GroupAdminUpdate
from .group_events import (
GroupEventNotifierCreate,
GroupEventNotifierOptions,
GroupEventNotifierOptionsOut,
GroupEventNotifierOptionsSave,
GroupEventNotifierOut,
GroupEventNotifierPrivate,
GroupEventNotifierSave,
GroupEventNotifierUpdate,
GroupEventPagination,
)
from .group_exports import GroupDataExport
from .group_migration import DataMigrationCreate, SupportedMigrations
from .group_permissions import SetPermissions
from .group_preferences import (
CreateGroupPreferences,
ReadGroupPreferences,
UpdateGroupPreferences,
)
from .group_recipe_action import (
CreateGroupRecipeAction,
GroupRecipeActionOut,
GroupRecipeActionPagination,
GroupRecipeActionType,
SaveGroupRecipeAction,
)
from .group_preferences import CreateGroupPreferences, ReadGroupPreferences, UpdateGroupPreferences
from .group_seeder import SeederConfig
from .group_shopping_list import (
ShoppingListAddRecipeParams,
ShoppingListCreate,
ShoppingListItemBase,
ShoppingListItemCreate,
ShoppingListItemOut,
ShoppingListItemPagination,
ShoppingListItemRecipeRefCreate,
ShoppingListItemRecipeRefOut,
ShoppingListItemRecipeRefUpdate,
ShoppingListItemsCollectionOut,
ShoppingListItemUpdate,
ShoppingListItemUpdateBulk,
ShoppingListMultiPurposeLabelCreate,
ShoppingListMultiPurposeLabelOut,
ShoppingListMultiPurposeLabelUpdate,
ShoppingListOut,
ShoppingListPagination,
ShoppingListRecipeRefOut,
ShoppingListRemoveRecipeParams,
ShoppingListSave,
ShoppingListSummary,
ShoppingListUpdate,
)
from .group_statistics import GroupStatistics, GroupStorage
from .invite_token import (
CreateInviteToken,
EmailInitationResponse,
EmailInvitation,
ReadInviteToken,
SaveInviteToken,
)
from .webhook import (
CreateWebhook,
ReadWebhook,
SaveWebhook,
WebhookPagination,
WebhookType,
)
from .group_statistics import GroupStorage
__all__ = [
"GroupEventNotifierCreate",
"GroupEventNotifierOptions",
"GroupEventNotifierOptionsOut",
"GroupEventNotifierOptionsSave",
"GroupEventNotifierOut",
"GroupEventNotifierPrivate",
"GroupEventNotifierSave",
"GroupEventNotifierUpdate",
"GroupEventPagination",
"CreateGroupRecipeAction",
"GroupRecipeActionOut",
"GroupRecipeActionPagination",
"GroupRecipeActionType",
"SaveGroupRecipeAction",
"CreateWebhook",
"ReadWebhook",
"SaveWebhook",
"WebhookPagination",
"WebhookType",
"GroupDataExport",
"CreateGroupPreferences",
"ReadGroupPreferences",
"UpdateGroupPreferences",
"GroupStatistics",
"GroupStorage",
"DataMigrationCreate",
"SupportedMigrations",
"SeederConfig",
"CreateInviteToken",
"EmailInitationResponse",
"EmailInvitation",
"ReadInviteToken",
"SaveInviteToken",
"SetPermissions",
"ShoppingListAddRecipeParams",
"ShoppingListCreate",
"ShoppingListItemBase",
"ShoppingListItemCreate",
"ShoppingListItemOut",
"ShoppingListItemPagination",
"ShoppingListItemRecipeRefCreate",
"ShoppingListItemRecipeRefOut",
"ShoppingListItemRecipeRefUpdate",
"ShoppingListItemUpdate",
"ShoppingListItemUpdateBulk",
"ShoppingListItemsCollectionOut",
"ShoppingListMultiPurposeLabelCreate",
"ShoppingListMultiPurposeLabelOut",
"ShoppingListMultiPurposeLabelUpdate",
"ShoppingListOut",
"ShoppingListPagination",
"ShoppingListRecipeRefOut",
"ShoppingListRemoveRecipeParams",
"ShoppingListSave",
"ShoppingListSummary",
"ShoppingListUpdate",
"GroupAdminUpdate",
"CreateWebhook",
"ReadWebhook",
"SaveWebhook",
"WebhookPagination",
"WebhookType",
"GroupAdminUpdate",
"CreateGroupPreferences",
"ReadGroupPreferences",
"UpdateGroupPreferences",
"SetPermissions",
"DataMigrationCreate",
"SupportedMigrations",
"SeederConfig",
"GroupDataExport",
"CreateInviteToken",
"EmailInitationResponse",
"EmailInvitation",
"ReadInviteToken",
"SaveInviteToken",
"GroupStatistics",
"GroupStorage",
"GroupEventNotifierCreate",
"GroupEventNotifierOptions",
"GroupEventNotifierOptionsOut",
"GroupEventNotifierOptionsSave",
"GroupEventNotifierOut",
"GroupEventNotifierPrivate",
"GroupEventNotifierSave",
"GroupEventNotifierUpdate",
"GroupEventPagination",
]

View File

@@ -1,99 +0,0 @@
from pydantic import UUID4, ConfigDict
from sqlalchemy.orm import joinedload
from sqlalchemy.orm.interfaces import LoaderOption
from mealie.db.models.group import GroupEventNotifierModel
from mealie.schema._mealie import MealieModel
from mealie.schema.response.pagination import PaginationBase
# =============================================================================
# Group Events Notifier Options
class GroupEventNotifierOptions(MealieModel):
"""
These events are in-sync with the EventTypes found in the EventBusService.
If you modify this, make sure to update the EventBusService as well.
"""
test_message: bool = False
webhook_task: bool = False
recipe_created: bool = False
recipe_updated: bool = False
recipe_deleted: bool = False
user_signup: bool = False
data_migrations: bool = False
data_export: bool = False
data_import: bool = False
mealplan_entry_created: bool = False
shopping_list_created: bool = False
shopping_list_updated: bool = False
shopping_list_deleted: bool = False
cookbook_created: bool = False
cookbook_updated: bool = False
cookbook_deleted: bool = False
tag_created: bool = False
tag_updated: bool = False
tag_deleted: bool = False
category_created: bool = False
category_updated: bool = False
category_deleted: bool = False
class GroupEventNotifierOptionsSave(GroupEventNotifierOptions):
notifier_id: UUID4
class GroupEventNotifierOptionsOut(GroupEventNotifierOptions):
id: UUID4
model_config = ConfigDict(from_attributes=True)
# =======================================================================
# Notifiers
class GroupEventNotifierCreate(MealieModel):
name: str
apprise_url: str | None = None
class GroupEventNotifierSave(GroupEventNotifierCreate):
enabled: bool = True
group_id: UUID4
options: GroupEventNotifierOptions = GroupEventNotifierOptions()
class GroupEventNotifierUpdate(GroupEventNotifierSave):
id: UUID4
apprise_url: str | None = None
class GroupEventNotifierOut(MealieModel):
id: UUID4
name: str
enabled: bool
group_id: UUID4
options: GroupEventNotifierOptionsOut
model_config = ConfigDict(from_attributes=True)
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [joinedload(GroupEventNotifierModel.options)]
class GroupEventPagination(PaginationBase):
items: list[GroupEventNotifierOut]
class GroupEventNotifierPrivate(GroupEventNotifierOut):
apprise_url: str
model_config = ConfigDict(from_attributes=True)

View File

@@ -1,10 +0,0 @@
from pydantic import UUID4
from mealie.schema._mealie import MealieModel
class SetPermissions(MealieModel):
user_id: UUID4
can_manage: bool = False
can_invite: bool = False
can_organize: bool = False

View File

@@ -7,15 +7,6 @@ from mealie.schema._mealie import MealieModel
class UpdateGroupPreferences(MealieModel):
private_group: bool = True
first_day_of_week: int = 0
# Recipe Defaults
recipe_public: bool = True
recipe_show_nutrition: bool = False
recipe_show_assets: bool = False
recipe_landscape_view: bool = False
recipe_disable_comments: bool = False
recipe_disable_amount: bool = True
class CreateGroupPreferences(UpdateGroupPreferences):

View File

@@ -1,32 +0,0 @@
from enum import Enum
from pydantic import UUID4, ConfigDict
from mealie.schema._mealie import MealieModel
from mealie.schema.response.pagination import PaginationBase
class GroupRecipeActionType(Enum):
link = "link"
post = "post"
class CreateGroupRecipeAction(MealieModel):
action_type: GroupRecipeActionType
title: str
url: str
model_config = ConfigDict(use_enum_values=True)
class SaveGroupRecipeAction(CreateGroupRecipeAction):
group_id: UUID4
class GroupRecipeActionOut(SaveGroupRecipeAction):
id: UUID4
model_config = ConfigDict(from_attributes=True)
class GroupRecipeActionPagination(PaginationBase):
items: list[GroupRecipeActionOut]

View File

@@ -1,285 +0,0 @@
from __future__ import annotations
from datetime import datetime
from uuid import UUID
from pydantic import UUID4, ConfigDict, field_validator, model_validator
from sqlalchemy.orm import joinedload, selectinload
from sqlalchemy.orm.interfaces import LoaderOption
from mealie.db.models.group import (
ShoppingList,
ShoppingListItem,
ShoppingListMultiPurposeLabel,
ShoppingListRecipeReference,
)
from mealie.db.models.recipe import IngredientFoodModel, RecipeModel
from mealie.schema._mealie import MealieModel
from mealie.schema._mealie.types import NoneFloat
from mealie.schema.labels.multi_purpose_label import MultiPurposeLabelSummary
from mealie.schema.recipe.recipe import RecipeSummary
from mealie.schema.recipe.recipe_ingredient import (
IngredientFood,
IngredientUnit,
RecipeIngredient,
RecipeIngredientBase,
)
from mealie.schema.response.pagination import PaginationBase
class ShoppingListItemRecipeRefCreate(MealieModel):
recipe_id: UUID4
recipe_quantity: float = 0
"""the quantity of this item in a single recipe (scale == 1)"""
recipe_scale: NoneFloat = 1
"""the number of times this recipe has been added"""
recipe_note: str | None = None
"""the original note from the recipe"""
@field_validator("recipe_quantity", mode="before")
@classmethod
def default_none_to_zero(cls, v):
return 0 if v is None else v
class ShoppingListItemRecipeRefUpdate(ShoppingListItemRecipeRefCreate):
id: UUID4
shopping_list_item_id: UUID4
class ShoppingListItemRecipeRefOut(ShoppingListItemRecipeRefUpdate):
model_config = ConfigDict(from_attributes=True)
class ShoppingListItemBase(RecipeIngredientBase):
shopping_list_id: UUID4
checked: bool = False
position: int = 0
quantity: float = 1
food_id: UUID4 | None = None
label_id: UUID4 | None = None
unit_id: UUID4 | None = None
is_food: bool = False
extras: dict | None = {}
@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 {}
class ShoppingListItemCreate(ShoppingListItemBase):
id: UUID4 | None = None
"""The unique id of the item to create. If not supplied, one will be generated."""
recipe_references: list[ShoppingListItemRecipeRefCreate] = []
@field_validator("id", mode="before")
def validate_id(cls, v):
v = v or None
if not v or isinstance(v, UUID):
return v
try:
return UUID(v)
except Exception:
return None
class ShoppingListItemUpdate(ShoppingListItemBase):
recipe_references: list[ShoppingListItemRecipeRefCreate | ShoppingListItemRecipeRefUpdate] = []
class ShoppingListItemUpdateBulk(ShoppingListItemUpdate):
"""Only used for bulk update operations where the shopping list item id isn't already supplied"""
id: UUID4
class ShoppingListItemOut(ShoppingListItemBase):
id: UUID4
food: IngredientFood | None = None
label: MultiPurposeLabelSummary | None = None
unit: IngredientUnit | None = None
recipe_references: list[ShoppingListItemRecipeRefOut] = []
created_at: datetime | None = None
update_at: datetime | None = None
@model_validator(mode="after")
def populate_missing_label(self):
# if we're missing a label, but the food has a label, use that as the label
if (not self.label) and (self.food and self.food.label):
self.label = self.food.label
self.label_id = self.label.id
return self
model_config = ConfigDict(from_attributes=True)
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [
selectinload(ShoppingListItem.extras),
selectinload(ShoppingListItem.food).joinedload(IngredientFoodModel.extras),
selectinload(ShoppingListItem.food).joinedload(IngredientFoodModel.label),
joinedload(ShoppingListItem.label),
joinedload(ShoppingListItem.unit),
selectinload(ShoppingListItem.recipe_references),
]
class ShoppingListItemsCollectionOut(MealieModel):
"""Container for bulk shopping list item changes"""
created_items: list[ShoppingListItemOut] = []
updated_items: list[ShoppingListItemOut] = []
deleted_items: list[ShoppingListItemOut] = []
class ShoppingListMultiPurposeLabelCreate(MealieModel):
shopping_list_id: UUID4
label_id: UUID4
position: int = 0
class ShoppingListMultiPurposeLabelUpdate(ShoppingListMultiPurposeLabelCreate):
id: UUID4
class ShoppingListMultiPurposeLabelOut(ShoppingListMultiPurposeLabelUpdate):
label: MultiPurposeLabelSummary
model_config = ConfigDict(from_attributes=True)
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [joinedload(ShoppingListMultiPurposeLabel.label)]
class ShoppingListItemPagination(PaginationBase):
items: list[ShoppingListItemOut]
class ShoppingListCreate(MealieModel):
name: str | None = None
extras: dict | None = {}
created_at: datetime | None = None
update_at: datetime | None = None
@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 {}
class ShoppingListRecipeRefOut(MealieModel):
id: UUID4
shopping_list_id: UUID4
recipe_id: UUID4
recipe_quantity: float
"""the number of times this recipe has been added"""
recipe: RecipeSummary
model_config = ConfigDict(from_attributes=True)
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [
selectinload(ShoppingListRecipeReference.recipe).joinedload(RecipeModel.recipe_category),
selectinload(ShoppingListRecipeReference.recipe).joinedload(RecipeModel.tags),
selectinload(ShoppingListRecipeReference.recipe).joinedload(RecipeModel.tools),
]
class ShoppingListSave(ShoppingListCreate):
group_id: UUID4
user_id: UUID4
class ShoppingListSummary(ShoppingListSave):
id: UUID4
recipe_references: list[ShoppingListRecipeRefOut]
label_settings: list[ShoppingListMultiPurposeLabelOut]
model_config = ConfigDict(from_attributes=True)
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [
selectinload(ShoppingList.extras),
selectinload(ShoppingList.recipe_references)
.joinedload(ShoppingListRecipeReference.recipe)
.joinedload(RecipeModel.recipe_category),
selectinload(ShoppingList.recipe_references)
.joinedload(ShoppingListRecipeReference.recipe)
.joinedload(RecipeModel.tags),
selectinload(ShoppingList.recipe_references)
.joinedload(ShoppingListRecipeReference.recipe)
.joinedload(RecipeModel.tools),
selectinload(ShoppingList.label_settings).joinedload(ShoppingListMultiPurposeLabel.label),
]
class ShoppingListPagination(PaginationBase):
items: list[ShoppingListSummary]
class ShoppingListUpdate(ShoppingListSave):
id: UUID4
list_items: list[ShoppingListItemOut] = []
class ShoppingListOut(ShoppingListUpdate):
recipe_references: list[ShoppingListRecipeRefOut] = []
label_settings: list[ShoppingListMultiPurposeLabelOut] = []
model_config = ConfigDict(from_attributes=True)
@field_validator("recipe_references", "label_settings", mode="before")
def default_none_to_empty_list(cls, v):
return v or []
@classmethod
def loader_options(cls) -> list[LoaderOption]:
return [
selectinload(ShoppingList.extras),
selectinload(ShoppingList.list_items).joinedload(ShoppingListItem.extras),
selectinload(ShoppingList.list_items)
.joinedload(ShoppingListItem.food)
.joinedload(IngredientFoodModel.extras),
selectinload(ShoppingList.list_items)
.joinedload(ShoppingListItem.food)
.joinedload(IngredientFoodModel.label),
selectinload(ShoppingList.list_items).joinedload(ShoppingListItem.label),
selectinload(ShoppingList.list_items).joinedload(ShoppingListItem.unit),
selectinload(ShoppingList.list_items).joinedload(ShoppingListItem.recipe_references),
selectinload(ShoppingList.recipe_references)
.joinedload(ShoppingListRecipeReference.recipe)
.joinedload(RecipeModel.recipe_category),
selectinload(ShoppingList.recipe_references)
.joinedload(ShoppingListRecipeReference.recipe)
.joinedload(RecipeModel.tags),
selectinload(ShoppingList.recipe_references)
.joinedload(ShoppingListRecipeReference.recipe)
.joinedload(RecipeModel.tools),
selectinload(ShoppingList.label_settings).joinedload(ShoppingListMultiPurposeLabel.label),
]
class ShoppingListAddRecipeParams(MealieModel):
recipe_increment_quantity: float = 1
recipe_ingredients: list[RecipeIngredient] | None = None
"""optionally override which ingredients are added from the recipe"""
class ShoppingListRemoveRecipeParams(MealieModel):
recipe_decrement_quantity: float = 1

View File

@@ -2,14 +2,6 @@ from mealie.pkgs.stats import fs_stats
from mealie.schema._mealie import MealieModel
class GroupStatistics(MealieModel):
total_recipes: int
total_users: int
total_categories: int
total_tags: int
total_tools: int
class GroupStorage(MealieModel):
used_storage_bytes: int
used_storage_str: str

View File

@@ -1,32 +0,0 @@
from uuid import UUID
from pydantic import ConfigDict
from mealie.schema._mealie import MealieModel
class CreateInviteToken(MealieModel):
uses: int
class SaveInviteToken(MealieModel):
uses_left: int
group_id: UUID
token: str
class ReadInviteToken(MealieModel):
token: str
uses_left: int
group_id: UUID
model_config = ConfigDict(from_attributes=True)
class EmailInvitation(MealieModel):
email: str
token: str
class EmailInitationResponse(MealieModel):
success: bool
error: str | None = None

View File

@@ -1,62 +0,0 @@
import datetime
import enum
from uuid import UUID
from isodate import parse_time
from pydantic import UUID4, ConfigDict, field_validator
from mealie.schema._mealie import MealieModel
from mealie.schema._mealie.datetime_parse import parse_datetime
from mealie.schema.response.pagination import PaginationBase
class WebhookType(str, enum.Enum):
mealplan = "mealplan"
class CreateWebhook(MealieModel):
enabled: bool = True
name: str = ""
url: str = ""
webhook_type: WebhookType = WebhookType.mealplan
scheduled_time: datetime.time
@field_validator("scheduled_time", mode="before")
@classmethod
def validate_scheduled_time(cls, v):
"""
Validator accepts both datetime and time values from external sources.
DateTime types are parsed and converted to time objects without timezones
type: time is treated as a UTC value
type: datetime is treated as a value with a timezone
"""
parser_funcs = [
lambda x: parse_datetime(x).astimezone(datetime.timezone.utc).time(),
parse_time,
]
if isinstance(v, datetime.time):
return v
for parser_func in parser_funcs:
try:
return parser_func(v)
except ValueError:
continue
raise ValueError(f"Invalid scheduled time: {v}")
class SaveWebhook(CreateWebhook):
group_id: UUID
class ReadWebhook(SaveWebhook):
id: UUID4
model_config = ConfigDict(from_attributes=True)
class WebhookPagination(PaginationBase):
items: list[ReadWebhook]