mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 22:43:34 -07:00
multiple recipes per day
This commit is contained in:
parent
da9826dc7c
commit
c204a5e628
13 changed files with 323 additions and 159 deletions
|
@ -1,14 +1,45 @@
|
||||||
<template>
|
<template>
|
||||||
<v-row>
|
<v-row>
|
||||||
<SearchDialog ref="mealselect" @select="setSlug" />
|
<SearchDialog ref="mealselect" @select="setSlug" />
|
||||||
<v-col cols="12" sm="12" md="6" lg="4" xl="3" v-for="(meal, index) in value" :key="index">
|
<v-col cols="12" sm="12" md="6" lg="4" xl="3" v-for="(planDay, index) in value" :key="index">
|
||||||
<v-hover v-slot="{ hover }" :open-delay="50">
|
<v-hover v-slot="{ hover }" :open-delay="50">
|
||||||
<v-card :class="{ 'on-hover': hover }" :elevation="hover ? 12 : 2">
|
<v-card :class="{ 'on-hover': hover }" :elevation="hover ? 12 : 2">
|
||||||
<v-img height="200" :src="getImage(meal.slug)" @click="openSearch(index)"></v-img>
|
<CardImage large :slug="planDay.meals[0].slug" icon-size="200" @click="openSearch(index, modes.primary)">
|
||||||
|
</CardImage>
|
||||||
|
|
||||||
<v-card-title class="my-n3 mb-n6">
|
<v-card-title class="my-n3 mb-n6">
|
||||||
{{ $d(new Date(meal.date.split("-")), "short") }}
|
{{ $d(new Date(planDay.date.split("-")), "short") }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-subtitle> {{ meal.name }}</v-card-subtitle>
|
<v-card-subtitle class="mb-0 pb-0"> {{ planDay.meals[0].name }}</v-card-subtitle>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="info" outlined small @click="openSearch(index, modes.sides)">
|
||||||
|
<v-icon small class="mr-1">
|
||||||
|
mdi-plus
|
||||||
|
</v-icon>
|
||||||
|
Side
|
||||||
|
</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
<v-divider class="mx-2"></v-divider>
|
||||||
|
<v-list dense>
|
||||||
|
<v-list-item v-for="(recipe, i) in planDay.meals.slice(1)" :key="i">
|
||||||
|
<v-list-item-avatar>
|
||||||
|
<v-img :alt="recipe.slug" :src="getImage(recipe.slug)"></v-img>
|
||||||
|
</v-list-item-avatar>
|
||||||
|
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title v-text="recipe.name"></v-list-item-title>
|
||||||
|
</v-list-item-content>
|
||||||
|
|
||||||
|
<v-list-item-icon>
|
||||||
|
<v-btn icon @click="removeSide(index, i + 1)">
|
||||||
|
<v-icon color="error">
|
||||||
|
mdi-delete
|
||||||
|
</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-list-item-icon>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-hover>
|
</v-hover>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
@ -18,37 +49,71 @@
|
||||||
<script>
|
<script>
|
||||||
import SearchDialog from "../UI/Search/SearchDialog";
|
import SearchDialog from "../UI/Search/SearchDialog";
|
||||||
import { api } from "@/api";
|
import { api } from "@/api";
|
||||||
|
import CardImage from "../Recipe/CardImage.vue";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SearchDialog,
|
SearchDialog,
|
||||||
|
CardImage,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: Array,
|
value: Array,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
recipeData: [],
|
|
||||||
cardData: [],
|
|
||||||
activeIndex: 0,
|
activeIndex: 0,
|
||||||
|
mode: "PRIMARY",
|
||||||
|
modes: {
|
||||||
|
primary: "PRIMARY",
|
||||||
|
sides: "SIDES",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
value(val) {
|
||||||
|
console.log(val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
console.log(this.value);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getImage(slug) {
|
getImage(slug) {
|
||||||
if (slug) {
|
if (slug) {
|
||||||
return api.recipes.recipeSmallImage(slug);
|
return api.recipes.recipeSmallImage(slug);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setSlug(name, slug) {
|
setSide(name, slug) {
|
||||||
let index = this.activeIndex;
|
const meal = { name: name, slug: slug };
|
||||||
this.value[index]["slug"] = slug;
|
this.value[this.activeIndex]["meals"].push(meal);
|
||||||
this.value[index]["name"] = name;
|
|
||||||
},
|
},
|
||||||
openSearch(index) {
|
setPrimary(name, slug) {
|
||||||
|
this.value[this.activeIndex]["meals"][0]["slug"] = slug;
|
||||||
|
this.value[this.activeIndex]["meals"][0]["name"] = name;
|
||||||
|
},
|
||||||
|
setSlug(name, slug) {
|
||||||
|
switch (this.mode) {
|
||||||
|
case this.modes.primary:
|
||||||
|
this.setPrimary(name, slug);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.setSide(name, slug);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
openSearch(index, mode) {
|
||||||
|
this.mode = mode;
|
||||||
this.activeIndex = index;
|
this.activeIndex = index;
|
||||||
this.$refs.mealselect.open();
|
this.$refs.mealselect.open();
|
||||||
},
|
},
|
||||||
|
removeSide(dayIndex, sideIndex) {
|
||||||
|
this.value[dayIndex]["meals"].splice(sideIndex, 1);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style></style>
|
<style>
|
||||||
|
.relative-card {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -63,14 +63,14 @@
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-text v-if="startDate">
|
<v-card-text v-if="startDate">
|
||||||
<MealPlanCard v-model="meals" />
|
<MealPlanCard v-model="planDays" />
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-row align="center" justify="end">
|
<v-row align="center" justify="end">
|
||||||
<v-card-actions class="mr-5">
|
<v-card-actions class="mr-5">
|
||||||
<v-btn color="success" @click="random" v-if="meals.length > 0" text>
|
<v-btn color="success" @click="random" v-if="planDays.length > 0" text>
|
||||||
{{ $t("general.random") }}
|
{{ $t("general.random") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn color="success" @click="save" text :disabled="meals.length == 0">
|
<v-btn color="success" @click="save" text :disabled="planDays.length == 0">
|
||||||
{{ $t("general.save") }}
|
{{ $t("general.save") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
|
@ -92,7 +92,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
meals: [],
|
planDays: [],
|
||||||
items: [],
|
items: [],
|
||||||
|
|
||||||
// Dates
|
// Dates
|
||||||
|
@ -106,11 +106,17 @@ export default {
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
dateDif() {
|
dateDif() {
|
||||||
this.meals = [];
|
this.planDays = [];
|
||||||
for (let i = 0; i < this.dateDif; i++) {
|
for (let i = 0; i < this.dateDif; i++) {
|
||||||
this.meals.push({
|
this.planDays.push({
|
||||||
slug: "empty",
|
|
||||||
date: this.getDate(i),
|
date: this.getDate(i),
|
||||||
|
meals: [
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
slug: "empty",
|
||||||
|
description: "empty",
|
||||||
|
},
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -172,10 +178,10 @@ export default {
|
||||||
},
|
},
|
||||||
random() {
|
random() {
|
||||||
this.usedRecipes = [1];
|
this.usedRecipes = [1];
|
||||||
this.meals.forEach((element, index) => {
|
this.planDays.forEach((element, index) => {
|
||||||
let recipe = this.getRandom(this.filteredRecipes);
|
let recipe = this.getRandom(this.filteredRecipes);
|
||||||
this.meals[index]["slug"] = recipe.slug;
|
this.planDays[index]["meals"][0]["slug"] = recipe.slug;
|
||||||
this.meals[index]["name"] = recipe.name;
|
this.planDays[index]["meals"][0]["name"] = recipe.name;
|
||||||
this.usedRecipes.push(recipe);
|
this.usedRecipes.push(recipe);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -193,11 +199,11 @@ export default {
|
||||||
group: this.groupSettings.name,
|
group: this.groupSettings.name,
|
||||||
startDate: this.startDate,
|
startDate: this.startDate,
|
||||||
endDate: this.endDate,
|
endDate: this.endDate,
|
||||||
meals: this.meals,
|
planDays: this.planDays,
|
||||||
};
|
};
|
||||||
if (await api.mealPlans.create(mealBody)) {
|
if (await api.mealPlans.create(mealBody)) {
|
||||||
this.$emit(CREATE_EVENT);
|
this.$emit(CREATE_EVENT);
|
||||||
this.meals = [];
|
this.planDays = [];
|
||||||
this.startDate = null;
|
this.startDate = null;
|
||||||
this.endDate = null;
|
this.endDate = null;
|
||||||
}
|
}
|
||||||
|
|
85
frontend/src/components/Recipe/CardImage.vue
Normal file
85
frontend/src/components/Recipe/CardImage.vue
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
<template>
|
||||||
|
<div @click="$emit('click')">
|
||||||
|
<v-img
|
||||||
|
:height="height"
|
||||||
|
v-if="!fallBackImage"
|
||||||
|
:src="getImage(slug)"
|
||||||
|
@load="fallBackImage = false"
|
||||||
|
@error="fallBackImage = true"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</v-img>
|
||||||
|
<v-icon v-else color="primary" class="icon-position" :size="iconSize">
|
||||||
|
mdi-silverware-variant
|
||||||
|
</v-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { api } from "@/api";
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
tiny: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
small: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
large: {
|
||||||
|
type: Boolean,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
iconSize: {
|
||||||
|
default: 100,
|
||||||
|
},
|
||||||
|
slug: {
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
default: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
imageSize() {
|
||||||
|
if (this.tiny) return "tiny";
|
||||||
|
if (this.small) return "small";
|
||||||
|
if (this.large) return "large";
|
||||||
|
return "large";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
slug() {
|
||||||
|
this.fallBackImage = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
fallBackImage: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getImage(image) {
|
||||||
|
switch (this.imageSize) {
|
||||||
|
case "tiny":
|
||||||
|
return api.recipes.recipeTinyImage(image);
|
||||||
|
case "small":
|
||||||
|
return api.recipes.recipeSmallImage(image);
|
||||||
|
case "large":
|
||||||
|
return api.recipes.recipeImage(image);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.icon-position {
|
||||||
|
opacity: 0.8;
|
||||||
|
display: flex !important;
|
||||||
|
position: relative;
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -92,11 +92,5 @@ export default {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-position {
|
|
||||||
opacity: 0.8;
|
|
||||||
display: flex !important;
|
|
||||||
position: relative;
|
|
||||||
margin-left: auto !important;
|
|
||||||
margin-right: auto !important;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -17,10 +17,22 @@
|
||||||
{{ $d(new Date(mealplan.startDate.split("-")), "short") }} -
|
{{ $d(new Date(mealplan.startDate.split("-")), "short") }} -
|
||||||
{{ $d(new Date(mealplan.endDate.split("-")), "short") }}
|
{{ $d(new Date(mealplan.endDate.split("-")), "short") }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-list nav>
|
|
||||||
<v-list-item-group color="primary">
|
<v-list>
|
||||||
|
<v-list-group v-for="(planDay, pdi) in mealplan.planDays" :key="`planDays-${pdi}`">
|
||||||
|
<template v-slot:activator>
|
||||||
|
<v-list-item-avatar color="primary" class="headline font-weight-light white--text">
|
||||||
|
<v-img :src="getImage(planDay['meals'][0].slug)"></v-img>
|
||||||
|
</v-list-item-avatar>
|
||||||
|
<v-list-item-content>
|
||||||
|
<v-list-item-title v-html="$d(new Date(planDay.date.split('-')), 'short')"></v-list-item-title>
|
||||||
|
<v-list-item-subtitle v-html="planDay['meals'][0].name"></v-list-item-subtitle>
|
||||||
|
</v-list-item-content>
|
||||||
|
</template>
|
||||||
|
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-for="(meal, index) in mealplan.meals"
|
three-line
|
||||||
|
v-for="(meal, index) in planDay.meals"
|
||||||
:key="generateKey(meal.slug, index)"
|
:key="generateKey(meal.slug, index)"
|
||||||
:to="meal.slug ? `/recipe/${meal.slug}` : null"
|
:to="meal.slug ? `/recipe/${meal.slug}` : null"
|
||||||
>
|
>
|
||||||
|
@ -28,12 +40,13 @@
|
||||||
<v-img :src="getImage(meal.slug)"></v-img>
|
<v-img :src="getImage(meal.slug)"></v-img>
|
||||||
</v-list-item-avatar>
|
</v-list-item-avatar>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title v-text="meal.name"></v-list-item-title>
|
<v-list-item-title v-html="meal.name"></v-list-item-title>
|
||||||
<v-list-item-subtitle v-text="$d(new Date(meal.date.split('-')), 'short')"> </v-list-item-subtitle>
|
<v-list-item-subtitle v-html="meal.description"> </v-list-item-subtitle>
|
||||||
</v-list-item-content>
|
</v-list-item-content>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
</v-list-item-group>
|
</v-list-group>
|
||||||
</v-list>
|
</v-list>
|
||||||
|
|
||||||
<v-card-actions class="mt-n5">
|
<v-card-actions class="mt-n5">
|
||||||
<v-btn color="accent lighten-2" class="mx-0" text @click="openShoppingList(mealplan.uid)">
|
<v-btn color="accent lighten-2" class="mx-0" text @click="openShoppingList(mealplan.uid)">
|
||||||
{{ $t("meal-plan.shopping-list") }}
|
{{ $t("meal-plan.shopping-list") }}
|
||||||
|
@ -76,6 +89,7 @@ export default {
|
||||||
async requestMeals() {
|
async requestMeals() {
|
||||||
const response = await api.mealPlans.all();
|
const response = await api.mealPlans.all();
|
||||||
this.plannedMeals = response.data;
|
this.plannedMeals = response.data;
|
||||||
|
console.log(this.plannedMeals);
|
||||||
},
|
},
|
||||||
generateKey(name, index) {
|
generateKey(name, index) {
|
||||||
return utils.generateUniqueKey(name, index);
|
return utils.generateUniqueKey(name, index);
|
||||||
|
|
|
@ -3,7 +3,7 @@ from logging import getLogger
|
||||||
from mealie.db.db_base import BaseDocument
|
from mealie.db.db_base import BaseDocument
|
||||||
from mealie.db.models.event import Event, EventNotification
|
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 MealPlanModel
|
from mealie.db.models.mealplan import MealPlan
|
||||||
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
|
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
|
||||||
from mealie.db.models.settings import CustomPage, SiteSettings
|
from mealie.db.models.settings import CustomPage, SiteSettings
|
||||||
from mealie.db.models.sign_up import SignUp
|
from mealie.db.models.sign_up import SignUp
|
||||||
|
@ -12,7 +12,7 @@ from mealie.db.models.users import LongLiveToken, User
|
||||||
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
|
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
|
||||||
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 MealPlanInDB
|
from mealie.schema.meal import MealPlanOut
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
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
|
||||||
|
@ -75,8 +75,8 @@ class _Tags(BaseDocument):
|
||||||
class _Meals(BaseDocument):
|
class _Meals(BaseDocument):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.primary_key = "uid"
|
self.primary_key = "uid"
|
||||||
self.sql_model = MealPlanModel
|
self.sql_model = MealPlan
|
||||||
self.schema = MealPlanInDB
|
self.schema = MealPlanOut
|
||||||
|
|
||||||
|
|
||||||
class _Settings(BaseDocument):
|
class _Settings(BaseDocument):
|
||||||
|
@ -120,7 +120,7 @@ class _Groups(BaseDocument):
|
||||||
self.sql_model = Group
|
self.sql_model = Group
|
||||||
self.schema = GroupInDB
|
self.schema = GroupInDB
|
||||||
|
|
||||||
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanInDB]:
|
def get_meals(self, session: Session, match_value: str, match_key: str = "name") -> list[MealPlanOut]:
|
||||||
"""A Helper function to get the group from the database and return a sorted list of
|
"""A Helper function to get the group from the database and return a sorted list of
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
@ -129,7 +129,7 @@ class _Groups(BaseDocument):
|
||||||
match_key (str, optional): Match Key. Defaults to "name".
|
match_key (str, optional): Match Key. Defaults to "name".
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[MealPlanInDB]: [description]
|
list[MealPlanOut]: [description]
|
||||||
"""
|
"""
|
||||||
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
|
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,10 @@ class Group(SqlAlchemyBase, BaseMixins):
|
||||||
name = sa.Column(sa.String, index=True, nullable=False, unique=True)
|
name = sa.Column(sa.String, index=True, nullable=False, unique=True)
|
||||||
users = orm.relationship("User", back_populates="group")
|
users = orm.relationship("User", back_populates="group")
|
||||||
mealplans = orm.relationship(
|
mealplans = orm.relationship(
|
||||||
"MealPlanModel",
|
"MealPlan",
|
||||||
back_populates="group",
|
back_populates="group",
|
||||||
single_parent=True,
|
single_parent=True,
|
||||||
order_by="MealPlanModel.startDate",
|
order_by="MealPlan.start_date",
|
||||||
)
|
)
|
||||||
categories = orm.relationship("Category", secondary=group2categories, single_parent=True)
|
categories = orm.relationship("Category", secondary=group2categories, single_parent=True)
|
||||||
|
|
||||||
|
|
|
@ -1,50 +1,63 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import sqlalchemy as sa
|
|
||||||
import sqlalchemy.orm as orm
|
import sqlalchemy.orm as orm
|
||||||
from mealie.db.models.group import Group
|
from mealie.db.models.group import Group
|
||||||
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
|
||||||
|
from mealie.db.models.recipe.recipe import RecipeModel
|
||||||
|
from sqlalchemy import Column, Date, ForeignKey, Integer, String
|
||||||
|
from sqlalchemy.ext.orderinglist import ordering_list
|
||||||
|
|
||||||
|
|
||||||
class Meal(SqlAlchemyBase):
|
class Meal(SqlAlchemyBase):
|
||||||
__tablename__ = "meal"
|
__tablename__ = "meal"
|
||||||
id = sa.Column(sa.Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
parent_id = sa.Column(sa.Integer, sa.ForeignKey("mealplan.uid"))
|
parent_id = Column(Integer, ForeignKey("mealdays.id"))
|
||||||
slug = sa.Column(sa.String)
|
position = Column(Integer)
|
||||||
name = sa.Column(sa.String)
|
name = Column(String)
|
||||||
date = sa.Column(sa.Date)
|
slug = Column(String)
|
||||||
image = sa.Column(sa.String)
|
description = Column(String)
|
||||||
description = sa.Column(sa.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
|
||||||
|
|
||||||
def __init__(self, slug, name, date, image, description, session=None) -> None:
|
|
||||||
self.slug = slug
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.date = date
|
|
||||||
self.image = image
|
|
||||||
self.description = description
|
self.description = description
|
||||||
|
|
||||||
|
|
||||||
class MealPlanModel(SqlAlchemyBase, BaseMixins):
|
class MealDay(SqlAlchemyBase, BaseMixins):
|
||||||
|
__tablename__ = "mealdays"
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
parent_id = Column(Integer, ForeignKey("mealplan.uid"))
|
||||||
|
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"
|
__tablename__ = "mealplan"
|
||||||
uid = sa.Column(sa.Integer, primary_key=True, unique=True) # ! Probably Bad?
|
uid = Column(Integer, primary_key=True, unique=True)
|
||||||
startDate = sa.Column(sa.Date)
|
start_date = Column(Date)
|
||||||
endDate = sa.Column(sa.Date)
|
end_date = Column(Date)
|
||||||
meals: List[Meal] = orm.relationship(Meal, cascade="all, delete, delete-orphan")
|
plan_days: list[MealDay] = orm.relationship(MealDay, cascade="all, delete, delete-orphan")
|
||||||
group_id = sa.Column(sa.Integer, sa.ForeignKey("groups.id"))
|
|
||||||
|
group_id = Column(Integer, ForeignKey("groups.id"))
|
||||||
group = orm.relationship("Group", back_populates="mealplans")
|
group = orm.relationship("Group", back_populates="mealplans")
|
||||||
|
|
||||||
def __init__(self, startDate, endDate, meals, group: str, uid=None, session=None) -> None:
|
def __init__(self, start_date, end_date, plan_days, group: str, uid=None, session=None) -> None:
|
||||||
self.startDate = startDate
|
self.start_date = start_date
|
||||||
self.endDate = endDate
|
self.end_date = end_date
|
||||||
self.group = Group.get_ref(session, group)
|
self.group = Group.get_ref(session, group)
|
||||||
self.meals = [Meal(**meal) for meal in meals]
|
self.plan_days = [MealDay(**day, session=session) for day in plan_days]
|
||||||
|
|
||||||
def update(self, session, startDate, endDate, meals, uid, group) -> None:
|
|
||||||
|
|
||||||
self.__init__(
|
|
||||||
startDate=startDate,
|
|
||||||
endDate=endDate,
|
|
||||||
meals=meals,
|
|
||||||
group=group,
|
|
||||||
session=session,
|
|
||||||
)
|
|
||||||
|
|
|
@ -2,18 +2,18 @@ from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import generate_session
|
from mealie.db.db_setup import generate_session
|
||||||
from mealie.routes.deps import get_current_user
|
from mealie.routes.deps import get_current_user
|
||||||
from mealie.schema.meal import MealPlanIn, MealPlanInDB
|
from mealie.schema.meal import MealPlanIn, MealPlanOut
|
||||||
from mealie.schema.user import GroupInDB, UserInDB
|
from mealie.schema.user import GroupInDB, UserInDB
|
||||||
from mealie.services.events import create_group_event
|
from mealie.services.events import create_group_event
|
||||||
from mealie.services.image import image
|
from mealie.services.image import image
|
||||||
from mealie.services.meal_services import get_todays_meal, process_meals
|
from mealie.services.meal_services import get_todays_meal, set_mealplan_dates
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
from starlette.responses import FileResponse
|
from starlette.responses import FileResponse
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
router = APIRouter(prefix="/api/meal-plans", tags=["Meal Plan"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/all", response_model=list[MealPlanInDB])
|
@router.get("/all", response_model=list[MealPlanOut])
|
||||||
def get_all_meals(
|
def get_all_meals(
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
session: Session = Depends(generate_session),
|
session: Session = Depends(generate_session),
|
||||||
|
@ -31,11 +31,11 @@ def create_meal_plan(
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
""" Creates a meal plan database entry """
|
""" Creates a meal plan database entry """
|
||||||
processed_plan = process_meals(session, data)
|
set_mealplan_dates(data)
|
||||||
background_tasks.add_task(
|
background_tasks.add_task(
|
||||||
create_group_event, "Meal Plan Created", f"Mealplan Created for '{current_user.group}'", session=session
|
create_group_event, "Meal Plan Created", f"Mealplan Created for '{current_user.group}'", session=session
|
||||||
)
|
)
|
||||||
return db.meals.create(session, processed_plan.dict())
|
return db.meals.create(session, data.dict())
|
||||||
|
|
||||||
|
|
||||||
@router.put("/{plan_id}")
|
@router.put("/{plan_id}")
|
||||||
|
@ -47,8 +47,8 @@ def update_meal_plan(
|
||||||
current_user: UserInDB = Depends(get_current_user),
|
current_user: UserInDB = Depends(get_current_user),
|
||||||
):
|
):
|
||||||
""" Updates a meal plan based off ID """
|
""" Updates a meal plan based off ID """
|
||||||
processed_plan = process_meals(session, meal_plan)
|
set_mealplan_dates(meal_plan)
|
||||||
processed_plan = MealPlanInDB(uid=plan_id, **processed_plan.dict())
|
processed_plan = MealPlanOut(uid=plan_id, **meal_plan.dict())
|
||||||
try:
|
try:
|
||||||
db.meals.update(session, plan_id, processed_plan.dict())
|
db.meals.update(session, plan_id, processed_plan.dict())
|
||||||
background_tasks.add_task(
|
background_tasks.add_task(
|
||||||
|
@ -76,7 +76,7 @@ def delete_meal_plan(
|
||||||
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
raise HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/this-week", response_model=MealPlanInDB)
|
@router.get("/this-week", response_model=MealPlanOut)
|
||||||
def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
|
def get_this_week(session: Session = Depends(generate_session), current_user: UserInDB = Depends(get_current_user)):
|
||||||
""" Returns the meal plan data for this week """
|
""" Returns the meal plan data for this week """
|
||||||
plans = db.groups.get_meals(session, current_user.group)
|
plans = db.groups.get_meals(session, current_user.group)
|
||||||
|
|
|
@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import generate_session
|
from mealie.db.db_setup import generate_session
|
||||||
from mealie.routes.deps import get_current_user
|
from mealie.routes.deps import get_current_user
|
||||||
from mealie.schema.meal import MealPlanInDB
|
from mealie.schema.meal import MealPlanOut
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ def get_shopping_list(
|
||||||
|
|
||||||
# ! Refactor into Single Database Call
|
# ! Refactor into Single Database Call
|
||||||
mealplan = db.meals.get(session, id)
|
mealplan = db.meals.get(session, id)
|
||||||
mealplan: MealPlanInDB
|
mealplan: MealPlanOut
|
||||||
slugs = [x.slug for x in mealplan.meals]
|
slugs = [x.slug for x in mealplan.meals]
|
||||||
recipes: list[Recipe] = [db.recipes.get(session, x) for x in slugs]
|
recipes: list[Recipe] = [db.recipes.get(session, x) for x in slugs]
|
||||||
return [{"name": x.name, "recipe_ingredient": x.recipe_ingredient} for x in recipes if x]
|
return [{"name": x.name, "recipe_ingredient": x.recipe_ingredient} for x in recipes if x]
|
||||||
|
|
|
@ -1,50 +1,60 @@
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import List, Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mealie.db.models.mealplan import MealPlanModel
|
from fastapi_camelcase import CamelModel
|
||||||
from pydantic import BaseModel, validator
|
from mealie.db.models.mealplan import MealPlan
|
||||||
|
from pydantic import validator
|
||||||
from pydantic.utils import GetterDict
|
from pydantic.utils import GetterDict
|
||||||
|
|
||||||
|
|
||||||
class MealIn(BaseModel):
|
class MealIn(CamelModel):
|
||||||
name: Optional[str]
|
|
||||||
slug: Optional[str]
|
slug: Optional[str]
|
||||||
date: Optional[date]
|
name: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class MealOut(MealIn):
|
|
||||||
image: Optional[str]
|
|
||||||
description: Optional[str]
|
description: Optional[str]
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class MealPlanIn(BaseModel):
|
class MealDayIn(CamelModel):
|
||||||
group: str
|
date: Optional[date]
|
||||||
startDate: date
|
meals: list[MealIn]
|
||||||
endDate: date
|
|
||||||
meals: List[MealIn]
|
|
||||||
|
|
||||||
@validator("endDate")
|
class Config:
|
||||||
def endDate_after_startDate(v, values, config, field):
|
orm_mode = True
|
||||||
if "startDate" in values and v < values["startDate"]:
|
|
||||||
|
|
||||||
|
class MealDayOut(MealDayIn):
|
||||||
|
id: int
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
|
class MealPlanIn(CamelModel):
|
||||||
|
group: str
|
||||||
|
start_date: date
|
||||||
|
end_date: date
|
||||||
|
plan_days: list[MealDayIn]
|
||||||
|
|
||||||
|
@validator("end_date")
|
||||||
|
def end_date_after_start_date(v, values, config, field):
|
||||||
|
if "start_date" in values and v < values["start_date"]:
|
||||||
raise ValueError("EndDate should be greater than StartDate")
|
raise ValueError("EndDate should be greater than StartDate")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
class Config:
|
||||||
class MealPlanProcessed(MealPlanIn):
|
orm_mode = True
|
||||||
meals: list[MealOut]
|
|
||||||
|
|
||||||
|
|
||||||
class MealPlanInDB(MealPlanProcessed):
|
class MealPlanOut(MealPlanIn):
|
||||||
uid: str
|
uid: int
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getter_dict(_cls, name_orm: MealPlanModel):
|
def getter_dict(_cls, name_orm: MealPlan):
|
||||||
return {
|
return {
|
||||||
**GetterDict(name_orm),
|
**GetterDict(name_orm),
|
||||||
"group": name_orm.group.name,
|
"group": name_orm.group.name,
|
||||||
|
|
|
@ -5,7 +5,7 @@ from mealie.core.config import settings
|
||||||
from mealie.db.models.group import Group
|
from mealie.db.models.group import Group
|
||||||
from mealie.db.models.users import User
|
from mealie.db.models.users import User
|
||||||
from mealie.schema.category import CategoryBase
|
from mealie.schema.category import CategoryBase
|
||||||
from mealie.schema.meal import MealPlanInDB
|
from mealie.schema.meal import MealPlanOut
|
||||||
from pydantic.types import constr
|
from pydantic.types import constr
|
||||||
from pydantic.utils import GetterDict
|
from pydantic.utils import GetterDict
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ class UpdateGroup(GroupBase):
|
||||||
|
|
||||||
class GroupInDB(UpdateGroup):
|
class GroupInDB(UpdateGroup):
|
||||||
users: Optional[list[UserOut]]
|
users: Optional[list[UserOut]]
|
||||||
mealplans: Optional[list[MealPlanInDB]]
|
mealplans: Optional[list[MealPlanOut]]
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
|
@ -3,41 +3,17 @@ from typing import Union
|
||||||
|
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import create_session
|
from mealie.db.db_setup import create_session
|
||||||
from mealie.schema.meal import MealIn, MealOut, MealPlanIn, MealPlanInDB, MealPlanProcessed
|
from mealie.schema.meal import MealDayIn, MealPlanIn
|
||||||
from mealie.schema.recipe import Recipe
|
from mealie.schema.recipe import Recipe
|
||||||
from mealie.schema.user import GroupInDB
|
from mealie.schema.user import GroupInDB
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
|
|
||||||
def process_meals(session: Session, meal_plan_base: MealPlanIn) -> MealPlanProcessed:
|
def set_mealplan_dates(meal_plan_base: MealPlanIn) -> MealPlanIn:
|
||||||
meals = []
|
for x, plan_days in enumerate(meal_plan_base.plan_days):
|
||||||
for x, meal in enumerate(meal_plan_base.meals):
|
plan_days: MealDayIn
|
||||||
meal: MealIn
|
|
||||||
try:
|
|
||||||
recipe: Recipe = db.recipes.get(session, meal.slug)
|
|
||||||
|
|
||||||
meal_data = MealOut(
|
plan_days.date = meal_plan_base.start_date + timedelta(days=x)
|
||||||
slug=recipe.slug,
|
|
||||||
name=recipe.name,
|
|
||||||
date=meal_plan_base.startDate + timedelta(days=x),
|
|
||||||
image=recipe.image,
|
|
||||||
description=recipe.description,
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
|
|
||||||
meal_data = MealOut(
|
|
||||||
date=meal_plan_base.startDate + timedelta(days=x),
|
|
||||||
)
|
|
||||||
|
|
||||||
meals.append(meal_data)
|
|
||||||
|
|
||||||
return MealPlanProcessed(
|
|
||||||
group=meal_plan_base.group,
|
|
||||||
meals=meals,
|
|
||||||
startDate=meal_plan_base.startDate,
|
|
||||||
endDate=meal_plan_base.endDate,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_todays_meal(session: Session, group: Union[int, GroupInDB]) -> Recipe:
|
def get_todays_meal(session: Session, group: Union[int, GroupInDB]) -> Recipe:
|
||||||
|
@ -52,22 +28,23 @@ def get_todays_meal(session: Session, group: Union[int, GroupInDB]) -> Recipe:
|
||||||
Returns:
|
Returns:
|
||||||
Recipe: Pydantic Recipe Object
|
Recipe: Pydantic Recipe Object
|
||||||
"""
|
"""
|
||||||
session = session or create_session()
|
|
||||||
|
|
||||||
if isinstance(group, int):
|
return
|
||||||
group: GroupInDB = db.groups.get(session, group)
|
|
||||||
|
|
||||||
today_slug = None
|
# session = session or create_session()
|
||||||
|
|
||||||
for mealplan in group.mealplans:
|
# if isinstance(group, int):
|
||||||
mealplan: MealPlanInDB
|
# group: GroupInDB = db.groups.get(session, group)
|
||||||
for meal in mealplan.meals:
|
|
||||||
meal: MealOut
|
|
||||||
if meal.date == date.today():
|
|
||||||
today_slug = meal.slug
|
|
||||||
break
|
|
||||||
|
|
||||||
if today_slug:
|
# today_slug = None
|
||||||
return db.recipes.get(session, today_slug)
|
|
||||||
else:
|
# for mealplan in group.mealplans:
|
||||||
return None
|
# for meal in mealplan.meals:
|
||||||
|
# if meal.date == date.today():
|
||||||
|
# today_slug = meal.slug
|
||||||
|
# break
|
||||||
|
|
||||||
|
# if today_slug:
|
||||||
|
# return db.recipes.get(session, today_slug)
|
||||||
|
# else:
|
||||||
|
# return None
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue