mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-01-01 22:51:21 -05:00
feat(backend): ✨ rewrite mealplanner with simple api (#683)
* feat(backend): ✨ new meal-planner feature * feat(frontend): ✨ new meal plan feature * refactor(backend): ♻️ refactor base services classes and add mixins for crud * feat(frontend): ✨ add UI/API for mealplanner * feat(backend): ✨ add get_today and get_slice options for mealplanner * test(backend): ✅ add and update group mealplanner tests * fix(backend): 🐛 Fix recipe_id column type for PG Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
@@ -3,14 +3,14 @@ from logging import getLogger
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.data_access_layer.group_access_model import GroupDataAccessModel
|
||||
from mealie.db.data_access_layer.meal_access_model import MealDataAccessModel
|
||||
from mealie.db.models.event import Event, EventNotification
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.group import Group, GroupMealPlan
|
||||
from mealie.db.models.group.cookbook import CookBook
|
||||
from mealie.db.models.group.invite_tokens import GroupInviteToken
|
||||
from mealie.db.models.group.preferences import GroupPreferencesModel
|
||||
from mealie.db.models.group.shopping_list import ShoppingList
|
||||
from mealie.db.models.group.webhooks import GroupWebhooksModel
|
||||
from mealie.db.models.mealplan import MealPlan
|
||||
from mealie.db.models.recipe.category import Category
|
||||
from mealie.db.models.recipe.comment import RecipeComment
|
||||
from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUnitModel
|
||||
@@ -25,7 +25,8 @@ from mealie.schema.events import EventNotificationIn
|
||||
from mealie.schema.group.group_preferences import ReadGroupPreferences
|
||||
from mealie.schema.group.invite_token import ReadInviteToken
|
||||
from mealie.schema.group.webhook import ReadWebhook
|
||||
from mealie.schema.meal_plan import MealPlanOut, ShoppingListOut
|
||||
from mealie.schema.meal_plan import ShoppingListOut
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
from mealie.schema.recipe import (
|
||||
CommentOut,
|
||||
IngredientFood,
|
||||
@@ -90,7 +91,7 @@ class DatabaseAccessLayer:
|
||||
# Group Data
|
||||
self.groups = GroupDataAccessModel(pk_id, Group, GroupInDB)
|
||||
self.group_tokens = BaseAccessModel("token", GroupInviteToken, ReadInviteToken)
|
||||
self.meals = BaseAccessModel(pk_id, MealPlan, MealPlanOut)
|
||||
self.meals = MealDataAccessModel(pk_id, GroupMealPlan, ReadPlanEntry)
|
||||
self.webhooks = BaseAccessModel(pk_id, GroupWebhooksModel, ReadWebhook)
|
||||
self.shopping_lists = BaseAccessModel(pk_id, ShoppingList, ShoppingListOut)
|
||||
self.cookbooks = BaseAccessModel(pk_id, CookBook, ReadCookBook)
|
||||
|
||||
26
mealie/db/data_access_layer/meal_access_model.py
Normal file
26
mealie/db/data_access_layer/meal_access_model.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from datetime import date
|
||||
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from mealie.db.models.group import GroupMealPlan
|
||||
from mealie.schema.meal_plan.new_meal import ReadPlanEntry
|
||||
|
||||
from ._base_access_model import BaseAccessModel
|
||||
|
||||
|
||||
class MealDataAccessModel(BaseAccessModel[ReadPlanEntry, GroupMealPlan]):
|
||||
def get_slice(self, session: Session, start: date, end: date, group_id: int) -> list[ReadPlanEntry]:
|
||||
start = start.strftime("%Y-%m-%d")
|
||||
end = end.strftime("%Y-%m-%d")
|
||||
qry = session.query(GroupMealPlan).filter(
|
||||
GroupMealPlan.date.between(start, end),
|
||||
GroupMealPlan.group_id == group_id,
|
||||
)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
|
||||
def get_today(self, session: Session, group_id: int) -> list[ReadPlanEntry]:
|
||||
today = date.today()
|
||||
qry = session.query(GroupMealPlan).filter(GroupMealPlan.date == today, GroupMealPlan.group_id == group_id)
|
||||
|
||||
return [self.schema.from_orm(x) for x in qry.all()]
|
||||
@@ -10,7 +10,7 @@ from mealie.db.models._model_base import SqlAlchemyBase
|
||||
from mealie.schema.admin import SiteSettings
|
||||
from mealie.schema.user.user import GroupBase
|
||||
from mealie.services.events import create_general_event
|
||||
from mealie.services.group_services.group_mixins import create_new_group
|
||||
from mealie.services.group_services.group_utils import create_new_group
|
||||
|
||||
logger = root_logger.get_logger("init_db")
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from .event import *
|
||||
from .group import *
|
||||
from .mealplan import *
|
||||
from .recipe.recipe import *
|
||||
from .settings import *
|
||||
from .sign_up import *
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from .cookbook import *
|
||||
from .group import *
|
||||
from .invite_tokens import *
|
||||
from .mealplan import *
|
||||
from .preferences import *
|
||||
from .shopping_list import *
|
||||
from .webhooks import *
|
||||
|
||||
@@ -10,6 +10,7 @@ from .._model_utils import auto_init
|
||||
from ..group.webhooks import GroupWebhooksModel
|
||||
from ..recipe.category import Category, group2categories
|
||||
from .cookbook import CookBook
|
||||
from .mealplan import GroupMealPlan
|
||||
from .preferences import GroupPreferencesModel
|
||||
|
||||
|
||||
@@ -34,12 +35,14 @@ class Group(SqlAlchemyBase, BaseMixins):
|
||||
recipes = orm.relationship("RecipeModel", back_populates="group", uselist=True)
|
||||
|
||||
# CRUD From Others
|
||||
mealplans = orm.relationship("MealPlan", back_populates="group", single_parent=True, order_by="MealPlan.start_date")
|
||||
mealplans = orm.relationship(
|
||||
GroupMealPlan, back_populates="group", single_parent=True, order_by="GroupMealPlan.date"
|
||||
)
|
||||
webhooks = orm.relationship(GroupWebhooksModel, uselist=True, cascade="all, delete-orphan")
|
||||
cookbooks = orm.relationship(CookBook, back_populates="group", single_parent=True)
|
||||
shopping_lists = orm.relationship("ShoppingList", back_populates="group", single_parent=True)
|
||||
|
||||
@auto_init({"users", "webhooks", "shopping_lists", "cookbooks", "preferences", "invite_tokens"})
|
||||
@auto_init({"users", "webhooks", "shopping_lists", "cookbooks", "preferences", "invite_tokens", "mealplans"})
|
||||
def __init__(self, **_) -> None:
|
||||
pass
|
||||
|
||||
|
||||
24
mealie/db/models/group/mealplan.py
Normal file
24
mealie/db/models/group/mealplan.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from sqlalchemy import Column, Date, ForeignKey, String, orm
|
||||
from sqlalchemy.sql.sqltypes import Integer
|
||||
|
||||
from .._model_base import BaseMixins, SqlAlchemyBase
|
||||
from .._model_utils import auto_init
|
||||
|
||||
|
||||
class GroupMealPlan(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "group_meal_plans"
|
||||
|
||||
date = Column(Date, index=True, nullable=False)
|
||||
entry_type = Column(String, index=True, nullable=False)
|
||||
title = Column(String, index=True, nullable=False)
|
||||
text = Column(String, nullable=False)
|
||||
|
||||
group_id = Column(Integer, ForeignKey("groups.id"), index=True)
|
||||
group = orm.relationship("Group", back_populates="mealplans")
|
||||
|
||||
recipe_id = Column(Integer, ForeignKey("recipes.id"))
|
||||
recipe = orm.relationship("RecipeModel", back_populates="meal_entries", uselist=False)
|
||||
|
||||
@auto_init()
|
||||
def __init__(self, **_) -> None:
|
||||
pass
|
||||
@@ -1,82 +0,0 @@
|
||||
import sqlalchemy.orm as orm
|
||||
from sqlalchemy import Column, Date, ForeignKey, Integer, String
|
||||
from sqlalchemy.ext.orderinglist import ordering_list
|
||||
|
||||
from mealie.db.models._model_base import BaseMixins, SqlAlchemyBase
|
||||
from mealie.db.models.group import Group
|
||||
from mealie.db.models.recipe.recipe import RecipeModel
|
||||
|
||||
from .group.shopping_list import ShoppingList
|
||||
|
||||
|
||||
class Meal(SqlAlchemyBase):
|
||||
__tablename__ = "meal"
|
||||
id = Column(Integer, primary_key=True)
|
||||
parent_id = Column(Integer, ForeignKey("mealdays.id"))
|
||||
position = Column(Integer)
|
||||
name = Column(String)
|
||||
slug = Column(String)
|
||||
description = Column(String)
|
||||
|
||||
def __init__(self, slug, name="", description="", session=None) -> None:
|
||||
|
||||
if slug and slug != "":
|
||||
recipe: RecipeModel = session.query(RecipeModel).filter(RecipeModel.slug == slug).one_or_none()
|
||||
|
||||
if recipe:
|
||||
name = recipe.name
|
||||
self.slug = recipe.slug
|
||||
description = recipe.description
|
||||
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
||||
|
||||
class MealDay(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "mealdays"
|
||||
id = Column(Integer, primary_key=True)
|
||||
parent_id = Column(Integer, ForeignKey("mealplan.id"))
|
||||
date = Column(Date)
|
||||
meals: list[Meal] = orm.relationship(
|
||||
Meal,
|
||||
cascade="all, delete, delete-orphan",
|
||||
order_by="Meal.position",
|
||||
collection_class=ordering_list("position"),
|
||||
)
|
||||
|
||||
def __init__(self, date, meals: list, session=None):
|
||||
self.date = date
|
||||
self.meals = [Meal(**m, session=session) for m in meals]
|
||||
|
||||
|
||||
class MealPlan(SqlAlchemyBase, BaseMixins):
|
||||
__tablename__ = "mealplan"
|
||||
# TODO: Migrate to use ID as PK
|
||||
start_date = Column(Date)
|
||||
end_date = Column(Date)
|
||||
plan_days: list[MealDay] = orm.relationship(MealDay, cascade="all, delete, delete-orphan")
|
||||
|
||||
group_id = Column(Integer, ForeignKey("groups.id"))
|
||||
group = orm.relationship("Group", back_populates="mealplans")
|
||||
|
||||
shopping_list_id = Column(Integer, ForeignKey("shopping_lists.id"))
|
||||
shopping_list: ShoppingList = orm.relationship("ShoppingList", single_parent=True)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
start_date,
|
||||
end_date,
|
||||
plan_days,
|
||||
group: str,
|
||||
shopping_list: int = None,
|
||||
session=None,
|
||||
**_,
|
||||
) -> None:
|
||||
self.start_date = start_date
|
||||
self.end_date = end_date
|
||||
self.group = Group.get_ref(session, group)
|
||||
|
||||
if shopping_list:
|
||||
self.shopping_list = ShoppingList.get_ref(session, shopping_list)
|
||||
|
||||
self.plan_days = [MealDay(**day, session=session) for day in plan_days]
|
||||
@@ -34,7 +34,9 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
||||
user_id = sa.Column(sa.Integer, sa.ForeignKey("users.id"))
|
||||
user = orm.relationship("User", uselist=False, foreign_keys=[user_id])
|
||||
|
||||
favorited_by: list = orm.relationship("User", secondary=users_to_favorites, back_populates="favorite_recipes")
|
||||
meal_entries = orm.relationship("GroupMealPlan", back_populates="recipe")
|
||||
|
||||
favorited_by = orm.relationship("User", secondary=users_to_favorites, back_populates="favorite_recipes")
|
||||
|
||||
# General Recipe Properties
|
||||
name = sa.Column(sa.String, nullable=False)
|
||||
|
||||
Reference in New Issue
Block a user