mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-04-13 16:35:36 -04:00
feat: Add Households to Mealie (#3970)
This commit is contained in:
@@ -3,11 +3,11 @@ from .datetime_parse import DateError, DateTimeError, DurationError, TimeError
|
||||
from .mealie_model import HasUUID, MealieModel, SearchType
|
||||
|
||||
__all__ = [
|
||||
"HasUUID",
|
||||
"MealieModel",
|
||||
"SearchType",
|
||||
"DateError",
|
||||
"DateTimeError",
|
||||
"DurationError",
|
||||
"TimeError",
|
||||
"HasUUID",
|
||||
"MealieModel",
|
||||
"SearchType",
|
||||
]
|
||||
|
||||
@@ -7,7 +7,7 @@ from enum import Enum
|
||||
from typing import ClassVar, Protocol, TypeVar
|
||||
|
||||
from humps.main import camelize
|
||||
from pydantic import UUID4, BaseModel, ConfigDict, model_validator
|
||||
from pydantic import UUID4, AliasChoices, BaseModel, ConfigDict, Field, model_validator
|
||||
from sqlalchemy import Select, desc, func, or_, text
|
||||
from sqlalchemy.orm import InstrumentedAttribute, Session
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
@@ -20,6 +20,26 @@ T = TypeVar("T", bound=BaseModel)
|
||||
HOUR_ONLY_TZ_PATTERN = re.compile(r"[+-]\d{2}$")
|
||||
|
||||
|
||||
def UpdatedAtField(*args, **kwargs):
|
||||
"""
|
||||
Wrapper for Pydantic's Field, which sets default values for our update_at aliases
|
||||
|
||||
Since the database stores this value as update_at, we want to accept this as a possible value
|
||||
"""
|
||||
|
||||
kwargs.pop("alias", None)
|
||||
kwargs.pop("alias_generator", None)
|
||||
|
||||
# ensure the alias is not overwritten by the generator, if there is one
|
||||
# https://docs.pydantic.dev/latest/concepts/alias/#alias-priority
|
||||
kwargs["alias_priority"] = 2
|
||||
|
||||
kwargs["validation_alias"] = AliasChoices("update_at", "updateAt", "updated_at", "updatedAt")
|
||||
kwargs["serialization_alias"] = "updatedAt"
|
||||
|
||||
return Field(*args, **kwargs)
|
||||
|
||||
|
||||
class SearchType(Enum):
|
||||
fuzzy = "fuzzy"
|
||||
tokenized = "tokenized"
|
||||
@@ -77,7 +97,7 @@ class MealieModel(BaseModel):
|
||||
Cast the current model to another with additional arguments. Useful for
|
||||
transforming DTOs into models that are saved to a database
|
||||
"""
|
||||
create_data = {field: getattr(self, field) for field in self.__fields__ if field in cls.__fields__}
|
||||
create_data = {field: getattr(self, field) for field in self.model_fields if field in cls.model_fields}
|
||||
create_data.update(kwargs or {})
|
||||
return cls(**create_data)
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ __all__ = [
|
||||
"MaintenanceLogs",
|
||||
"MaintenanceStorageDetails",
|
||||
"MaintenanceSummary",
|
||||
"ChowdownURL",
|
||||
"MigrationFile",
|
||||
"MigrationImport",
|
||||
"Migrations",
|
||||
"CustomPageBase",
|
||||
"CustomPageOut",
|
||||
"CommentImport",
|
||||
"CustomPageImport",
|
||||
"GroupImport",
|
||||
@@ -28,11 +34,11 @@ __all__ = [
|
||||
"RecipeImport",
|
||||
"SettingsImport",
|
||||
"UserImport",
|
||||
"EmailReady",
|
||||
"EmailSuccess",
|
||||
"EmailTest",
|
||||
"CustomPageBase",
|
||||
"CustomPageOut",
|
||||
"AllBackups",
|
||||
"BackupFile",
|
||||
"BackupOptions",
|
||||
"CreateBackup",
|
||||
"ImportJob",
|
||||
"AdminAboutInfo",
|
||||
"AppInfo",
|
||||
"AppStartupInfo",
|
||||
@@ -40,13 +46,7 @@ __all__ = [
|
||||
"AppTheme",
|
||||
"CheckAppConfig",
|
||||
"OIDCInfo",
|
||||
"ChowdownURL",
|
||||
"MigrationFile",
|
||||
"MigrationImport",
|
||||
"Migrations",
|
||||
"AllBackups",
|
||||
"BackupFile",
|
||||
"BackupOptions",
|
||||
"CreateBackup",
|
||||
"ImportJob",
|
||||
"EmailReady",
|
||||
"EmailSuccess",
|
||||
"EmailTest",
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@ from mealie.schema._mealie import MealieModel
|
||||
class AppStatistics(MealieModel):
|
||||
total_recipes: int
|
||||
total_users: int
|
||||
total_households: int
|
||||
total_groups: int
|
||||
uncategorized_recipes: int
|
||||
untagged_recipes: int
|
||||
@@ -15,6 +16,7 @@ class AppInfo(MealieModel):
|
||||
demo_status: bool
|
||||
allow_signup: bool
|
||||
default_group_slug: str | None = None
|
||||
default_household_slug: str | None = None
|
||||
enable_oidc: bool
|
||||
oidc_redirect: bool
|
||||
oidc_provider_name: str
|
||||
@@ -58,6 +60,7 @@ class AdminAboutInfo(AppInfo):
|
||||
db_type: str
|
||||
db_url: str | None = None
|
||||
default_group: str
|
||||
default_household: str
|
||||
build_id: str
|
||||
recipe_scraper_version: str
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.recipe.recipe import RecipeSummary, RecipeTool
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
from ...db.models.group import CookBook
|
||||
from ...db.models.household import CookBook
|
||||
from ..recipe.recipe_category import CategoryBase, TagBase
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ class CreateCookBook(MealieModel):
|
||||
|
||||
class SaveCookBook(CreateCookBook):
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
|
||||
|
||||
class UpdateCookBook(SaveCookBook):
|
||||
@@ -52,6 +53,7 @@ class UpdateCookBook(SaveCookBook):
|
||||
|
||||
class ReadCookBook(UpdateCookBook):
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
categories: list[CategoryBase] = []
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -66,5 +68,6 @@ class CookBookPagination(PaginationBase):
|
||||
|
||||
class RecipeCookBook(ReadCookBook):
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
recipes: list[RecipeSummary]
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
126
mealie/schema/household/__init__.py
Normal file
126
mealie/schema/household/__init__.py
Normal file
@@ -0,0 +1,126 @@
|
||||
# This file is auto-generated by gen_schema_exports.py
|
||||
from .group_events import (
|
||||
GroupEventNotifierCreate,
|
||||
GroupEventNotifierOptions,
|
||||
GroupEventNotifierOptionsOut,
|
||||
GroupEventNotifierOptionsSave,
|
||||
GroupEventNotifierOut,
|
||||
GroupEventNotifierPrivate,
|
||||
GroupEventNotifierSave,
|
||||
GroupEventNotifierUpdate,
|
||||
GroupEventPagination,
|
||||
)
|
||||
from .group_recipe_action import (
|
||||
CreateGroupRecipeAction,
|
||||
GroupRecipeActionOut,
|
||||
GroupRecipeActionPagination,
|
||||
GroupRecipeActionType,
|
||||
SaveGroupRecipeAction,
|
||||
)
|
||||
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 .household import (
|
||||
HouseholdCreate,
|
||||
HouseholdInDB,
|
||||
HouseholdPagination,
|
||||
HouseholdSave,
|
||||
HouseholdSummary,
|
||||
HouseholdUserSummary,
|
||||
UpdateHousehold,
|
||||
UpdateHouseholdAdmin,
|
||||
)
|
||||
from .household_permissions import SetPermissions
|
||||
from .household_preferences import (
|
||||
CreateHouseholdPreferences,
|
||||
ReadHouseholdPreferences,
|
||||
SaveHouseholdPreferences,
|
||||
UpdateHouseholdPreferences,
|
||||
)
|
||||
from .household_statistics import HouseholdStatistics
|
||||
from .invite_token import CreateInviteToken, EmailInitationResponse, EmailInvitation, ReadInviteToken, SaveInviteToken
|
||||
from .webhook import CreateWebhook, ReadWebhook, SaveWebhook, WebhookPagination, WebhookType
|
||||
|
||||
__all__ = [
|
||||
"GroupEventNotifierCreate",
|
||||
"GroupEventNotifierOptions",
|
||||
"GroupEventNotifierOptionsOut",
|
||||
"GroupEventNotifierOptionsSave",
|
||||
"GroupEventNotifierOut",
|
||||
"GroupEventNotifierPrivate",
|
||||
"GroupEventNotifierSave",
|
||||
"GroupEventNotifierUpdate",
|
||||
"GroupEventPagination",
|
||||
"CreateGroupRecipeAction",
|
||||
"GroupRecipeActionOut",
|
||||
"GroupRecipeActionPagination",
|
||||
"GroupRecipeActionType",
|
||||
"SaveGroupRecipeAction",
|
||||
"CreateWebhook",
|
||||
"ReadWebhook",
|
||||
"SaveWebhook",
|
||||
"WebhookPagination",
|
||||
"WebhookType",
|
||||
"CreateHouseholdPreferences",
|
||||
"ReadHouseholdPreferences",
|
||||
"SaveHouseholdPreferences",
|
||||
"UpdateHouseholdPreferences",
|
||||
"HouseholdCreate",
|
||||
"HouseholdInDB",
|
||||
"HouseholdPagination",
|
||||
"HouseholdSave",
|
||||
"HouseholdSummary",
|
||||
"HouseholdUserSummary",
|
||||
"UpdateHousehold",
|
||||
"UpdateHouseholdAdmin",
|
||||
"HouseholdStatistics",
|
||||
"CreateInviteToken",
|
||||
"EmailInitationResponse",
|
||||
"EmailInvitation",
|
||||
"ReadInviteToken",
|
||||
"SaveInviteToken",
|
||||
"ShoppingListAddRecipeParams",
|
||||
"ShoppingListCreate",
|
||||
"ShoppingListItemBase",
|
||||
"ShoppingListItemCreate",
|
||||
"ShoppingListItemOut",
|
||||
"ShoppingListItemPagination",
|
||||
"ShoppingListItemRecipeRefCreate",
|
||||
"ShoppingListItemRecipeRefOut",
|
||||
"ShoppingListItemRecipeRefUpdate",
|
||||
"ShoppingListItemUpdate",
|
||||
"ShoppingListItemUpdateBulk",
|
||||
"ShoppingListItemsCollectionOut",
|
||||
"ShoppingListMultiPurposeLabelCreate",
|
||||
"ShoppingListMultiPurposeLabelOut",
|
||||
"ShoppingListMultiPurposeLabelUpdate",
|
||||
"ShoppingListOut",
|
||||
"ShoppingListPagination",
|
||||
"ShoppingListRecipeRefOut",
|
||||
"ShoppingListRemoveRecipeParams",
|
||||
"ShoppingListSave",
|
||||
"ShoppingListSummary",
|
||||
"ShoppingListUpdate",
|
||||
"SetPermissions",
|
||||
]
|
||||
@@ -2,7 +2,7 @@ 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.db.models.household import GroupEventNotifierModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
@@ -69,6 +69,7 @@ class GroupEventNotifierCreate(MealieModel):
|
||||
class GroupEventNotifierSave(GroupEventNotifierCreate):
|
||||
enabled: bool = True
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
options: GroupEventNotifierOptions = GroupEventNotifierOptions()
|
||||
|
||||
|
||||
@@ -82,6 +83,7 @@ class GroupEventNotifierOut(MealieModel):
|
||||
name: str
|
||||
enabled: bool
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
options: GroupEventNotifierOptionsOut
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -21,6 +21,7 @@ class CreateGroupRecipeAction(MealieModel):
|
||||
|
||||
class SaveGroupRecipeAction(CreateGroupRecipeAction):
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
|
||||
|
||||
class GroupRecipeActionOut(SaveGroupRecipeAction):
|
||||
@@ -7,7 +7,7 @@ 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 (
|
||||
from mealie.db.models.household import (
|
||||
ShoppingList,
|
||||
ShoppingListItem,
|
||||
ShoppingListMultiPurposeLabel,
|
||||
@@ -15,6 +15,7 @@ from mealie.db.models.group import (
|
||||
)
|
||||
from mealie.db.models.recipe import IngredientFoodModel, RecipeModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema._mealie.mealie_model import UpdatedAtField
|
||||
from mealie.schema._mealie.types import NoneFloat
|
||||
from mealie.schema.labels.multi_purpose_label import MultiPurposeLabelSummary
|
||||
from mealie.schema.recipe.recipe import RecipeSummary
|
||||
@@ -104,6 +105,8 @@ class ShoppingListItemUpdateBulk(ShoppingListItemUpdate):
|
||||
|
||||
class ShoppingListItemOut(ShoppingListItemBase):
|
||||
id: UUID4
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
|
||||
food: IngredientFood | None = None
|
||||
label: MultiPurposeLabelSummary | None = None
|
||||
@@ -112,7 +115,7 @@ class ShoppingListItemOut(ShoppingListItemBase):
|
||||
recipe_references: list[ShoppingListItemRecipeRefOut] = []
|
||||
|
||||
created_at: datetime | None = None
|
||||
update_at: datetime | None = None
|
||||
updated_at: datetime | None = UpdatedAtField(None)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def populate_missing_label(self):
|
||||
@@ -134,6 +137,7 @@ class ShoppingListItemOut(ShoppingListItemBase):
|
||||
joinedload(ShoppingListItem.label),
|
||||
joinedload(ShoppingListItem.unit),
|
||||
selectinload(ShoppingListItem.recipe_references),
|
||||
joinedload(ShoppingListItem.shopping_list).joinedload(ShoppingList.user),
|
||||
]
|
||||
|
||||
|
||||
@@ -173,7 +177,7 @@ class ShoppingListCreate(MealieModel):
|
||||
extras: dict | None = {}
|
||||
|
||||
created_at: datetime | None = None
|
||||
update_at: datetime | None = None
|
||||
updated_at: datetime | None = UpdatedAtField(None)
|
||||
|
||||
@field_validator("extras", mode="before")
|
||||
def convert_extras_to_dict(cls, v):
|
||||
@@ -209,6 +213,7 @@ class ShoppingListSave(ShoppingListCreate):
|
||||
|
||||
class ShoppingListSummary(ShoppingListSave):
|
||||
id: UUID4
|
||||
household_id: UUID4
|
||||
recipe_references: list[ShoppingListRecipeRefOut]
|
||||
label_settings: list[ShoppingListMultiPurposeLabelOut]
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -227,6 +232,7 @@ class ShoppingListSummary(ShoppingListSave):
|
||||
.joinedload(ShoppingListRecipeReference.recipe)
|
||||
.joinedload(RecipeModel.tools),
|
||||
selectinload(ShoppingList.label_settings).joinedload(ShoppingListMultiPurposeLabel.label),
|
||||
joinedload(ShoppingList.user),
|
||||
]
|
||||
|
||||
|
||||
@@ -240,6 +246,7 @@ class ShoppingListUpdate(ShoppingListSave):
|
||||
|
||||
|
||||
class ShoppingListOut(ShoppingListUpdate):
|
||||
household_id: UUID4
|
||||
recipe_references: list[ShoppingListRecipeRefOut] = []
|
||||
label_settings: list[ShoppingListMultiPurposeLabelOut] = []
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -272,6 +279,7 @@ class ShoppingListOut(ShoppingListUpdate):
|
||||
.joinedload(ShoppingListRecipeReference.recipe)
|
||||
.joinedload(RecipeModel.tools),
|
||||
selectinload(ShoppingList.label_settings).joinedload(ShoppingListMultiPurposeLabel.label),
|
||||
joinedload(ShoppingList.user),
|
||||
]
|
||||
|
||||
|
||||
75
mealie/schema/household/household.py
Normal file
75
mealie/schema/household/household.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import UUID4, ConfigDict, StringConstraints, field_validator
|
||||
from sqlalchemy.orm import joinedload, selectinload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.db.models.household.household import Household
|
||||
from mealie.db.models.users.users import User
|
||||
from mealie.schema._mealie.mealie_model import MealieModel
|
||||
from mealie.schema.household.webhook import ReadWebhook
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
from .household_preferences import ReadHouseholdPreferences, UpdateHouseholdPreferences
|
||||
|
||||
|
||||
class HouseholdCreate(MealieModel):
|
||||
group_id: UUID4 | None = None
|
||||
name: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class HouseholdSave(HouseholdCreate):
|
||||
group_id: UUID4
|
||||
|
||||
|
||||
class UpdateHousehold(HouseholdSave):
|
||||
id: UUID4
|
||||
slug: str
|
||||
|
||||
|
||||
class UpdateHouseholdAdmin(HouseholdSave):
|
||||
id: UUID4
|
||||
preferences: UpdateHouseholdPreferences | None = None
|
||||
|
||||
|
||||
class HouseholdSummary(UpdateHousehold):
|
||||
preferences: ReadHouseholdPreferences | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class HouseholdUserSummary(MealieModel):
|
||||
id: UUID4
|
||||
full_name: str
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class HouseholdInDB(HouseholdSummary):
|
||||
group: str
|
||||
users: list[HouseholdUserSummary] | None = None
|
||||
webhooks: list[ReadWebhook] = []
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@classmethod
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [
|
||||
joinedload(Household.group),
|
||||
joinedload(Household.webhooks),
|
||||
joinedload(Household.preferences),
|
||||
selectinload(Household.users).joinedload(User.group),
|
||||
selectinload(Household.users).joinedload(User.tokens),
|
||||
]
|
||||
|
||||
@field_validator("group", mode="before")
|
||||
def convert_group_to_name(cls, v):
|
||||
if not v or isinstance(v, str):
|
||||
return v
|
||||
|
||||
try:
|
||||
return v.name
|
||||
except AttributeError:
|
||||
return v
|
||||
|
||||
|
||||
class HouseholdPagination(PaginationBase):
|
||||
items: list[HouseholdInDB]
|
||||
28
mealie/schema/household/household_preferences.py
Normal file
28
mealie/schema/household/household_preferences.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from pydantic import UUID4, ConfigDict
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class UpdateHouseholdPreferences(MealieModel):
|
||||
private_household: 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 CreateHouseholdPreferences(UpdateHouseholdPreferences): ...
|
||||
|
||||
|
||||
class SaveHouseholdPreferences(UpdateHouseholdPreferences):
|
||||
household_id: UUID4
|
||||
|
||||
|
||||
class ReadHouseholdPreferences(CreateHouseholdPreferences):
|
||||
id: UUID4
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
9
mealie/schema/household/household_statistics.py
Normal file
9
mealie/schema/household/household_statistics.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from mealie.schema._mealie.mealie_model import MealieModel
|
||||
|
||||
|
||||
class HouseholdStatistics(MealieModel):
|
||||
total_recipes: int
|
||||
total_users: int
|
||||
total_categories: int
|
||||
total_tags: int
|
||||
total_tools: int
|
||||
@@ -12,6 +12,7 @@ class CreateInviteToken(MealieModel):
|
||||
class SaveInviteToken(MealieModel):
|
||||
uses_left: int
|
||||
group_id: UUID
|
||||
household_id: UUID
|
||||
token: str
|
||||
|
||||
|
||||
@@ -19,6 +20,7 @@ class ReadInviteToken(MealieModel):
|
||||
token: str
|
||||
uses_left: int
|
||||
group_id: UUID
|
||||
household_id: UUID
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import datetime
|
||||
import enum
|
||||
from uuid import UUID
|
||||
|
||||
from isodate import parse_time
|
||||
from pydantic import UUID4, ConfigDict, field_validator
|
||||
@@ -50,7 +49,8 @@ class CreateWebhook(MealieModel):
|
||||
|
||||
|
||||
class SaveWebhook(CreateWebhook):
|
||||
group_id: UUID
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
|
||||
|
||||
class ReadWebhook(SaveWebhook):
|
||||
@@ -1,5 +1,4 @@
|
||||
# This file is auto-generated by gen_schema_exports.py
|
||||
from .meal import MealDayIn, MealDayOut, MealIn, MealPlanIn, MealPlanOut
|
||||
from .new_meal import (
|
||||
CreatePlanEntry,
|
||||
CreateRandomEntry,
|
||||
@@ -22,14 +21,6 @@ from .plan_rules import (
|
||||
from .shopping_list import ListItem, ShoppingListIn, ShoppingListOut
|
||||
|
||||
__all__ = [
|
||||
"Category",
|
||||
"PlanRulesCreate",
|
||||
"PlanRulesDay",
|
||||
"PlanRulesOut",
|
||||
"PlanRulesPagination",
|
||||
"PlanRulesSave",
|
||||
"PlanRulesType",
|
||||
"Tag",
|
||||
"ListItem",
|
||||
"ShoppingListIn",
|
||||
"ShoppingListOut",
|
||||
@@ -40,9 +31,12 @@ __all__ = [
|
||||
"ReadPlanEntry",
|
||||
"SavePlanEntry",
|
||||
"UpdatePlanEntry",
|
||||
"MealDayIn",
|
||||
"MealDayOut",
|
||||
"MealIn",
|
||||
"MealPlanIn",
|
||||
"MealPlanOut",
|
||||
"Category",
|
||||
"PlanRulesCreate",
|
||||
"PlanRulesDay",
|
||||
"PlanRulesOut",
|
||||
"PlanRulesPagination",
|
||||
"PlanRulesSave",
|
||||
"PlanRulesType",
|
||||
"Tag",
|
||||
]
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import datetime
|
||||
|
||||
from pydantic import ConfigDict, field_validator
|
||||
from pydantic_core.core_schema import ValidationInfo
|
||||
|
||||
from mealie.schema._mealie import MealieModel
|
||||
|
||||
|
||||
class MealIn(MealieModel):
|
||||
slug: str | None = None
|
||||
name: str | None = None
|
||||
description: str | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class MealDayIn(MealieModel):
|
||||
date: datetime.date | None = None
|
||||
meals: list[MealIn]
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class MealDayOut(MealDayIn):
|
||||
id: int
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class MealPlanIn(MealieModel):
|
||||
group: str
|
||||
start_date: datetime.date
|
||||
end_date: datetime.date
|
||||
plan_days: list[MealDayIn]
|
||||
|
||||
@field_validator("end_date")
|
||||
def end_date_after_start_date(v, info: ValidationInfo):
|
||||
if "start_date" in info.data and v < info.data["start_date"]:
|
||||
raise ValueError("EndDate should be greater than StartDate")
|
||||
return v
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class MealPlanOut(MealPlanIn):
|
||||
id: int
|
||||
shopping_list: int | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -8,7 +8,7 @@ from pydantic_core.core_schema import ValidationInfo
|
||||
from sqlalchemy.orm import selectinload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.db.models.group import GroupMealPlan
|
||||
from mealie.db.models.household import GroupMealPlan
|
||||
from mealie.db.models.recipe import RecipeModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.recipe.recipe import RecipeSummary
|
||||
@@ -56,6 +56,7 @@ class SavePlanEntry(CreatePlanEntry):
|
||||
|
||||
|
||||
class ReadPlanEntry(UpdatePlanEntry):
|
||||
household_id: UUID
|
||||
recipe: RecipeSummary | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -65,6 +66,7 @@ class ReadPlanEntry(UpdatePlanEntry):
|
||||
selectinload(GroupMealPlan.recipe).joinedload(RecipeModel.recipe_category),
|
||||
selectinload(GroupMealPlan.recipe).joinedload(RecipeModel.tags),
|
||||
selectinload(GroupMealPlan.recipe).joinedload(RecipeModel.tools),
|
||||
selectinload(GroupMealPlan.user),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ from pydantic import UUID4, ConfigDict
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.db.models.group import GroupMealPlanRules
|
||||
from mealie.db.models.household import GroupMealPlanRules
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
@@ -57,6 +57,7 @@ class PlanRulesCreate(MealieModel):
|
||||
|
||||
class PlanRulesSave(PlanRulesCreate):
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
|
||||
|
||||
class PlanRulesOut(PlanRulesSave):
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# This file is auto-generated by gen_schema_exports.py
|
||||
from .recipe_ingredient import OpenAIIngredient, OpenAIIngredients
|
||||
|
||||
__all__ = [
|
||||
"OpenAIIngredient",
|
||||
"OpenAIIngredients",
|
||||
]
|
||||
|
||||
@@ -88,10 +88,9 @@ from .recipe_tool import RecipeToolCreate, RecipeToolOut, RecipeToolResponse, Re
|
||||
from .request_helpers import RecipeDuplicate, RecipeSlug, RecipeZipTokenResponse, SlugResponse, UpdateImageResponse
|
||||
|
||||
__all__ = [
|
||||
"RecipeToolCreate",
|
||||
"RecipeToolOut",
|
||||
"RecipeToolResponse",
|
||||
"RecipeToolSave",
|
||||
"IngredientReferences",
|
||||
"RecipeStep",
|
||||
"RecipeNote",
|
||||
"CategoryBase",
|
||||
"CategoryIn",
|
||||
"CategoryOut",
|
||||
@@ -102,41 +101,19 @@ __all__ = [
|
||||
"TagIn",
|
||||
"TagOut",
|
||||
"TagSave",
|
||||
"AssignCategories",
|
||||
"AssignSettings",
|
||||
"AssignTags",
|
||||
"DeleteRecipes",
|
||||
"ExportBase",
|
||||
"ExportRecipes",
|
||||
"ExportTypes",
|
||||
"RecipeAsset",
|
||||
"RecipeTimelineEventCreate",
|
||||
"RecipeTimelineEventIn",
|
||||
"RecipeTimelineEventOut",
|
||||
"RecipeTimelineEventPagination",
|
||||
"RecipeTimelineEventUpdate",
|
||||
"TimelineEventImage",
|
||||
"TimelineEventType",
|
||||
"Nutrition",
|
||||
"RecipeShareToken",
|
||||
"RecipeShareTokenCreate",
|
||||
"RecipeShareTokenSave",
|
||||
"RecipeShareTokenSummary",
|
||||
"ScrapeRecipe",
|
||||
"ScrapeRecipeTest",
|
||||
"RecipeCommentCreate",
|
||||
"RecipeCommentOut",
|
||||
"RecipeCommentPagination",
|
||||
"RecipeCommentSave",
|
||||
"RecipeCommentUpdate",
|
||||
"UserBase",
|
||||
"RecipeImageTypes",
|
||||
"CreateRecipe",
|
||||
"CreateRecipeBulk",
|
||||
"CreateRecipeByUrlBulk",
|
||||
"Recipe",
|
||||
"RecipeCategory",
|
||||
"RecipeCategoryPagination",
|
||||
"RecipeLastMade",
|
||||
"RecipePagination",
|
||||
"RecipeSummary",
|
||||
"RecipeTag",
|
||||
"RecipeTagPagination",
|
||||
"RecipeTool",
|
||||
"RecipeToolPagination",
|
||||
"IngredientReferences",
|
||||
"RecipeStep",
|
||||
"CreateIngredientFood",
|
||||
"CreateIngredientFoodAlias",
|
||||
"CreateIngredientUnit",
|
||||
@@ -159,20 +136,43 @@ __all__ = [
|
||||
"SaveIngredientFood",
|
||||
"SaveIngredientUnit",
|
||||
"UnitFoodBase",
|
||||
"RecipeAsset",
|
||||
"RecipeTimelineEventCreate",
|
||||
"RecipeTimelineEventIn",
|
||||
"RecipeTimelineEventOut",
|
||||
"RecipeTimelineEventPagination",
|
||||
"RecipeTimelineEventUpdate",
|
||||
"TimelineEventImage",
|
||||
"TimelineEventType",
|
||||
"RecipeCommentCreate",
|
||||
"RecipeCommentOut",
|
||||
"RecipeCommentPagination",
|
||||
"RecipeCommentSave",
|
||||
"RecipeCommentUpdate",
|
||||
"UserBase",
|
||||
"RecipeSettings",
|
||||
"CreateRecipe",
|
||||
"CreateRecipeBulk",
|
||||
"CreateRecipeByUrlBulk",
|
||||
"Recipe",
|
||||
"RecipeCategory",
|
||||
"RecipeCategoryPagination",
|
||||
"RecipeLastMade",
|
||||
"RecipePagination",
|
||||
"RecipeSummary",
|
||||
"RecipeTag",
|
||||
"RecipeTagPagination",
|
||||
"RecipeTool",
|
||||
"RecipeToolPagination",
|
||||
"ScrapeRecipe",
|
||||
"ScrapeRecipeTest",
|
||||
"AssignCategories",
|
||||
"AssignSettings",
|
||||
"AssignTags",
|
||||
"DeleteRecipes",
|
||||
"ExportBase",
|
||||
"ExportRecipes",
|
||||
"ExportTypes",
|
||||
"RecipeToolCreate",
|
||||
"RecipeToolOut",
|
||||
"RecipeToolResponse",
|
||||
"RecipeToolSave",
|
||||
"RecipeImageTypes",
|
||||
"RecipeDuplicate",
|
||||
"RecipeSlug",
|
||||
"RecipeZipTokenResponse",
|
||||
"SlugResponse",
|
||||
"UpdateImageResponse",
|
||||
"Nutrition",
|
||||
"RecipeSettings",
|
||||
"RecipeNote",
|
||||
]
|
||||
|
||||
@@ -15,6 +15,7 @@ from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.core.config import get_app_dirs
|
||||
from mealie.schema._mealie import MealieModel, SearchType
|
||||
from mealie.schema._mealie.mealie_model import UpdatedAtField
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
from ...db.models.recipe import (
|
||||
@@ -83,6 +84,7 @@ class RecipeSummary(MealieModel):
|
||||
_normalize_search: ClassVar[bool] = True
|
||||
|
||||
user_id: UUID4 = Field(default_factory=uuid4, validate_default=True)
|
||||
household_id: UUID4 = Field(default_factory=uuid4, validate_default=True)
|
||||
group_id: UUID4 = Field(default_factory=uuid4, validate_default=True)
|
||||
|
||||
name: str | None = None
|
||||
@@ -106,7 +108,7 @@ class RecipeSummary(MealieModel):
|
||||
date_updated: datetime.datetime | None = None
|
||||
|
||||
created_at: datetime.datetime | None = None
|
||||
update_at: datetime.datetime | None = None
|
||||
updated_at: datetime.datetime | None = UpdatedAtField(None)
|
||||
last_made: datetime.datetime | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@@ -230,6 +232,12 @@ class Recipe(RecipeSummary):
|
||||
return uuid4()
|
||||
return group_id
|
||||
|
||||
@field_validator("household_id", mode="before")
|
||||
def validate_household_id(household_id: Any):
|
||||
if isinstance(household_id, int):
|
||||
return uuid4()
|
||||
return household_id
|
||||
|
||||
@field_validator("user_id", mode="before")
|
||||
def validate_user_id(user_id: Any):
|
||||
if isinstance(user_id, int):
|
||||
|
||||
@@ -5,7 +5,9 @@ from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.db.models.recipe import RecipeComment
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema._mealie.mealie_model import UpdatedAtField
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
|
||||
@@ -34,14 +36,17 @@ class RecipeCommentOut(RecipeCommentCreate):
|
||||
id: UUID4
|
||||
recipe_id: UUID4
|
||||
created_at: datetime
|
||||
update_at: datetime
|
||||
updated_at: datetime = UpdatedAtField(...)
|
||||
user_id: UUID4
|
||||
user: UserBase
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@classmethod
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [joinedload(RecipeComment.user)]
|
||||
return [
|
||||
joinedload(RecipeComment.user),
|
||||
joinedload(RecipeComment.recipe).joinedload(RecipeModel.user),
|
||||
]
|
||||
|
||||
|
||||
class RecipeCommentPagination(PaginationBase):
|
||||
|
||||
@@ -12,6 +12,7 @@ from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.db.models.recipe import IngredientFoodModel
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema._mealie.mealie_model import UpdatedAtField
|
||||
from mealie.schema._mealie.types import NoneFloat
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
@@ -78,7 +79,7 @@ class IngredientFood(CreateIngredientFood):
|
||||
aliases: list[IngredientFoodAlias] = []
|
||||
|
||||
created_at: datetime.datetime | None = None
|
||||
update_at: datetime.datetime | None = None
|
||||
updated_at: datetime.datetime | None = UpdatedAtField(None)
|
||||
|
||||
_searchable_properties: ClassVar[list[str]] = [
|
||||
"name_normalized",
|
||||
@@ -124,7 +125,7 @@ class IngredientUnit(CreateIngredientUnit):
|
||||
aliases: list[IngredientUnitAlias] = []
|
||||
|
||||
created_at: datetime.datetime | None = None
|
||||
update_at: datetime.datetime | None = None
|
||||
updated_at: datetime.datetime | None = UpdatedAtField(None)
|
||||
|
||||
_searchable_properties: ClassVar[list[str]] = [
|
||||
"name_normalized",
|
||||
|
||||
@@ -4,9 +4,13 @@ from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import UUID4, ConfigDict, Field
|
||||
from sqlalchemy.orm import joinedload
|
||||
from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.core.config import get_app_dirs
|
||||
from mealie.schema._mealie.mealie_model import MealieModel
|
||||
from mealie.db.models.recipe.recipe_timeline import RecipeTimelineEvent
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema._mealie.mealie_model import UpdatedAtField
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
@@ -52,10 +56,20 @@ class RecipeTimelineEventUpdate(MealieModel):
|
||||
|
||||
class RecipeTimelineEventOut(RecipeTimelineEventCreate):
|
||||
id: UUID4
|
||||
group_id: UUID4
|
||||
household_id: UUID4
|
||||
|
||||
created_at: datetime
|
||||
update_at: datetime
|
||||
updated_at: datetime = UpdatedAtField(...)
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@classmethod
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [
|
||||
joinedload(RecipeTimelineEvent.recipe),
|
||||
joinedload(RecipeTimelineEvent.user),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def image_dir_from_id(cls, recipe_id: UUID4 | str, timeline_event_id: UUID4 | str) -> Path:
|
||||
return Recipe.timeline_image_dir_from_id(recipe_id, timeline_event_id)
|
||||
|
||||
@@ -11,13 +11,13 @@ __all__ = [
|
||||
"QueryFilterComponent",
|
||||
"RelationalKeyword",
|
||||
"RelationalOperator",
|
||||
"SearchFilter",
|
||||
"ValidationResponse",
|
||||
"OrderByNullPosition",
|
||||
"OrderDirection",
|
||||
"PaginationBase",
|
||||
"PaginationQuery",
|
||||
"RecipeSearchQuery",
|
||||
"SearchFilter",
|
||||
"ErrorResponse",
|
||||
"FileTokenResponse",
|
||||
"SuccessResponse",
|
||||
|
||||
@@ -10,6 +10,7 @@ from dateutil import parser as date_parser
|
||||
from dateutil.parser import ParserError
|
||||
from humps import decamelize
|
||||
from sqlalchemy import ColumnElement, Select, and_, inspect, or_
|
||||
from sqlalchemy.ext.associationproxy import AssociationProxyInstance
|
||||
from sqlalchemy.orm import InstrumentedAttribute, Mapper
|
||||
from sqlalchemy.sql import sqltypes
|
||||
|
||||
@@ -255,7 +256,9 @@ class QueryFilter:
|
||||
Works with shallow attributes (e.g. "slug" from `RecipeModel`)
|
||||
and arbitrarily deep ones (e.g. "recipe.group.preferences" on `RecipeTimelineEvent`).
|
||||
"""
|
||||
mapper: Mapper
|
||||
model_attr: InstrumentedAttribute | None = None
|
||||
|
||||
attribute_chain = attr_string.split(".")
|
||||
if not attribute_chain:
|
||||
raise ValueError("invalid query string: attribute name cannot be empty")
|
||||
@@ -265,16 +268,29 @@ class QueryFilter:
|
||||
try:
|
||||
model_attr = getattr(current_model, attribute_link)
|
||||
|
||||
# proxied attributes can't be joined to the query directly, so we need to inspect the proxy
|
||||
# and get the actual model and its attribute
|
||||
if isinstance(model_attr, AssociationProxyInstance):
|
||||
proxied_attribute_link = model_attr.target_collection
|
||||
next_attribute_link = model_attr.value_attr
|
||||
model_attr = getattr(current_model, proxied_attribute_link)
|
||||
|
||||
if query is not None:
|
||||
query = query.join(model_attr, isouter=True)
|
||||
|
||||
mapper = inspect(current_model)
|
||||
relationship = mapper.relationships[proxied_attribute_link]
|
||||
current_model = relationship.mapper.class_
|
||||
model_attr = getattr(current_model, next_attribute_link)
|
||||
|
||||
# at the end of the chain there are no more relationships to inspect
|
||||
if i == len(attribute_chain) - 1:
|
||||
break
|
||||
|
||||
if query is not None:
|
||||
query = query.join(
|
||||
model_attr, isouter=True
|
||||
) # we use outer joins to not unintentionally filter out values
|
||||
query = query.join(model_attr, isouter=True)
|
||||
|
||||
mapper: Mapper = inspect(current_model)
|
||||
mapper = inspect(current_model)
|
||||
relationship = mapper.relationships[attribute_link]
|
||||
current_model = relationship.mapper.class_
|
||||
|
||||
|
||||
@@ -6,8 +6,10 @@ from .user import (
|
||||
CreateToken,
|
||||
DeleteTokenResponse,
|
||||
GroupBase,
|
||||
GroupHouseholdSummary,
|
||||
GroupInDB,
|
||||
GroupPagination,
|
||||
GroupSummary,
|
||||
LongLiveTokenIn,
|
||||
LongLiveTokenInDB,
|
||||
LongLiveTokenOut,
|
||||
@@ -21,6 +23,7 @@ from .user import (
|
||||
UserRatingOut,
|
||||
UserRatings,
|
||||
UserRatingSummary,
|
||||
UserRatingUpdate,
|
||||
UserSummary,
|
||||
UserSummaryPagination,
|
||||
)
|
||||
@@ -34,25 +37,27 @@ from .user_passwords import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CreateUserRegistration",
|
||||
"CredentialsRequest",
|
||||
"CredentialsRequestForm",
|
||||
"OIDCRequest",
|
||||
"Token",
|
||||
"TokenData",
|
||||
"UnlockResults",
|
||||
"ForgotPassword",
|
||||
"PasswordResetToken",
|
||||
"PrivatePasswordResetToken",
|
||||
"ResetPassword",
|
||||
"SavePasswordResetToken",
|
||||
"ValidateResetToken",
|
||||
"CredentialsRequest",
|
||||
"CredentialsRequestForm",
|
||||
"OIDCRequest",
|
||||
"Token",
|
||||
"TokenData",
|
||||
"UnlockResults",
|
||||
"CreateUserRegistration",
|
||||
"ChangePassword",
|
||||
"CreateToken",
|
||||
"DeleteTokenResponse",
|
||||
"GroupBase",
|
||||
"GroupHouseholdSummary",
|
||||
"GroupInDB",
|
||||
"GroupPagination",
|
||||
"GroupSummary",
|
||||
"LongLiveTokenIn",
|
||||
"LongLiveTokenInDB",
|
||||
"LongLiveTokenOut",
|
||||
@@ -65,6 +70,7 @@ __all__ = [
|
||||
"UserRatingCreate",
|
||||
"UserRatingOut",
|
||||
"UserRatingSummary",
|
||||
"UserRatingUpdate",
|
||||
"UserRatings",
|
||||
"UserSummary",
|
||||
"UserSummaryPagination",
|
||||
|
||||
@@ -9,6 +9,7 @@ from mealie.schema._mealie.validators import validate_locale
|
||||
|
||||
class CreateUserRegistration(MealieModel):
|
||||
group: str | None = None
|
||||
household: str | None = None
|
||||
group_token: Annotated[str | None, Field(validate_default=True)] = None
|
||||
email: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)]
|
||||
username: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)]
|
||||
|
||||
@@ -9,10 +9,10 @@ from sqlalchemy.orm.interfaces import LoaderOption
|
||||
|
||||
from mealie.core.config import get_app_dirs, get_app_settings
|
||||
from mealie.db.models.users import User
|
||||
from mealie.db.models.users.users import AuthMethod
|
||||
from mealie.db.models.users.users import AuthMethod, LongLiveToken
|
||||
from mealie.schema._mealie import MealieModel
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.group.webhook import CreateWebhook, ReadWebhook
|
||||
from mealie.schema.household.webhook import CreateWebhook, ReadWebhook
|
||||
from mealie.schema.response.pagination import PaginationBase
|
||||
|
||||
from ...db.models.group import Group
|
||||
@@ -35,6 +35,10 @@ class LongLiveTokenOut(MealieModel):
|
||||
created_at: datetime | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
@classmethod
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [joinedload(LongLiveToken.user)]
|
||||
|
||||
|
||||
class CreateToken(LongLiveTokenIn):
|
||||
user_id: UUID4
|
||||
@@ -53,7 +57,7 @@ class ChangePassword(MealieModel):
|
||||
|
||||
|
||||
class GroupBase(MealieModel):
|
||||
name: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] # type: ignore
|
||||
name: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)]
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
@@ -93,10 +97,11 @@ class UserBase(MealieModel):
|
||||
id: UUID4 | None = None
|
||||
username: str | None = None
|
||||
full_name: str | None = None
|
||||
email: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)] # type: ignore
|
||||
email: Annotated[str, StringConstraints(to_lower=True, strip_whitespace=True)]
|
||||
auth_method: AuthMethod = AuthMethod.MEALIE
|
||||
admin: bool = False
|
||||
group: str | None = None
|
||||
household: str | None = None
|
||||
advanced: bool = False
|
||||
|
||||
can_invite: bool = False
|
||||
@@ -110,6 +115,7 @@ class UserBase(MealieModel):
|
||||
"fullName": "Change Me",
|
||||
"email": "changeme@example.com",
|
||||
"group": settings.DEFAULT_GROUP,
|
||||
"household": settings.DEFAULT_HOUSEHOLD,
|
||||
"admin": "false",
|
||||
}
|
||||
},
|
||||
@@ -125,6 +131,16 @@ class UserBase(MealieModel):
|
||||
except AttributeError:
|
||||
return v
|
||||
|
||||
@field_validator("household", mode="before")
|
||||
def convert_household_to_name(cls, v):
|
||||
if not v or isinstance(v, str):
|
||||
return v
|
||||
|
||||
try:
|
||||
return v.name
|
||||
except AttributeError:
|
||||
return v
|
||||
|
||||
|
||||
class UserIn(UserBase):
|
||||
password: str
|
||||
@@ -135,6 +151,9 @@ class UserOut(UserBase):
|
||||
group: str
|
||||
group_id: UUID4
|
||||
group_slug: str
|
||||
household: str
|
||||
household_id: UUID4
|
||||
household_slug: str
|
||||
tokens: list[LongLiveTokenOut] | None = None
|
||||
cache_key: str
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -145,7 +164,7 @@ class UserOut(UserBase):
|
||||
|
||||
@classmethod
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [joinedload(User.group), joinedload(User.tokens)]
|
||||
return [joinedload(User.group), joinedload(User.household), joinedload(User.tokens)]
|
||||
|
||||
|
||||
class UserSummary(MealieModel):
|
||||
@@ -164,7 +183,6 @@ class UserSummaryPagination(PaginationBase):
|
||||
|
||||
class PrivateUser(UserOut):
|
||||
password: str
|
||||
group_id: UUID4
|
||||
login_attemps: int = 0
|
||||
locked_at: datetime | None = None
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
@@ -193,7 +211,7 @@ class PrivateUser(UserOut):
|
||||
|
||||
@classmethod
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [joinedload(User.group), joinedload(User.tokens)]
|
||||
return [joinedload(User.group), joinedload(User.household), joinedload(User.tokens)]
|
||||
|
||||
|
||||
class UpdateGroup(GroupBase):
|
||||
@@ -205,7 +223,14 @@ class UpdateGroup(GroupBase):
|
||||
webhooks: list[CreateWebhook] = []
|
||||
|
||||
|
||||
class GroupHouseholdSummary(MealieModel):
|
||||
id: UUID4
|
||||
name: str
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class GroupInDB(UpdateGroup):
|
||||
households: list[GroupHouseholdSummary] | None = None
|
||||
users: list[UserSummary] | None = None
|
||||
preferences: ReadGroupPreferences | None = None
|
||||
webhooks: list[ReadWebhook] = []
|
||||
@@ -238,6 +263,7 @@ class GroupInDB(UpdateGroup):
|
||||
joinedload(Group.categories),
|
||||
joinedload(Group.webhooks),
|
||||
joinedload(Group.preferences),
|
||||
joinedload(Group.households),
|
||||
selectinload(Group.users).joinedload(User.group),
|
||||
selectinload(Group.users).joinedload(User.tokens),
|
||||
]
|
||||
|
||||
@@ -39,5 +39,6 @@ class PrivatePasswordResetToken(SavePasswordResetToken):
|
||||
def loader_options(cls) -> list[LoaderOption]:
|
||||
return [
|
||||
selectinload(PasswordResetModel.user).joinedload(User.group),
|
||||
selectinload(PasswordResetModel.user).joinedload(User.household),
|
||||
selectinload(PasswordResetModel.user).joinedload(User.tokens),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user