mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-12-27 12:45:17 -05:00
Feature/automated meal planner (#939)
* cleanup oversized buttons * add get all by category function to reciep repos * fix shopping-list can_merge logic * use randomized data for testing * add random getter to repository for meal-planner * add stub route for random meals * cleanup global namespace * add rules database type * fix type * add plan rules schema * test plan rules methods * add mealplan rules controller * add new repository * update frontend types * formatting * fix regression * update autogenerated types * add api class for mealplan rules * add tests and fix bugs * fix data returns * proof of concept rules editor * add tag support * remove old group categories * add tag support * implement random by rules api * change snack to sides * remove incorrect typing * split repo for custom methods * fix query and use and_ clause * use repo function * remove old test * update changelog
This commit is contained in:
@@ -12,6 +12,7 @@ router = APIRouter(prefix="/categories", tags=["Categories: CRUD"])
|
||||
|
||||
|
||||
class CategorySummary(BaseModel):
|
||||
id: int
|
||||
slug: str
|
||||
name: str
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ from . import (
|
||||
controller_invitations,
|
||||
controller_labels,
|
||||
controller_mealplan,
|
||||
controller_meaplan_config,
|
||||
controller_mealplan_config,
|
||||
controller_mealplan_rules,
|
||||
controller_migrations,
|
||||
controller_shopping_lists,
|
||||
controller_webhooks,
|
||||
@@ -17,9 +18,10 @@ from . import (
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(controller_group_self_service.router)
|
||||
router.include_router(controller_mealplan_rules.router)
|
||||
router.include_router(controller_mealplan_config.router)
|
||||
router.include_router(controller_mealplan.router)
|
||||
router.include_router(controller_cookbooks.router)
|
||||
router.include_router(controller_meaplan_config.router)
|
||||
router.include_router(controller_webhooks.router)
|
||||
router.include_router(controller_invitations.router)
|
||||
router.include_router(controller_migrations.router)
|
||||
|
||||
@@ -2,7 +2,7 @@ from datetime import date, timedelta
|
||||
from functools import cached_property
|
||||
from typing import Type
|
||||
|
||||
from fastapi import APIRouter
|
||||
from fastapi import APIRouter, HTTPException
|
||||
|
||||
from mealie.core.exceptions import mealie_registered_exceptions
|
||||
from mealie.repos.repository_meals import RepositoryMeals
|
||||
@@ -10,6 +10,10 @@ from mealie.routes._base import BaseUserController, controller
|
||||
from mealie.routes._base.mixins import CrudMixins
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.meal_plan import CreatePlanEntry, ReadPlanEntry, SavePlanEntry, UpdatePlanEntry
|
||||
from mealie.schema.meal_plan.new_meal import CreatRandomEntry
|
||||
from mealie.schema.meal_plan.plan_rules import PlanRulesDay
|
||||
from mealie.schema.recipe.recipe import Recipe
|
||||
from mealie.schema.response.responses import ErrorResponse
|
||||
|
||||
router = APIRouter(prefix="/groups/mealplans", tags=["Groups: Mealplans"])
|
||||
|
||||
@@ -34,10 +38,54 @@ class GroupMealplanController(BaseUserController):
|
||||
self.registered_exceptions,
|
||||
)
|
||||
|
||||
@router.get("/today", tags=["Groups: Mealplans"])
|
||||
@router.get("/today")
|
||||
def get_todays_meals(self):
|
||||
return self.repo.get_today(group_id=self.group_id)
|
||||
|
||||
@router.post("/random", response_model=ReadPlanEntry)
|
||||
def create_random_meal(self, data: CreatRandomEntry):
|
||||
"""
|
||||
create_random_meal is a route that provides the randomized funcitonality for mealplaners.
|
||||
It operates by following the rules setout in the Groups mealplan settings. If not settings
|
||||
are set, it will default return any random meal.
|
||||
|
||||
Refer to the mealplan settings routes for more information on how rules can be applied
|
||||
to the random meal selector.
|
||||
"""
|
||||
# Get relavent group rules
|
||||
rules = self.repos.group_meal_plan_rules.by_group(self.group_id).get_rules(
|
||||
PlanRulesDay.from_date(data.date), data.entry_type.value
|
||||
)
|
||||
|
||||
recipe_repo = self.repos.recipes.by_group(self.group_id)
|
||||
random_recipes: Recipe = []
|
||||
|
||||
if not rules: # If no rules are set, return any random recipe from the group
|
||||
random_recipes = recipe_repo.get_random()
|
||||
else: # otherwise construct a query based on the rules
|
||||
tags = []
|
||||
categories = []
|
||||
for rule in rules:
|
||||
if rule.tags:
|
||||
tags.extend(rule.tags)
|
||||
if rule.categories:
|
||||
categories.extend(rule.categories)
|
||||
|
||||
if tags or categories:
|
||||
random_recipes = self.repos.recipes.by_group(self.group_id).get_random_by_categories_and_tags(
|
||||
categories, tags
|
||||
)
|
||||
else:
|
||||
random_recipes = recipe_repo.get_random()
|
||||
|
||||
try:
|
||||
recipe = random_recipes[0]
|
||||
return self.mixins.create_one(
|
||||
SavePlanEntry(date=data.date, entry_type=data.entry_type, recipe_id=recipe.id, group_id=self.group_id)
|
||||
)
|
||||
except IndexError:
|
||||
raise HTTPException(status_code=404, detail=ErrorResponse.respond(message="No recipes match your rules"))
|
||||
|
||||
@router.get("", response_model=list[ReadPlanEntry])
|
||||
def get_all(self, start: date = None, limit: date = None):
|
||||
start = start or date.today() - timedelta(days=999)
|
||||
|
||||
44
mealie/routes/groups/controller_mealplan_rules.py
Normal file
44
mealie/routes/groups/controller_mealplan_rules.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from functools import cached_property
|
||||
|
||||
from pydantic import UUID4
|
||||
|
||||
from mealie.routes._base.abc_controller import BaseUserController
|
||||
from mealie.routes._base.controller import controller
|
||||
from mealie.routes._base.mixins import CrudMixins
|
||||
from mealie.routes._base.routers import UserAPIRouter
|
||||
from mealie.schema import mapper
|
||||
from mealie.schema.meal_plan.plan_rules import PlanRulesCreate, PlanRulesOut, PlanRulesSave
|
||||
|
||||
router = UserAPIRouter(prefix="/groups/mealplans/rules", tags=["Groups: Mealplan Rules"])
|
||||
|
||||
|
||||
@controller(router)
|
||||
class GroupMealplanConfigController(BaseUserController):
|
||||
@cached_property
|
||||
def repo(self):
|
||||
return self.repos.group_meal_plan_rules.by_group(self.group_id)
|
||||
|
||||
@cached_property
|
||||
def mixins(self):
|
||||
return CrudMixins[PlanRulesCreate, PlanRulesOut, PlanRulesOut](self.repo, self.deps.logger)
|
||||
|
||||
@router.get("", response_model=list[PlanRulesOut])
|
||||
def get_all(self):
|
||||
return self.repo.get_all(override_schema=PlanRulesOut)
|
||||
|
||||
@router.post("", response_model=PlanRulesOut, status_code=201)
|
||||
def create_one(self, data: PlanRulesCreate):
|
||||
save = mapper.cast(data, PlanRulesSave, group_id=self.group.id)
|
||||
return self.mixins.create_one(save)
|
||||
|
||||
@router.get("/{item_id}", response_model=PlanRulesOut)
|
||||
def get_one(self, item_id: UUID4):
|
||||
return self.mixins.get_one(item_id)
|
||||
|
||||
@router.put("/{item_id}", response_model=PlanRulesOut)
|
||||
def update_one(self, item_id: UUID4, data: PlanRulesCreate):
|
||||
return self.mixins.update_one(data, item_id)
|
||||
|
||||
@router.delete("/{item_id}", response_model=PlanRulesOut)
|
||||
def delete_one(self, item_id: UUID4):
|
||||
return self.mixins.delete_one(item_id) # type: ignore
|
||||
Reference in New Issue
Block a user