mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -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:
parent
0a927afaa0
commit
e95ca870b1
@ -31,6 +31,7 @@ const apiReq = {
|
|||||||
post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
|
post: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
|
||||||
const response = await axios.post(url, data).catch(function(error) {
|
const response = await axios.post(url, data).catch(function(error) {
|
||||||
handleError(error, getErrorText);
|
handleError(error, getErrorText);
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
return handleResponse(response, getSuccessText);
|
return handleResponse(response, getSuccessText);
|
||||||
},
|
},
|
||||||
@ -38,6 +39,7 @@ const apiReq = {
|
|||||||
put: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
|
put: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
|
||||||
const response = await axios.put(url, data).catch(function(error) {
|
const response = await axios.put(url, data).catch(function(error) {
|
||||||
handleError(error, getErrorText);
|
handleError(error, getErrorText);
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
return handleResponse(response, getSuccessText);
|
return handleResponse(response, getSuccessText);
|
||||||
},
|
},
|
||||||
@ -45,6 +47,7 @@ const apiReq = {
|
|||||||
patch: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
|
patch: async function(url, data, getErrorText = defaultErrorText, getSuccessText) {
|
||||||
const response = await axios.patch(url, data).catch(function(error) {
|
const response = await axios.patch(url, data).catch(function(error) {
|
||||||
handleError(error, getErrorText);
|
handleError(error, getErrorText);
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
return handleResponse(response, getSuccessText);
|
return handleResponse(response, getSuccessText);
|
||||||
},
|
},
|
||||||
@ -52,12 +55,14 @@ const apiReq = {
|
|||||||
get: async function(url, data, getErrorText = defaultErrorText) {
|
get: async function(url, data, getErrorText = defaultErrorText) {
|
||||||
return axios.get(url, data).catch(function(error) {
|
return axios.get(url, data).catch(function(error) {
|
||||||
handleError(error, getErrorText);
|
handleError(error, getErrorText);
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText) {
|
delete: async function(url, data, getErrorText = defaultErrorText, getSuccessText = defaultSuccessText) {
|
||||||
const response = await axios.delete(url, data).catch(function(error) {
|
const response = await axios.delete(url, data).catch(function(error) {
|
||||||
handleError(error, getErrorText);
|
handleError(error, getErrorText);
|
||||||
|
return error;
|
||||||
});
|
});
|
||||||
return handleResponse(response, getSuccessText);
|
return handleResponse(response, getSuccessText);
|
||||||
},
|
},
|
||||||
|
@ -35,9 +35,11 @@ export const recipeAPI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async requestDetails(recipeSlug) {
|
async requestDetails(recipeSlug) {
|
||||||
let response = await apiReq.get(API_ROUTES.recipesRecipeSlug(recipeSlug));
|
const response = await apiReq.get(API_ROUTES.recipesRecipeSlug(recipeSlug));
|
||||||
if (response && response.data) return response.data;
|
if (response.response) {
|
||||||
else return null;
|
return response.response;
|
||||||
|
}
|
||||||
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
updateImage(recipeSlug, fileObject, overrideSuccessMsg = false) {
|
updateImage(recipeSlug, fileObject, overrideSuccessMsg = false) {
|
||||||
|
@ -16,26 +16,14 @@
|
|||||||
:top="menuTop"
|
:top="menuTop"
|
||||||
:nudge-top="menuTop ? '5' : '0'"
|
:nudge-top="menuTop ? '5' : '0'"
|
||||||
allow-overflow
|
allow-overflow
|
||||||
|
close-delay="125"
|
||||||
|
open-on-hover
|
||||||
>
|
>
|
||||||
<template v-slot:activator="{ on: onMenu, attrs: attrsMenu }">
|
<template v-slot:activator="{ on, attrs }">
|
||||||
<v-tooltip bottom dark :color="color">
|
<v-btn :fab="fab" :small="fab" :color="color" :icon="!fab" dark v-bind="attrs" v-on="on" @click.prevent>
|
||||||
<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
|
|
||||||
>
|
|
||||||
<v-icon>{{ menuIcon }}</v-icon>
|
<v-icon>{{ menuIcon }}</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</template>
|
</template>
|
||||||
<span>{{ $t("general.more") }}</span>
|
|
||||||
</v-tooltip>
|
|
||||||
</template>
|
|
||||||
<v-list dense>
|
<v-list dense>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="(item, index) in loggedIn && cardMenu ? userMenu : defaultMenu"
|
v-for="(item, index) in loggedIn && cardMenu ? userMenu : defaultMenu"
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-tooltip right :color="buttonStyle ? 'primary' : 'secondary'">
|
<v-tooltip bottom nudge-right="50" :color="buttonStyle ? 'primary' : 'secondary'">
|
||||||
<template v-slot:activator="{ on, attrs }">
|
<template v-slot:activator="{ on, attrs }">
|
||||||
<v-btn
|
<v-btn
|
||||||
small
|
small
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div v-if="value && value.length > 0">
|
||||||
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
|
<h2 class="mb-4">{{ $t("recipe.ingredients") }}</h2>
|
||||||
<div v-if="edit">
|
<div v-if="edit">
|
||||||
<draggable :value="value" @input="updateIndex" @start="drag = true" @end="drag = false" handle=".handle">
|
<draggable :value="value" @input="updateIndex" @start="drag = true" @end="drag = false" handle=".handle">
|
||||||
@ -9,7 +9,7 @@
|
|||||||
<v-textarea
|
<v-textarea
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
:label="$t('recipe.ingredient')"
|
:label="$t('recipe.ingredient')"
|
||||||
v-model="value[index]"
|
v-model="value[index].note"
|
||||||
mdi-move-resize
|
mdi-move-resize
|
||||||
auto-grow
|
auto-grow
|
||||||
solo
|
solo
|
||||||
@ -45,7 +45,7 @@
|
|||||||
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary"> </v-checkbox>
|
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary"> </v-checkbox>
|
||||||
|
|
||||||
<v-list-item-content>
|
<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-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</div>
|
</div>
|
||||||
@ -85,9 +85,26 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
addIngredient(ingredients = null) {
|
addIngredient(ingredients = null) {
|
||||||
if (ingredients.length) {
|
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 {
|
} else {
|
||||||
this.value.push("");
|
this.value.push({
|
||||||
|
title: null,
|
||||||
|
note: "",
|
||||||
|
unit: null,
|
||||||
|
food: null,
|
||||||
|
disableAmount: true,
|
||||||
|
quantity: 1,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
generateKey(item, index) {
|
generateKey(item, index) {
|
||||||
|
@ -192,8 +192,12 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recipeDetails = await api.recipes.requestDetails(this.currentRecipe);
|
const response = await api.recipes.requestDetails(this.currentRecipe);
|
||||||
if (!this.recipeDetails) router.push(`/login`);
|
console.log("View Response", { response });
|
||||||
|
if (response.status === 401) router.push(`/login`);
|
||||||
|
if (response.status === 404) return;
|
||||||
|
|
||||||
|
this.recipeDetails = response.data;
|
||||||
this.skeleton = false;
|
this.skeleton = false;
|
||||||
},
|
},
|
||||||
getImage(slug) {
|
getImage(slug) {
|
||||||
|
@ -5,18 +5,19 @@
|
|||||||
<v-icon left>
|
<v-icon left>
|
||||||
mdi-arrow-left-bold
|
mdi-arrow-left-bold
|
||||||
</v-icon>
|
</v-icon>
|
||||||
{{$t('shopping-list.all-lists')}}
|
{{ $t("shopping-list.all-lists") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-icon v-if="!list" large left>
|
<v-icon v-if="!list" large left>
|
||||||
mdi-format-list-checks
|
mdi-format-list-checks
|
||||||
</v-icon>
|
</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>
|
<v-spacer></v-spacer>
|
||||||
<BaseDialog
|
<BaseDialog
|
||||||
:title="$t('shopping-list.new-list')"
|
:title="$t('shopping-list.new-list')"
|
||||||
title-icon="mdi-format-list-checks"
|
title-icon="mdi-format-list-checks"
|
||||||
:submit-text="$t('general.create')"
|
:submit-text="$t('general.create')"
|
||||||
@submit="createNewList">
|
@submit="createNewList"
|
||||||
|
>
|
||||||
<template v-slot:open="{ open }">
|
<template v-slot:open="{ open }">
|
||||||
<TheButton create @click="open" />
|
<TheButton create @click="open" />
|
||||||
</template>
|
</template>
|
||||||
@ -41,7 +42,7 @@
|
|||||||
<v-icon left>
|
<v-icon left>
|
||||||
mdi-cart-check
|
mdi-cart-check
|
||||||
</v-icon>
|
</v-icon>
|
||||||
{{$t('general.view')}}
|
{{ $t("general.view") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
@ -65,7 +66,7 @@
|
|||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row dense v-for="(item, index) in activeList.items" :key="index">
|
<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">
|
<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">
|
<div v-if="edit">
|
||||||
<v-btn x-small text class="ml-1" @click="activeList.items[index].quantity -= 1">
|
<v-btn x-small text class="ml-1" @click="activeList.items[index].quantity -= 1">
|
||||||
<v-icon>
|
<v-icon>
|
||||||
@ -123,13 +124,13 @@
|
|||||||
<v-icon left>
|
<v-icon left>
|
||||||
{{ $globals.icons.primary }}
|
{{ $globals.icons.primary }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
{{$t('shopping-list.from-recipe')}}
|
{{ $t("shopping-list.from-recipe") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn v-if="edit" color="success" @click="newItem">
|
<v-btn v-if="edit" color="success" @click="newItem">
|
||||||
<v-icon left>
|
<v-icon left>
|
||||||
{{ $globals.icons.create }}
|
{{ $globals.icons.create }}
|
||||||
</v-icon>
|
</v-icon>
|
||||||
{{$t('general.new')}}
|
{{ $t("general.new") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
@ -197,7 +198,8 @@ export default {
|
|||||||
this.$refs.searchRecipe.open();
|
this.$refs.searchRecipe.open();
|
||||||
},
|
},
|
||||||
async importIngredients(selected) {
|
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 => ({
|
const ingredients = recipe.recipeIngredient.map(x => ({
|
||||||
title: "",
|
title: "",
|
||||||
|
@ -26,7 +26,8 @@ export const recipeRoutes = [
|
|||||||
component: ViewRecipe,
|
component: ViewRecipe,
|
||||||
meta: {
|
meta: {
|
||||||
title: async route => {
|
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;
|
if (recipe && recipe.name) return recipe.name;
|
||||||
else return null;
|
else return null;
|
||||||
},
|
},
|
||||||
|
2
makefile
2
makefile
@ -23,7 +23,7 @@ BROWSER := python -c "$$BROWSER_PYSCRIPT"
|
|||||||
help:
|
help:
|
||||||
@python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST)
|
@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/recipes/
|
||||||
rm -r ./dev/data/users/
|
rm -r ./dev/data/users/
|
||||||
rm -f ./dev/data/mealie_v*.db
|
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.group import Group
|
||||||
from mealie.db.models.mealplan import MealPlan
|
from mealie.db.models.mealplan import MealPlan
|
||||||
from mealie.db.models.recipe.comment import RecipeComment
|
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.recipe import Category, RecipeModel, Tag
|
||||||
from mealie.db.models.recipe.settings import RecipeSettings
|
from mealie.db.models.recipe.settings import RecipeSettings
|
||||||
from mealie.db.models.settings import CustomPage, SiteSettings
|
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.event_notifications import EventNotificationIn
|
||||||
from mealie.schema.events import Event as EventSchema
|
from mealie.schema.events import Event as EventSchema
|
||||||
from mealie.schema.meal import MealPlanOut
|
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 CustomPageOut
|
||||||
from mealie.schema.settings import SiteSettings as SiteSettingsSchema
|
from mealie.schema.settings import SiteSettings as SiteSettingsSchema
|
||||||
from mealie.schema.shopping_list import ShoppingListOut
|
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):
|
class _Categories(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "slug"
|
self.primary_key = "slug"
|
||||||
@ -215,21 +231,28 @@ class _EventNotification(BaseDocument):
|
|||||||
|
|
||||||
class Database:
|
class Database:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
# Recipes
|
||||||
self.recipes = _Recipes()
|
self.recipes = _Recipes()
|
||||||
self.meals = _Meals()
|
self.ingredient_foods = _IngredientUnits()
|
||||||
self.settings = _Settings()
|
self.ingredient_units = _IngredientFoods()
|
||||||
self.themes = _Themes()
|
|
||||||
self.categories = _Categories()
|
self.categories = _Categories()
|
||||||
self.tags = _Tags()
|
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.users = _Users()
|
||||||
self.api_tokens = _LongLiveToken()
|
self.api_tokens = _LongLiveToken()
|
||||||
self.sign_ups = _SignUps()
|
|
||||||
self.groups = _Groups()
|
self.groups = _Groups()
|
||||||
self.custom_pages = _CustomPages()
|
self.meals = _Meals()
|
||||||
self.events = _Events()
|
|
||||||
self.event_notifications = _EventNotification()
|
|
||||||
self.shopping_lists = _ShoppingList()
|
self.shopping_lists = _ShoppingList()
|
||||||
self.comments = _Comments()
|
|
||||||
|
|
||||||
|
|
||||||
db = Database()
|
db = Database()
|
||||||
|
@ -1,5 +1,70 @@
|
|||||||
from mealie.db.models.model_base import SqlAlchemyBase
|
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||||
from sqlalchemy import Column, ForeignKey, Integer, String
|
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):
|
class RecipeIngredient(SqlAlchemyBase):
|
||||||
@ -7,8 +72,24 @@ class RecipeIngredient(SqlAlchemyBase):
|
|||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
position = Column(Integer)
|
position = Column(Integer)
|
||||||
parent_id = Column(Integer, ForeignKey("recipes.id"))
|
parent_id = Column(Integer, ForeignKey("recipes.id"))
|
||||||
# title = Column(String)
|
|
||||||
ingredient = Column(String)
|
|
||||||
|
|
||||||
def update(self, ingredient):
|
title = Column(String) # Section Header - Shows if Present
|
||||||
self.ingredient = ingredient
|
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.tools = [Tool(tool=x) for x in tools] if tools else []
|
||||||
|
|
||||||
self.recipe_yield = recipe_yield
|
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.assets = [RecipeAsset(**a) for a in assets]
|
||||||
self.recipe_instructions = [
|
self.recipe_instructions = [
|
||||||
RecipeInstruction(text=instruc.get("text"), title=instruc.get("title"), type=instruc.get("@type", None))
|
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")
|
logger.error("Recipe Not Found")
|
||||||
|
|
||||||
new_list = ShoppingListIn(
|
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)
|
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)
|
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:
|
if recipe.settings.public or is_user:
|
||||||
|
|
||||||
return recipe
|
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
|
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):
|
class RecipeSummary(CamelModel):
|
||||||
id: Optional[int]
|
id: Optional[int]
|
||||||
name: Optional[str]
|
name: Optional[str]
|
||||||
@ -87,7 +111,7 @@ class RecipeSummary(CamelModel):
|
|||||||
|
|
||||||
class Recipe(RecipeSummary):
|
class Recipe(RecipeSummary):
|
||||||
recipe_yield: Optional[str]
|
recipe_yield: Optional[str]
|
||||||
recipe_ingredient: Optional[list[str]]
|
recipe_ingredient: Optional[list[RecipeIngredient]]
|
||||||
recipe_instructions: Optional[list[RecipeStep]]
|
recipe_instructions: Optional[list[RecipeStep]]
|
||||||
nutrition: Optional[Nutrition]
|
nutrition: Optional[Nutrition]
|
||||||
tools: Optional[list[str]] = []
|
tools: Optional[list[str]] = []
|
||||||
@ -134,7 +158,7 @@ class Recipe(RecipeSummary):
|
|||||||
def getter_dict(_cls, name_orm: RecipeModel):
|
def getter_dict(_cls, name_orm: RecipeModel):
|
||||||
return {
|
return {
|
||||||
**GetterDict(name_orm),
|
**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],
|
"recipe_category": [x.name for x in name_orm.recipe_category],
|
||||||
"tags": [x.name for x in name_orm.tags],
|
"tags": [x.name for x in name_orm.tags],
|
||||||
"tools": [x.tool for x in name_orm.tools],
|
"tools": [x.tool for x in name_orm.tools],
|
||||||
@ -179,6 +203,16 @@ class Recipe(RecipeSummary):
|
|||||||
|
|
||||||
return slug
|
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):
|
class AllRecipeRequest(BaseModel):
|
||||||
properties: list[str]
|
properties: list[str]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user