mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	Add Database Layer for Recipe Scaling (#506)
* move badge * fix add individual ingredient * fix redirect issue Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
		| @@ -31,6 +31,7 @@ const apiReq = { | ||||
|   post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) { | ||||
|     const response = await axios.post(url, data).catch(function(error) { | ||||
|       handleError(error, getErrorText); | ||||
|       return error; | ||||
|     }); | ||||
|     return handleResponse(response, getSuccessText); | ||||
|   }, | ||||
| @@ -38,6 +39,7 @@ const apiReq = { | ||||
|   put: async function(url, data, getErrorText = defaultErrorText, getSuccessText) { | ||||
|     const response = await axios.put(url, data).catch(function(error) { | ||||
|       handleError(error, getErrorText); | ||||
|       return error; | ||||
|     }); | ||||
|     return handleResponse(response, getSuccessText); | ||||
|   }, | ||||
| @@ -45,6 +47,7 @@ const apiReq = { | ||||
|   patch: async function(url, data, getErrorText = defaultErrorText, getSuccessText) { | ||||
|     const response = await axios.patch(url, data).catch(function(error) { | ||||
|       handleError(error, getErrorText); | ||||
|       return error; | ||||
|     }); | ||||
|     return handleResponse(response, getSuccessText); | ||||
|   }, | ||||
| @@ -52,12 +55,14 @@ const apiReq = { | ||||
|   get: async function(url, data, getErrorText = defaultErrorText) { | ||||
|     return axios.get(url, data).catch(function(error) { | ||||
|       handleError(error, getErrorText); | ||||
|       return error; | ||||
|     }); | ||||
|   }, | ||||
|  | ||||
|   delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText) { | ||||
|     const response = await axios.delete(url, data).catch(function(error) { | ||||
|       handleError(error, getErrorText); | ||||
|       return error; | ||||
|     }); | ||||
|     return handleResponse(response, getSuccessText); | ||||
|   }, | ||||
|   | ||||
| @@ -35,9 +35,11 @@ export const recipeAPI = { | ||||
|   }, | ||||
|  | ||||
|   async requestDetails(recipeSlug) { | ||||
|     let response = await apiReq.get(API_ROUTES.recipesRecipeSlug(recipeSlug)); | ||||
|     if (response && response.data) return response.data; | ||||
|     else return null; | ||||
|     const response = await apiReq.get(API_ROUTES.recipesRecipeSlug(recipeSlug)); | ||||
|     if (response.response) { | ||||
|       return response.response; | ||||
|     } | ||||
|     return response; | ||||
|   }, | ||||
|  | ||||
|   updateImage(recipeSlug, fileObject, overrideSuccessMsg = false) { | ||||
|   | ||||
| @@ -16,26 +16,14 @@ | ||||
|       :top="menuTop" | ||||
|       :nudge-top="menuTop ? '5' : '0'" | ||||
|       allow-overflow | ||||
|       close-delay="125" | ||||
|       open-on-hover | ||||
|     > | ||||
|       <template v-slot:activator="{ on: onMenu, attrs: attrsMenu }"> | ||||
|         <v-tooltip bottom dark :color="color"> | ||||
|           <template v-slot:activator="{ on: onTooltip, attrs: attrsTooltip }"> | ||||
|             <v-btn | ||||
|               :fab="fab" | ||||
|               :small="fab" | ||||
|               :color="color" | ||||
|               :icon="!fab" | ||||
|               dark | ||||
|               v-bind="{ ...attrsMenu, ...attrsTooltip }" | ||||
|               v-on="{ ...onMenu, ...onTooltip }" | ||||
|               @click.prevent | ||||
|             > | ||||
|       <template v-slot:activator="{ on, attrs }"> | ||||
|         <v-btn :fab="fab" :small="fab" :color="color" :icon="!fab" dark v-bind="attrs" v-on="on" @click.prevent> | ||||
|           <v-icon>{{ menuIcon }}</v-icon> | ||||
|         </v-btn> | ||||
|       </template> | ||||
|           <span>{{ $t("general.more") }}</span> | ||||
|         </v-tooltip> | ||||
|       </template> | ||||
|       <v-list dense> | ||||
|         <v-list-item | ||||
|           v-for="(item, index) in loggedIn && cardMenu ? userMenu : defaultMenu" | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <v-tooltip right :color="buttonStyle ? 'primary' : 'secondary'"> | ||||
|   <v-tooltip bottom nudge-right="50" :color="buttonStyle ? 'primary' : 'secondary'"> | ||||
|     <template v-slot:activator="{ on, attrs }"> | ||||
|       <v-btn | ||||
|         small | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <template> | ||||
|   <div> | ||||
|   <div v-if="value && value.length > 0"> | ||||
|     <h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2> | ||||
|     <div v-if="edit"> | ||||
|       <draggable :value="value" @input="updateIndex" @start="drag = true" @end="drag = false" handle=".handle"> | ||||
| @@ -9,7 +9,7 @@ | ||||
|               <v-textarea | ||||
|                 class="mr-2" | ||||
|                 :label="$t('recipe.ingredient')" | ||||
|                 v-model="value[index]" | ||||
|                 v-model="value[index].note" | ||||
|                 mdi-move-resize | ||||
|                 auto-grow | ||||
|                 solo | ||||
| @@ -45,7 +45,7 @@ | ||||
|         <v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary"> </v-checkbox> | ||||
|  | ||||
|         <v-list-item-content> | ||||
|           <vue-markdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredient"> </vue-markdown> | ||||
|           <vue-markdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredient.note"> </vue-markdown> | ||||
|         </v-list-item-content> | ||||
|       </v-list-item> | ||||
|     </div> | ||||
| @@ -85,9 +85,26 @@ export default { | ||||
|   methods: { | ||||
|     addIngredient(ingredients = null) { | ||||
|       if (ingredients.length) { | ||||
|         this.value.push(...ingredients); | ||||
|         const newIngredients = ingredients.map(x => { | ||||
|           return { | ||||
|             title: null, | ||||
|             note: x, | ||||
|             unit: null, | ||||
|             food: null, | ||||
|             disableAmount: true, | ||||
|             quantity: 1, | ||||
|           }; | ||||
|         }); | ||||
|         this.value.push(...newIngredients); | ||||
|       } else { | ||||
|         this.value.push(""); | ||||
|         this.value.push({ | ||||
|           title: null, | ||||
|           note: "", | ||||
|           unit: null, | ||||
|           food: null, | ||||
|           disableAmount: true, | ||||
|           quantity: 1, | ||||
|         }); | ||||
|       } | ||||
|     }, | ||||
|     generateKey(item, index) { | ||||
|   | ||||
| @@ -192,8 +192,12 @@ export default { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       this.recipeDetails = await api.recipes.requestDetails(this.currentRecipe); | ||||
|       if (!this.recipeDetails) router.push(`/login`); | ||||
|       const response = await api.recipes.requestDetails(this.currentRecipe); | ||||
|       console.log("View Response", { response }); | ||||
|       if (response.status === 401) router.push(`/login`); | ||||
|       if (response.status === 404) return; | ||||
|  | ||||
|       this.recipeDetails = response.data; | ||||
|       this.skeleton = false; | ||||
|     }, | ||||
|     getImage(slug) { | ||||
|   | ||||
| @@ -5,18 +5,19 @@ | ||||
|         <v-icon left> | ||||
|           mdi-arrow-left-bold | ||||
|         </v-icon> | ||||
|         {{$t('shopping-list.all-lists')}} | ||||
|         {{ $t("shopping-list.all-lists") }} | ||||
|       </v-btn> | ||||
|       <v-icon v-if="!list" large left> | ||||
|         mdi-format-list-checks | ||||
|       </v-icon> | ||||
|       <v-toolbar-title v-if="!list" class="headline"> {{$t('shopping-list.shopping-lists')}} </v-toolbar-title> | ||||
|       <v-toolbar-title v-if="!list" class="headline"> {{ $t("shopping-list.shopping-lists") }} </v-toolbar-title> | ||||
|       <v-spacer></v-spacer> | ||||
|       <BaseDialog | ||||
|         :title="$t('shopping-list.new-list')" | ||||
|         title-icon="mdi-format-list-checks" | ||||
|         :submit-text="$t('general.create')" | ||||
|         @submit="createNewList"> | ||||
|         @submit="createNewList" | ||||
|       > | ||||
|         <template v-slot:open="{ open }"> | ||||
|           <TheButton create @click="open" /> | ||||
|         </template> | ||||
| @@ -41,7 +42,7 @@ | ||||
|                 <v-icon left> | ||||
|                   mdi-cart-check | ||||
|                 </v-icon> | ||||
|                 {{$t('general.view')}} | ||||
|                 {{ $t("general.view") }} | ||||
|               </v-btn> | ||||
|             </v-card-actions> | ||||
|           </v-card> | ||||
| @@ -65,7 +66,7 @@ | ||||
|         <v-card-text> | ||||
|           <v-row dense v-for="(item, index) in activeList.items" :key="index"> | ||||
|             <v-col v-if="edit" cols="12" class="d-flex no-wrap align-center"> | ||||
|               <p class="mb-0">{{$t('shopping-list.quantity', [item.quantity])}}</p> | ||||
|               <p class="mb-0">{{ $t("shopping-list.quantity", [item.quantity]) }}</p> | ||||
|               <div v-if="edit"> | ||||
|                 <v-btn x-small text class="ml-1" @click="activeList.items[index].quantity -= 1"> | ||||
|                   <v-icon> | ||||
| @@ -123,13 +124,13 @@ | ||||
|             <v-icon left> | ||||
|               {{ $globals.icons.primary }} | ||||
|             </v-icon> | ||||
|             {{$t('shopping-list.from-recipe')}} | ||||
|             {{ $t("shopping-list.from-recipe") }} | ||||
|           </v-btn> | ||||
|           <v-btn v-if="edit" color="success" @click="newItem"> | ||||
|             <v-icon left> | ||||
|               {{ $globals.icons.create }} | ||||
|             </v-icon> | ||||
|             {{$t('general.new')}} | ||||
|             {{ $t("general.new") }} | ||||
|           </v-btn> | ||||
|         </v-card-actions> | ||||
|       </v-card> | ||||
| @@ -197,7 +198,8 @@ export default { | ||||
|       this.$refs.searchRecipe.open(); | ||||
|     }, | ||||
|     async importIngredients(selected) { | ||||
|       const recipe = await api.recipes.requestDetails(selected.slug); | ||||
|       const response = await api.recipes.requestDetails(selected.slug); | ||||
|       const recipe = response.data; | ||||
|  | ||||
|       const ingredients = recipe.recipeIngredient.map(x => ({ | ||||
|         title: "", | ||||
|   | ||||
| @@ -26,7 +26,8 @@ export const recipeRoutes = [ | ||||
|     component: ViewRecipe, | ||||
|     meta: { | ||||
|       title: async route => { | ||||
|         const recipe = await api.recipes.requestDetails(route.params.recipe); | ||||
|         const response = await api.recipes.requestDetails(route.params.recipe); | ||||
|         const recipe = response.data; | ||||
|         if (recipe && recipe.name) return recipe.name; | ||||
|         else return null; | ||||
|       }, | ||||
|   | ||||
							
								
								
									
										2
									
								
								makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								makefile
									
									
									
									
									
								
							| @@ -23,7 +23,7 @@ BROWSER := python -c "$$BROWSER_PYSCRIPT" | ||||
| help: | ||||
| 	@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) | ||||
|  | ||||
| clean-purge: clean ## ⚠️  Removes All Developer Data for a fresh server start | ||||
| purge: clean ## ⚠️  Removes All Developer Data for a fresh server start | ||||
| 	rm -r ./dev/data/recipes/ | ||||
| 	rm -r ./dev/data/users/ | ||||
| 	rm -f ./dev/data/mealie_v*.db | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from mealie.db.models.event import Event, EventNotification | ||||
| from mealie.db.models.group import Group | ||||
| from mealie.db.models.mealplan import MealPlan | ||||
| from mealie.db.models.recipe.comment import RecipeComment | ||||
| from mealie.db.models.recipe.ingredient import IngredientFood, IngredientUnit | ||||
| from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag | ||||
| from mealie.db.models.recipe.settings import RecipeSettings | ||||
| from mealie.db.models.settings import CustomPage, SiteSettings | ||||
| @@ -18,7 +19,8 @@ from mealie.schema.comments import CommentOut | ||||
| from mealie.schema.event_notifications import EventNotificationIn | ||||
| from mealie.schema.events import Event as EventSchema | ||||
| from mealie.schema.meal import MealPlanOut | ||||
| from mealie.schema.recipe import Recipe | ||||
| from mealie.schema.recipe import (Recipe, RecipeIngredientFood, | ||||
|                                   RecipeIngredientUnit) | ||||
| from mealie.schema.settings import CustomPageOut | ||||
| from mealie.schema.settings import SiteSettings as SiteSettingsSchema | ||||
| from mealie.schema.shopping_list import ShoppingListOut | ||||
| @@ -87,6 +89,20 @@ class _Recipes(BaseDocument): | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class _IngredientFoods(BaseDocument): | ||||
|     def __init__(self) -> None: | ||||
|         self.primary_key = "id" | ||||
|         self.sql_model = IngredientFood | ||||
|         self.schema = RecipeIngredientFood | ||||
|  | ||||
|  | ||||
| class _IngredientUnits(BaseDocument): | ||||
|     def __init__(self) -> None: | ||||
|         self.primary_key = "id" | ||||
|         self.sql_model = IngredientUnit | ||||
|         self.schema = RecipeIngredientUnit | ||||
|  | ||||
|  | ||||
| class _Categories(BaseDocument): | ||||
|     def __init__(self) -> None: | ||||
|         self.primary_key = "slug" | ||||
| @@ -215,21 +231,28 @@ class _EventNotification(BaseDocument): | ||||
|  | ||||
| class Database: | ||||
|     def __init__(self) -> None: | ||||
|         # Recipes | ||||
|         self.recipes = _Recipes() | ||||
|         self.meals = _Meals() | ||||
|         self.settings = _Settings() | ||||
|         self.themes = _Themes() | ||||
|         self.ingredient_foods = _IngredientUnits() | ||||
|         self.ingredient_units = _IngredientFoods() | ||||
|         self.categories = _Categories() | ||||
|         self.tags = _Tags() | ||||
|         self.comments = _Comments() | ||||
|  | ||||
|         # Site | ||||
|         self.settings = _Settings() | ||||
|         self.themes = _Themes() | ||||
|         self.sign_ups = _SignUps() | ||||
|         self.custom_pages = _CustomPages() | ||||
|         self.event_notifications = _EventNotification() | ||||
|         self.events = _Events() | ||||
|  | ||||
|         # Users / Groups | ||||
|         self.users = _Users() | ||||
|         self.api_tokens = _LongLiveToken() | ||||
|         self.sign_ups = _SignUps() | ||||
|         self.groups = _Groups() | ||||
|         self.custom_pages = _CustomPages() | ||||
|         self.events = _Events() | ||||
|         self.event_notifications = _EventNotification() | ||||
|         self.meals = _Meals() | ||||
|         self.shopping_lists = _ShoppingList() | ||||
|         self.comments = _Comments() | ||||
|  | ||||
|  | ||||
| db = Database() | ||||
|   | ||||
| @@ -1,5 +1,70 @@ | ||||
| from mealie.db.models.model_base import SqlAlchemyBase | ||||
| from sqlalchemy import Column, ForeignKey, Integer, String | ||||
| from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase | ||||
| from requests import Session | ||||
| from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, Table, orm | ||||
|  | ||||
| ingredients_to_units = Table( | ||||
|     "ingredients_to_units", | ||||
|     SqlAlchemyBase.metadata, | ||||
|     Column("ingredient_units.id", Integer, ForeignKey("ingredient_units.id")), | ||||
|     Column("recipes_ingredients_id", Integer, ForeignKey("recipes_ingredients.id")), | ||||
| ) | ||||
|  | ||||
| ingredients_to_foods = Table( | ||||
|     "ingredients_to_foods", | ||||
|     SqlAlchemyBase.metadata, | ||||
|     Column("ingredient_foods.id", Integer, ForeignKey("ingredient_foods.id")), | ||||
|     Column("recipes_ingredients_id", Integer, ForeignKey("recipes_ingredients.id")), | ||||
| ) | ||||
|  | ||||
|  | ||||
| class IngredientUnit(SqlAlchemyBase, BaseMixins): | ||||
|     __tablename__ = "ingredient_units" | ||||
|     id = Column(Integer, primary_key=True) | ||||
|     name = Column(String) | ||||
|     description = Column(String) | ||||
|     ingredients = orm.relationship("RecipeIngredient", secondary=ingredients_to_units, back_populates="unit") | ||||
|  | ||||
|     def __init__(self, name: str, description: str = None) -> None: | ||||
|         self.name = name | ||||
|         self.description = description | ||||
|  | ||||
|     @classmethod | ||||
|     def get_ref_or_create(cls, session: Session, obj: dict): | ||||
|         # sourcery skip: flip-comparison | ||||
|         if obj is None: | ||||
|             return None | ||||
|  | ||||
|         name = obj.get("name") | ||||
|  | ||||
|         unit = session.query(cls).filter("name" == name).one_or_none() | ||||
|  | ||||
|         if not unit: | ||||
|             return cls(**obj) | ||||
|  | ||||
|  | ||||
| class IngredientFood(SqlAlchemyBase, BaseMixins): | ||||
|     __tablename__ = "ingredient_foods" | ||||
|     id = Column(Integer, primary_key=True) | ||||
|     name = Column(String) | ||||
|     description = Column(String) | ||||
|     ingredients = orm.relationship("RecipeIngredient", secondary=ingredients_to_foods, back_populates="food") | ||||
|  | ||||
|     def __init__(self, name: str, description: str = None) -> None: | ||||
|         self.name = name | ||||
|         self.description = description | ||||
|  | ||||
|     @classmethod | ||||
|     def get_ref_or_create(cls, session: Session, obj: dict): | ||||
|         # sourcery skip: flip-comparison | ||||
|         if obj is None: | ||||
|             return None | ||||
|  | ||||
|         name = obj.get("name") | ||||
|  | ||||
|         unit = session.query(cls).filter("name" == name).one_or_none() | ||||
|  | ||||
|         if not unit: | ||||
|             return cls(**obj) | ||||
|  | ||||
|  | ||||
| class RecipeIngredient(SqlAlchemyBase): | ||||
| @@ -7,8 +72,24 @@ class RecipeIngredient(SqlAlchemyBase): | ||||
|     id = Column(Integer, primary_key=True) | ||||
|     position = Column(Integer) | ||||
|     parent_id = Column(Integer, ForeignKey("recipes.id")) | ||||
|     # title = Column(String) | ||||
|     ingredient = Column(String) | ||||
|  | ||||
|     def update(self, ingredient): | ||||
|         self.ingredient = ingredient | ||||
|     title = Column(String)  # Section Header - Shows if Present | ||||
|     note = Column(String)  # Force Show Text - Overrides Concat | ||||
|  | ||||
|     # Scaling Items | ||||
|     unit = orm.relationship(IngredientUnit, secondary=ingredients_to_units, uselist=False) | ||||
|     food = orm.relationship(IngredientFood, secondary=ingredients_to_foods, uselist=False) | ||||
|     quantity = Column(Integer) | ||||
|  | ||||
|     # Extras | ||||
|     disable_amount = Column(Boolean, default=False) | ||||
|  | ||||
|     def __init__( | ||||
|         self, title: str, note: str, unit: dict, food: dict, quantity: int, disable_amount: bool, session: Session, **_ | ||||
|     ) -> None: | ||||
|         self.title = title | ||||
|         self.note = note | ||||
|         self.unit = IngredientUnit.get_ref_or_create(session, unit) | ||||
|         self.food = IngredientFood.get_ref_or_create(session, food) | ||||
|         self.quantity = quantity | ||||
|         self.disable_amount = disable_amount | ||||
|   | ||||
| @@ -117,7 +117,7 @@ class RecipeModel(SqlAlchemyBase, BaseMixins): | ||||
|         self.tools = [Tool(tool=x) for x in tools] if tools else [] | ||||
|  | ||||
|         self.recipe_yield = recipe_yield | ||||
|         self.recipe_ingredient = [RecipeIngredient(ingredient=ingr) for ingr in recipe_ingredient] | ||||
|         self.recipe_ingredient = [RecipeIngredient(**ingr, session=session) for ingr in recipe_ingredient] | ||||
|         self.assets = [RecipeAsset(**a) for a in assets] | ||||
|         self.recipe_instructions = [ | ||||
|             RecipeInstruction(text=instruc.get("text"), title=instruc.get("title"), type=instruc.get("@type", None)) | ||||
|   | ||||
| @@ -37,7 +37,7 @@ def get_shopping_list( | ||||
|                 logger.error("Recipe Not Found") | ||||
|  | ||||
|     new_list = ShoppingListIn( | ||||
|         name="MealPlan Shopping List", group=current_user.group, items=[ListItem(text=t) for t in all_ingredients] | ||||
|         name="MealPlan Shopping List", group=current_user.group, items=[ListItem(text=t.note) for t in all_ingredients] | ||||
|     ) | ||||
|  | ||||
|     created_list: ShoppingListOut = db.shopping_lists.create(session, new_list) | ||||
|   | ||||
| @@ -76,6 +76,9 @@ def get_recipe(recipe_slug: str, session: Session = Depends(generate_session), i | ||||
|  | ||||
|     recipe: Recipe = db.recipes.get(session, recipe_slug) | ||||
|  | ||||
|     if not recipe: | ||||
|         raise HTTPException(status.HTTP_404_NOT_FOUND) | ||||
|  | ||||
|     if recipe.settings.public or is_user: | ||||
|  | ||||
|         return recipe | ||||
|   | ||||
							
								
								
									
										8
									
								
								mealie/routes/unit_and_foods/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								mealie/routes/unit_and_foods/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| from fastapi import APIRouter | ||||
|  | ||||
| from . import food_routes, unit_routes | ||||
|  | ||||
| units_and_foods_router = APIRouter(tags=["Food and Units"]) | ||||
|  | ||||
| units_and_foods_router.include_router(food_routes.router) | ||||
| units_and_foods_router.include_router(unit_routes.router) | ||||
							
								
								
									
										34
									
								
								mealie/routes/unit_and_foods/food_routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								mealie/routes/unit_and_foods/food_routes.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| from fastapi import APIRouter, Depends | ||||
| from mealie.core.root_logger import get_logger | ||||
| from mealie.routes.deps import get_current_user | ||||
|  | ||||
| router = APIRouter(prefix="/api/foods", dependencies=[Depends(get_current_user)]) | ||||
| logger = get_logger() | ||||
|  | ||||
|  | ||||
| @router.post("") | ||||
| async def create_food(): | ||||
|     """ Create food in the Database """ | ||||
|     # Create food | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @router.get("/{id}") | ||||
| async def get_food(): | ||||
|     """ Get food from the Database """ | ||||
|     # Get food | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @router.put("/{id}") | ||||
| async def update_food(): | ||||
|     """ Update food in the Database """ | ||||
|     # Update food | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @router.delete("/{id}") | ||||
| async def delete_food(): | ||||
|     """ Delete food from the Database """ | ||||
|     # Delete food | ||||
|     pass | ||||
							
								
								
									
										34
									
								
								mealie/routes/unit_and_foods/unit_routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								mealie/routes/unit_and_foods/unit_routes.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| from fastapi import APIRouter, Depends | ||||
| from mealie.core.root_logger import get_logger | ||||
| from mealie.routes.deps import get_current_user | ||||
|  | ||||
| router = APIRouter(prefix="/api/units", dependencies=[Depends(get_current_user)]) | ||||
| logger = get_logger() | ||||
|  | ||||
|  | ||||
| @router.post("") | ||||
| async def create_food(): | ||||
|     """ Create food in the Database """ | ||||
|     # Create food | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @router.get("/{id}") | ||||
| async def get_food(): | ||||
|     """ Get food from the Database """ | ||||
|     # Get food | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @router.put("/{id}") | ||||
| async def update_food(): | ||||
|     """ Update food in the Database """ | ||||
|     # Update food | ||||
|     pass | ||||
|  | ||||
|  | ||||
| @router.delete("/{id}") | ||||
| async def delete_food(): | ||||
|     """ Delete food from the Database """ | ||||
|     # Delete food | ||||
|     pass | ||||
| @@ -59,6 +59,30 @@ class Nutrition(CamelModel): | ||||
|         orm_mode = True | ||||
|  | ||||
|  | ||||
| class RecipeIngredientFood(CamelModel): | ||||
|     name: str = "" | ||||
|     description: str = "" | ||||
|  | ||||
|     class Config: | ||||
|         orm_mode = True | ||||
|  | ||||
|  | ||||
| class RecipeIngredientUnit(RecipeIngredientFood): | ||||
|     pass | ||||
|  | ||||
|  | ||||
| class RecipeIngredient(CamelModel): | ||||
|     title: Optional[str] | ||||
|     note: Optional[str] | ||||
|     unit: Optional[RecipeIngredientUnit] | ||||
|     food: Optional[RecipeIngredientFood] | ||||
|     disable_amount: bool = True | ||||
|     quantity: int = 1 | ||||
|  | ||||
|     class Config: | ||||
|         orm_mode = True | ||||
|  | ||||
|  | ||||
| class RecipeSummary(CamelModel): | ||||
|     id: Optional[int] | ||||
|     name: Optional[str] | ||||
| @@ -87,7 +111,7 @@ class RecipeSummary(CamelModel): | ||||
|  | ||||
| class Recipe(RecipeSummary): | ||||
|     recipe_yield: Optional[str] | ||||
|     recipe_ingredient: Optional[list[str]] | ||||
|     recipe_ingredient: Optional[list[RecipeIngredient]] | ||||
|     recipe_instructions: Optional[list[RecipeStep]] | ||||
|     nutrition: Optional[Nutrition] | ||||
|     tools: Optional[list[str]] = [] | ||||
| @@ -134,7 +158,7 @@ class Recipe(RecipeSummary): | ||||
|         def getter_dict(_cls, name_orm: RecipeModel): | ||||
|             return { | ||||
|                 **GetterDict(name_orm), | ||||
|                 "recipe_ingredient": [x.ingredient for x in name_orm.recipe_ingredient], | ||||
|                 # "recipe_ingredient": [x.note for x in name_orm.recipe_ingredient], | ||||
|                 "recipe_category": [x.name for x in name_orm.recipe_category], | ||||
|                 "tags": [x.name for x in name_orm.tags], | ||||
|                 "tools": [x.tool for x in name_orm.tools], | ||||
| @@ -179,6 +203,16 @@ class Recipe(RecipeSummary): | ||||
|  | ||||
|         return slug | ||||
|  | ||||
|     @validator("recipe_ingredient", always=True, pre=True) | ||||
|     def validate_ingredients(recipe_ingredient, values): | ||||
|         if not recipe_ingredient or not isinstance(recipe_ingredient, list): | ||||
|             return recipe_ingredient | ||||
|  | ||||
|         if all(isinstance(elem, str) for elem in recipe_ingredient): | ||||
|             return [RecipeIngredient(note=x) for x in recipe_ingredient] | ||||
|  | ||||
|         return recipe_ingredient | ||||
|  | ||||
|  | ||||
| class AllRecipeRequest(BaseModel): | ||||
|     properties: list[str] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user