From 689eb426ea5b0e53d2c0e4cc643915d0187c9832 Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Sun, 4 Sep 2022 16:30:36 -0600 Subject: [PATCH 01/10] method for asynchronous generation of meals. start of menu for auto planner. delete method deletes all records for testing the auto planner. --- vue/src/apps/MealPlanView/MealPlanView.vue | 60 ++++- vue/src/components/AutoMealPlanModal.vue | 246 +++++++++++++++++++++ 2 files changed, 302 insertions(+), 4 deletions(-) create mode 100644 vue/src/components/AutoMealPlanModal.vue diff --git a/vue/src/apps/MealPlanView/MealPlanView.vue b/vue/src/apps/MealPlanView/MealPlanView.vue index 0866a293f..14f278583 100644 --- a/vue/src/apps/MealPlanView/MealPlanView.vue +++ b/vue/src/apps/MealPlanView/MealPlanView.vue @@ -208,6 +208,13 @@ @delete-entry="deleteEntry" @reload-meal-types="refreshMealTypes" > +
-
@@ -272,6 +278,8 @@ import VueCookies from "vue-cookies" import {ApiMixin, StandardToasts, ResolveUrlMixin} from "@/utils/utils" import {CalendarView, CalendarMathMixin} from "vue-simple-calendar/src/components/bundle" import {ApiApiFactory} from "@/utils/openapi/api" +import axios from "axios"; +import AutoMealPlanModal from "@/components/AutoMealPlanModal"; const {makeToast} = require("@/utils/utils") @@ -284,6 +292,7 @@ let SETTINGS_COOKIE_NAME = "mealplan_settings" export default { name: "MealPlanView", components: { + AutoMealPlanModal, MealPlanEditModal, MealPlanCard, CalendarView, @@ -546,7 +555,7 @@ export default { }, deleteEntry(data) { this.plan_entries.forEach((entry, index, list) => { - if (entry.id === data.id) { + //if (entry.id === data.id) {//todo:remove block! let apiClient = new ApiApiFactory() apiClient @@ -557,7 +566,7 @@ export default { .catch((err) => { StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) }) - } + //} }) }, entryClick(data) { @@ -634,6 +643,49 @@ export default { entry: plan_entry, } }, + createAutoPlan(date) { + this.$bvModal.show(`autoplan-modal`) + }, + async autoPlanThread(date,dateOffset,i,servings){ + + let apiClient = new ApiApiFactory() + let currentEntry = Object.assign({}, this.options.entryEditing) + currentEntry.date = moment(date).add(dateOffset,"d").format("YYYY-MM-DD") + currentEntry.servings = servings + await Promise.all([ + currentEntry.recipe = await this.randomRecipe(i+3).then((result)=>{return result}), + currentEntry.shared = await apiClient.listUserPreferences().then((result) => {return result.data[0].plan_share}), + currentEntry.meal_type = await this.getMealType(i+2).then((result)=>{return result}) + ]) + currentEntry.title = currentEntry.recipe.name + this.createEntry(currentEntry) + }, + autoPlan(data, servings){ + // ["breakfast","lunch","dinner"] + // meal types: 4,3,2 + //meal keywords: 5,4,3 + for (let i = 0; i < 3; i++) { + for (let dateOffset = 0; dateOffset < 7; dateOffset++) { + this.autoPlanThread(data,dateOffset,i,servings) + } + } + }, + randomRecipe(keyword) { + let url = `/api/recipe/?query=&keywords_or=${keyword}` + return axios.get(url).then((response) => { + let result = response.data + let count = result.count + return result.results[Math.floor(Math.random() * count)] + }).catch((err) => { + + }) + }, + getMealType(id) { + let url = `/api/meal-type/${id}` + return axios.get(url).then((response) => { + return response.data + }) + } }, directives: { hover: { diff --git a/vue/src/components/AutoMealPlanModal.vue b/vue/src/components/AutoMealPlanModal.vue new file mode 100644 index 000000000..1138e67b7 --- /dev/null +++ b/vue/src/components/AutoMealPlanModal.vue @@ -0,0 +1,246 @@ + + + + + From 31f34253540164458f9f95e4b70ca6fc619f5c8a Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Thu, 5 Jan 2023 16:25:42 -0700 Subject: [PATCH 02/10] Menu for auto planner, menu sets auto planner settings. delete method no longer deletes all records for testing the auto planner. --- vue/src/apps/MealPlanView/MealPlanView.vue | 57 ++-- vue/src/components/AutoMealPlanModal.vue | 324 ++++++++------------- 2 files changed, 159 insertions(+), 222 deletions(-) diff --git a/vue/src/apps/MealPlanView/MealPlanView.vue b/vue/src/apps/MealPlanView/MealPlanView.vue index 14f278583..97a4b8513 100644 --- a/vue/src/apps/MealPlanView/MealPlanView.vue +++ b/vue/src/apps/MealPlanView/MealPlanView.vue @@ -209,11 +209,9 @@ @reload-meal-types="refreshMealTypes" > @@ -305,6 +303,14 @@ export default { mixins: [CalendarMathMixin, ApiMixin, ResolveUrlMixin], data: function () { return { + AutoPlan: { + meal_types: [], + keywords: [[]], + servings: 1, + date: Date.now(), + startDay: null, + endDay: null + }, showDate: new Date(), plan_entries: [], recipe_viewed: {}, @@ -555,7 +561,7 @@ export default { }, deleteEntry(data) { this.plan_entries.forEach((entry, index, list) => { - //if (entry.id === data.id) {//todo:remove block! + if (entry.id === data.id) { let apiClient = new ApiApiFactory() apiClient @@ -566,7 +572,7 @@ export default { .catch((err) => { StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err) }) - //} + } }) }, entryClick(data) { @@ -643,35 +649,44 @@ export default { entry: plan_entry, } }, - createAutoPlan(date) { + createAutoPlan() { this.$bvModal.show(`autoplan-modal`) }, - async autoPlanThread(date,dateOffset,i,servings){ + async autoPlanThread(date,dateOffset,meal_type,keywords,servings,mealTypesKey){ let apiClient = new ApiApiFactory() let currentEntry = Object.assign({}, this.options.entryEditing) currentEntry.date = moment(date).add(dateOffset,"d").format("YYYY-MM-DD") currentEntry.servings = servings await Promise.all([ - currentEntry.recipe = await this.randomRecipe(i+3).then((result)=>{return result}), + currentEntry.recipe = await this.randomRecipe(keywords[mealTypesKey]).then((result)=>{return result}), currentEntry.shared = await apiClient.listUserPreferences().then((result) => {return result.data[0].plan_share}), - currentEntry.meal_type = await this.getMealType(i+2).then((result)=>{return result}) + currentEntry.meal_type = await this.getMealType(meal_type[mealTypesKey].id).then((result)=>{return result}) ]) currentEntry.title = currentEntry.recipe.name this.createEntry(currentEntry) }, - autoPlan(data, servings){ - // ["breakfast","lunch","dinner"] - // meal types: 4,3,2 - //meal keywords: 5,4,3 - for (let i = 0; i < 3; i++) { - for (let dateOffset = 0; dateOffset < 7; dateOffset++) { - this.autoPlanThread(data,dateOffset,i,servings) - } - } + doAutoPlan(autoPlan){ + console.log(autoPlan) + let dayInMilliseconds = (86400000) + console.log(autoPlan.startDay) + console.log(autoPlan.endDay) + console.log(autoPlan.endDay - autoPlan.startDay) + console.log(((autoPlan.endDay - autoPlan.startDay)/dayInMilliseconds) + 1) + let numberOfDays = ((autoPlan.endDay - autoPlan.startDay)/dayInMilliseconds) + 1 + + for (const mealTypesKey in autoPlan.meal_types) { + for (let dateOffset = 0; dateOffset < numberOfDays; dateOffset++) { + this.autoPlanThread(autoPlan.date, dateOffset, autoPlan.meal_types, autoPlan.keywords, autoPlan.servings, mealTypesKey) + } + } }, - randomRecipe(keyword) { - let url = `/api/recipe/?query=&keywords_or=${keyword}` + randomRecipe(keywords) { + let url = "/api/recipe/?query=" + for (const keywordsKey in keywords) { + let keyword = keywords[keywordsKey] + url += `&keywords_and=${keyword.id}` + } return axios.get(url).then((response) => { let result = response.data let count = result.count diff --git a/vue/src/components/AutoMealPlanModal.vue b/vue/src/components/AutoMealPlanModal.vue index 1138e67b7..339427aa7 100644 --- a/vue/src/components/AutoMealPlanModal.vue +++ b/vue/src/components/AutoMealPlanModal.vue @@ -1,245 +1,167 @@ From 4a390b58246c8f09d5b95c5b352c8319ef31ae49 Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Sun, 8 Jan 2023 12:01:59 -0700 Subject: [PATCH 03/10] removed logging --- vue/src/apps/MealPlanView/MealPlanView.vue | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vue/src/apps/MealPlanView/MealPlanView.vue b/vue/src/apps/MealPlanView/MealPlanView.vue index 97a4b8513..e4a88470a 100644 --- a/vue/src/apps/MealPlanView/MealPlanView.vue +++ b/vue/src/apps/MealPlanView/MealPlanView.vue @@ -667,12 +667,7 @@ export default { this.createEntry(currentEntry) }, doAutoPlan(autoPlan){ - console.log(autoPlan) let dayInMilliseconds = (86400000) - console.log(autoPlan.startDay) - console.log(autoPlan.endDay) - console.log(autoPlan.endDay - autoPlan.startDay) - console.log(((autoPlan.endDay - autoPlan.startDay)/dayInMilliseconds) + 1) let numberOfDays = ((autoPlan.endDay - autoPlan.startDay)/dayInMilliseconds) + 1 for (const mealTypesKey in autoPlan.meal_types) { From 6c9227faac059fa0c7b9b77a54ace3356d95fc9d Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Thu, 18 May 2023 11:14:59 -0600 Subject: [PATCH 04/10] fixed formatting and minor bug causeing the start of the period to always be the current day. --- .idea/recipes.iml | 2 +- vue/src/apps/MealPlanView/MealPlanView.vue | 89 ++++++++++++---------- 2 files changed, 48 insertions(+), 43 deletions(-) diff --git a/.idea/recipes.iml b/.idea/recipes.iml index 8a0e59c8e..1b96c9d80 100644 --- a/.idea/recipes.iml +++ b/.idea/recipes.iml @@ -18,7 +18,7 @@ - + diff --git a/vue/src/apps/MealPlanView/MealPlanView.vue b/vue/src/apps/MealPlanView/MealPlanView.vue index 7652c4bd7..c0d027a59 100644 --- a/vue/src/apps/MealPlanView/MealPlanView.vue +++ b/vue/src/apps/MealPlanView/MealPlanView.vue @@ -290,6 +290,9 @@ + {{ $t("Export_To_ICal") }} @@ -302,11 +305,7 @@ {{ $t("Create") }} -
- -
+ @@ -677,53 +676,59 @@ export default { this.$bvModal.show(`id_meal_plan_edit_modal`) }) - } + }, createAutoPlan() { this.$bvModal.show(`autoplan-modal`) }, - async autoPlanThread(date,dateOffset,meal_type,keywords,servings,mealTypesKey){ + async autoPlanThread(date, dateOffset, meal_type, keywords, servings, mealTypesKey) { - let apiClient = new ApiApiFactory() - let currentEntry = Object.assign({}, this.options.entryEditing) - currentEntry.date = moment(date).add(dateOffset,"d").format("YYYY-MM-DD") - currentEntry.servings = servings - await Promise.all([ - currentEntry.recipe = await this.randomRecipe(keywords[mealTypesKey]).then((result)=>{return result}), - currentEntry.shared = await apiClient.listUserPreferences().then((result) => {return result.data[0].plan_share}), - currentEntry.meal_type = await this.getMealType(meal_type[mealTypesKey].id).then((result)=>{return result}) - ]) - currentEntry.title = currentEntry.recipe.name - this.createEntry(currentEntry) - }, - doAutoPlan(autoPlan){ - let dayInMilliseconds = (86400000) - let numberOfDays = ((autoPlan.endDay - autoPlan.startDay)/dayInMilliseconds) + 1 + let apiClient = new ApiApiFactory() + let currentEntry = Object.assign({}, this.options.entryEditing) + currentEntry.date = moment(date).add(dateOffset, "d").format("YYYY-MM-DD") + currentEntry.servings = servings + await Promise.all([ + currentEntry.recipe = await this.randomRecipe(keywords[mealTypesKey]).then((result) => { + return result + }), + currentEntry.shared = await apiClient.listUserPreferences().then((result) => { + return result.data[0].plan_share + }), + currentEntry.meal_type = await this.getMealType(meal_type[mealTypesKey].id).then((result) => { + return result + }) + ]) + currentEntry.title = currentEntry.recipe.name + this.createEntry(currentEntry) + }, + doAutoPlan(autoPlan) { + let dayInMilliseconds = (86400000) + let numberOfDays = ((autoPlan.endDay - autoPlan.startDay) / dayInMilliseconds) + 1 - for (const mealTypesKey in autoPlan.meal_types) { - for (let dateOffset = 0; dateOffset < numberOfDays; dateOffset++) { - this.autoPlanThread(autoPlan.date, dateOffset, autoPlan.meal_types, autoPlan.keywords, autoPlan.servings, mealTypesKey) - } - } + for (const mealTypesKey in autoPlan.meal_types) { + for (let dateOffset = 0; dateOffset < numberOfDays; dateOffset++) { + this.autoPlanThread(autoPlan.startDay, dateOffset, autoPlan.meal_types, autoPlan.keywords, autoPlan.servings, mealTypesKey) + } + } }, randomRecipe(keywords) { - let url = "/api/recipe/?query=" - for (const keywordsKey in keywords) { - let keyword = keywords[keywordsKey] - url += `&keywords_and=${keyword.id}` - } - return axios.get(url).then((response) => { - let result = response.data - let count = result.count - return result.results[Math.floor(Math.random() * count)] - }).catch((err) => { + let url = "/api/recipe/?query=" + for (const keywordsKey in keywords) { + let keyword = keywords[keywordsKey] + url += `&keywords_and=${keyword.id}` + } + return axios.get(url).then((response) => { + let result = response.data + let count = result.count + return result.results[Math.floor(Math.random() * count)] + }).catch((err) => { - }) + }) }, getMealType(id) { - let url = `/api/meal-type/${id}` - return axios.get(url).then((response) => { - return response.data - }) + let url = `/api/meal-type/${id}` + return axios.get(url).then((response) => { + return response.data + }) } }, directives: { From ee38d93e3b83f334adf907b7fe1cd615b1c2f4a3 Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Wed, 21 Jun 2023 19:31:49 -0600 Subject: [PATCH 05/10] Created auto meal plan api endpoint. --- cookbook/serializer.py | 11 ++++++++ cookbook/urls.py | 1 + cookbook/views/api.py | 57 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 54ef43875..505f4e8ba 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -1,3 +1,4 @@ +import random import traceback import uuid from datetime import datetime, timedelta @@ -979,6 +980,16 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer): read_only_fields = ('created_by',) +class AutoMealPlanSerializer(serializers.Serializer): + + start_date = serializers.DateField() + end_date = serializers.DateField() + meal_type_id = serializers.IntegerField() + keywords = KeywordSerializer(many=True) + servings = CustomDecimalField() + shared = UserSerializer(many=True, required=False, allow_null=True) + + class ShoppingListRecipeSerializer(serializers.ModelSerializer): name = serializers.SerializerMethodField('get_name') # should this be done at the front end? recipe_name = serializers.ReadOnlyField(source='recipe.name') diff --git a/cookbook/urls.py b/cookbook/urls.py index 123a5083b..f7e087e21 100644 --- a/cookbook/urls.py +++ b/cookbook/urls.py @@ -36,6 +36,7 @@ router.register(r'ingredient', api.IngredientViewSet) router.register(r'invite-link', api.InviteLinkViewSet) router.register(r'keyword', api.KeywordViewSet) router.register(r'meal-plan', api.MealPlanViewSet) +router.register(r'auto-plan', api.AutoPlanViewSet, basename='auto-plan') router.register(r'meal-type', api.MealTypeViewSet) router.register(r'recipe', api.RecipeViewSet) router.register(r'recipe-book', api.RecipeBookViewSet) diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 9e428e8ff..749101741 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -1,7 +1,9 @@ +import datetime import io import json import mimetypes import pathlib +import random import re import threading import traceback @@ -25,6 +27,7 @@ from django.core.files import File from django.db.models import Case, Count, Exists, OuterRef, ProtectedError, Q, Subquery, Value, When, Avg, Max from django.db.models.fields.related import ForeignObjectRel from django.db.models.functions import Coalesce, Lower +from django.db.models.signals import post_save from django.http import FileResponse, HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect from django.urls import reverse @@ -94,7 +97,8 @@ from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSeri SyncLogSerializer, SyncSerializer, UnitSerializer, UserFileSerializer, UserSerializer, UserPreferenceSerializer, UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer, FoodSimpleSerializer, - RecipeExportSerializer, UnitConversionSerializer, PropertyTypeSerializer, PropertySerializer) + RecipeExportSerializer, UnitConversionSerializer, PropertyTypeSerializer, + PropertySerializer, AutoMealPlanSerializer) from cookbook.views.import_export import get_integration from recipes import settings @@ -661,6 +665,57 @@ class MealPlanViewSet(viewsets.ModelViewSet): return queryset +class AutoPlanViewSet(viewsets.ViewSet): + def create(self, request): + serializer = AutoMealPlanSerializer(data=request.data) + + if serializer.is_valid(): + keywords = serializer.validated_data['keywords'] + start_date = serializer.validated_data['start_date'] + end_date = serializer.validated_data['end_date'] + meal_type = MealType.objects.get(pk=serializer.validated_data['meal_type_id']) + servings = serializer.validated_data['servings'] + + days = (end_date - start_date).days + 1 + recipes = Recipe.objects.all() + meal_plans = list() + + for keyword in keywords: + recipes = recipes.filter(keywords__name=keyword['name']) + + recipes = recipes.order_by('?')[:days] + recipes = list(recipes) + + for i in range(0, days): + day = start_date + datetime.timedelta(i) + recipe = random.choice(recipes) + args = {'recipe': recipe, 'servings': servings, 'title': recipe.name, + 'created_by': request.user, + 'meal_type': meal_type, + 'note': '', 'date': day, 'space': request.space} + + m = MealPlan(**args) + meal_plans.append(m) + + MealPlan.objects.bulk_create(meal_plans) + for m in meal_plans: + if request.data.get('addshopping', False) and request.data.get('recipe', None): + SLR = RecipeShoppingEditor(user=request.user, space=request.space) + SLR.create(mealplan=m, servings=servings) + + else: + post_save.send( + sender=m.__class__, + instance=m, + created=True, + update_fields=None, + ) + + return Response(serializer.data) + + return Response(serializer.errors, 400) + + class MealTypeViewSet(viewsets.ModelViewSet): """ returns list of meal types created by the From 9756b7b65320bef16535ce447e90c882dac02647 Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Wed, 21 Jun 2023 19:32:54 -0600 Subject: [PATCH 06/10] regenerated open api file --- vue/src/utils/openapi/api.ts | 63 ++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/vue/src/utils/openapi/api.ts b/vue/src/utils/openapi/api.ts index 70d6a625e..42b44d979 100644 --- a/vue/src/utils/openapi/api.ts +++ b/vue/src/utils/openapi/api.ts @@ -4355,6 +4355,39 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * + * @param {any} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createAutoPlanViewSet: async (body?: any, options: any = {}): Promise => { + const localVarPath = `/api/auto-plan/`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter, options.query); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(body, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * * @param {Automation} [automation] @@ -12199,6 +12232,16 @@ export const ApiApiFp = function(configuration?: Configuration) { const localVarAxiosArgs = await localVarAxiosParamCreator.createAccessToken(accessToken, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, + /** + * + * @param {any} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async createAutoPlanViewSet(body?: any, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.createAutoPlanViewSet(body, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, /** * * @param {Automation} [automation] @@ -14529,6 +14572,15 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?: createAccessToken(accessToken?: AccessToken, options?: any): AxiosPromise { return localVarFp.createAccessToken(accessToken, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {any} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + createAutoPlanViewSet(body?: any, options?: any): AxiosPromise { + return localVarFp.createAutoPlanViewSet(body, options).then((request) => request(axios, basePath)); + }, /** * * @param {Automation} [automation] @@ -16642,6 +16694,17 @@ export class ApiApi extends BaseAPI { return ApiApiFp(this.configuration).createAccessToken(accessToken, options).then((request) => request(this.axios, this.basePath)); } + /** + * + * @param {any} [body] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof ApiApi + */ + public createAutoPlanViewSet(body?: any, options?: any) { + return ApiApiFp(this.configuration).createAutoPlanViewSet(body, options).then((request) => request(this.axios, this.basePath)); + } + /** * * @param {Automation} [automation] From ac17b84a7ae7a086e0b9866207661258418fb669 Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Wed, 21 Jun 2023 19:35:48 -0600 Subject: [PATCH 07/10] updated auto meal plan to start at the current day, and exclude a meal plan if it has no keywords. Added debug buttons to help with testing. --- vue/src/apps/MealPlanView/MealPlanView.vue | 75 +++++++++------------- vue/src/components/AutoMealPlanModal.vue | 9 ++- 2 files changed, 34 insertions(+), 50 deletions(-) diff --git a/vue/src/apps/MealPlanView/MealPlanView.vue b/vue/src/apps/MealPlanView/MealPlanView.vue index c0d027a59..9e34cb718 100644 --- a/vue/src/apps/MealPlanView/MealPlanView.vue +++ b/vue/src/apps/MealPlanView/MealPlanView.vue @@ -293,6 +293,12 @@ + + {{ $t("Export_To_ICal") }} @@ -680,55 +686,34 @@ export default { createAutoPlan() { this.$bvModal.show(`autoplan-modal`) }, - async autoPlanThread(date, dateOffset, meal_type, keywords, servings, mealTypesKey) { - + async autoPlanThread(autoPlan, mealTypeIndex) { let apiClient = new ApiApiFactory() - let currentEntry = Object.assign({}, this.options.entryEditing) - currentEntry.date = moment(date).add(dateOffset, "d").format("YYYY-MM-DD") - currentEntry.servings = servings - await Promise.all([ - currentEntry.recipe = await this.randomRecipe(keywords[mealTypesKey]).then((result) => { - return result - }), - currentEntry.shared = await apiClient.listUserPreferences().then((result) => { - return result.data[0].plan_share - }), - currentEntry.meal_type = await this.getMealType(meal_type[mealTypesKey].id).then((result) => { - return result - }) - ]) - currentEntry.title = currentEntry.recipe.name - this.createEntry(currentEntry) - }, - doAutoPlan(autoPlan) { - let dayInMilliseconds = (86400000) - let numberOfDays = ((autoPlan.endDay - autoPlan.startDay) / dayInMilliseconds) + 1 - - for (const mealTypesKey in autoPlan.meal_types) { - for (let dateOffset = 0; dateOffset < numberOfDays; dateOffset++) { - this.autoPlanThread(autoPlan.startDay, dateOffset, autoPlan.meal_types, autoPlan.keywords, autoPlan.servings, mealTypesKey) - } + let data = { + "start_date" : moment(autoPlan.startDay).format("YYYY-MM-DD"), + "end_date" : moment(autoPlan.endDay).format("YYYY-MM-DD"), + "meal_type_id" : autoPlan.meal_types[mealTypeIndex].id, + "keywords" : autoPlan.keywords[mealTypeIndex], + "servings" : autoPlan.servings, + "shared" : autoPlan.shared } - }, - randomRecipe(keywords) { - let url = "/api/recipe/?query=" - for (const keywordsKey in keywords) { - let keyword = keywords[keywordsKey] - url += `&keywords_and=${keyword.id}` - } - return axios.get(url).then((response) => { - let result = response.data - let count = result.count - return result.results[Math.floor(Math.random() * count)] - }).catch((err) => { + await apiClient.createAutoPlanViewSet(data) - }) }, - getMealType(id) { - let url = `/api/meal-type/${id}` - return axios.get(url).then((response) => { - return response.data - }) + async doAutoPlan(autoPlan) { + for (let i = 0; i < autoPlan.meal_types.length; i++) { + if (autoPlan.keywords[i].length === 0) continue + await this.autoPlanThread(autoPlan, i) + } + this.refreshEntries() + }, + refreshEntries(){//todo Remove method + let date = this.current_period + useMealPlanStore().refreshFromAPI(moment(date.periodStart).format("YYYY-MM-DD"), moment(date.periodEnd).format("YYYY-MM-DD")) + }, + deleteAll(){//todo Remove method, only used in debugging + for (let i = 0; i < useMealPlanStore().plan_list.length; i++) { + useMealPlanStore().deleteObject(useMealPlanStore().plan_list[i]) + } } }, directives: { diff --git a/vue/src/components/AutoMealPlanModal.vue b/vue/src/components/AutoMealPlanModal.vue index 339427aa7..2d8084b33 100644 --- a/vue/src/components/AutoMealPlanModal.vue +++ b/vue/src/components/AutoMealPlanModal.vue @@ -21,7 +21,7 @@ :initial_selection="AutoPlan.keywords[meal_type]" :parent_variable="`${k}`" :model="Models.KEYWORD" - :placeholder="$t('Keywords')" + :placeholder="$t('Keywords, leave blank to exclude meal type')" :limit="50" /> @@ -35,9 +35,9 @@
- +
- +
{{ $t("Start Day") }} @@ -102,7 +102,7 @@ export default { this.refreshMealTypes() this.AutoPlan.servings = 1 - this.AutoPlan.startDay = this.current_period.periodStart + this.AutoPlan.startDay = new Date() this.AutoPlan.endDay = this.current_period.periodEnd }, sortMealTypes() { @@ -148,7 +148,6 @@ export default { }, updateStartDay(date){ this.AutoPlan.startDay = date - console.log(date) }, updateEndDay(date){ this.AutoPlan.endDay = date From df684f591a5bafc5c1cc0551a31b515747f2292f Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Tue, 1 Aug 2023 17:02:05 -0600 Subject: [PATCH 08/10] added share functionality. changed random recipe selection to prevent repeating duplicate choices. --- cookbook/views/api.py | 10 +++++++++- vue/src/apps/MealPlanView/MealPlanView.vue | 3 ++- vue/src/components/AutoMealPlanModal.vue | 22 +++++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 749101741..7ebe76c53 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -675,6 +675,11 @@ class AutoPlanViewSet(viewsets.ViewSet): end_date = serializer.validated_data['end_date'] meal_type = MealType.objects.get(pk=serializer.validated_data['meal_type_id']) servings = serializer.validated_data['servings'] + shared = serializer.get_initial().get('shared', None) + shared_pks = list() + if shared is not None: + for i in range(len(shared)): + shared_pks.append(shared[i]['id']) days = (end_date - start_date).days + 1 recipes = Recipe.objects.all() @@ -688,7 +693,7 @@ class AutoPlanViewSet(viewsets.ViewSet): for i in range(0, days): day = start_date + datetime.timedelta(i) - recipe = random.choice(recipes) + recipe = recipes[i % len(recipes)] args = {'recipe': recipe, 'servings': servings, 'title': recipe.name, 'created_by': request.user, 'meal_type': meal_type, @@ -698,7 +703,10 @@ class AutoPlanViewSet(viewsets.ViewSet): meal_plans.append(m) MealPlan.objects.bulk_create(meal_plans) + for m in meal_plans: + m.shared.set(shared_pks) + if request.data.get('addshopping', False) and request.data.get('recipe', None): SLR = RecipeShoppingEditor(user=request.user, space=request.space) SLR.create(mealplan=m, servings=servings) diff --git a/vue/src/apps/MealPlanView/MealPlanView.vue b/vue/src/apps/MealPlanView/MealPlanView.vue index 9e34cb718..4c5d35791 100644 --- a/vue/src/apps/MealPlanView/MealPlanView.vue +++ b/vue/src/apps/MealPlanView/MealPlanView.vue @@ -371,7 +371,8 @@ export default { servings: 1, date: Date.now(), startDay: null, - endDay: null + endDay: null, + shared: [] }, showDate: new Date(), plan_entries: [], diff --git a/vue/src/components/AutoMealPlanModal.vue b/vue/src/components/AutoMealPlanModal.vue index 2d8084b33..c559f32dc 100644 --- a/vue/src/components/AutoMealPlanModal.vue +++ b/vue/src/components/AutoMealPlanModal.vue @@ -32,6 +32,21 @@ {{ $t("Servings") }}
+ + + {{ $t("Share") }} +
@@ -61,6 +76,7 @@ import Vue from "vue" import {BootstrapVue} from "bootstrap-vue" import GenericMultiselect from "@/components/GenericMultiselect" import {ApiMixin} from "@/utils/utils" +import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; const { ApiApiFactory } = require("@/utils/openapi/api") const { StandardToasts } = require("@/utils/utils") @@ -89,7 +105,8 @@ export default { servings: 1, date: Date.now(), startDay: null, - endDay: null + endDay: null, + shared: [] } } }, @@ -104,6 +121,9 @@ export default { this.AutoPlan.servings = 1 this.AutoPlan.startDay = new Date() this.AutoPlan.endDay = this.current_period.periodEnd + useUserPreferenceStore().getData().then(userPreference => { + this.AutoPlan.shared = userPreference.plan_share + }) }, sortMealTypes() { this.meal_types.forEach(function (element, index) { From ecd828008e49fbecb4733c7d9cdfff573e693485 Mon Sep 17 00:00:00 2001 From: AquaticLava Date: Tue, 1 Aug 2023 21:52:59 -0600 Subject: [PATCH 09/10] added auto shopping functionality. fixed bug when there are no matching recipes --- cookbook/serializer.py | 1 + cookbook/views/api.py | 4 ++- vue/src/apps/MealPlanView/MealPlanView.vue | 6 ++-- vue/src/components/AutoMealPlanModal.vue | 38 ++++++++++++++++++++-- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 505f4e8ba..15121d17e 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -988,6 +988,7 @@ class AutoMealPlanSerializer(serializers.Serializer): keywords = KeywordSerializer(many=True) servings = CustomDecimalField() shared = UserSerializer(many=True, required=False, allow_null=True) + addshopping = serializers.BooleanField() class ShoppingListRecipeSerializer(serializers.ModelSerializer): diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 7ebe76c53..b3a8c7625 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -688,6 +688,8 @@ class AutoPlanViewSet(viewsets.ViewSet): for keyword in keywords: recipes = recipes.filter(keywords__name=keyword['name']) + if len(recipes) == 0: + return Response(serializer.data) recipes = recipes.order_by('?')[:days] recipes = list(recipes) @@ -707,7 +709,7 @@ class AutoPlanViewSet(viewsets.ViewSet): for m in meal_plans: m.shared.set(shared_pks) - if request.data.get('addshopping', False) and request.data.get('recipe', None): + if request.data.get('addshopping', False): SLR = RecipeShoppingEditor(user=request.user, space=request.space) SLR.create(mealplan=m, servings=servings) diff --git a/vue/src/apps/MealPlanView/MealPlanView.vue b/vue/src/apps/MealPlanView/MealPlanView.vue index 4c5d35791..2763ac0d6 100644 --- a/vue/src/apps/MealPlanView/MealPlanView.vue +++ b/vue/src/apps/MealPlanView/MealPlanView.vue @@ -372,7 +372,8 @@ export default { date: Date.now(), startDay: null, endDay: null, - shared: [] + shared: [], + addshopping: false }, showDate: new Date(), plan_entries: [], @@ -695,7 +696,8 @@ export default { "meal_type_id" : autoPlan.meal_types[mealTypeIndex].id, "keywords" : autoPlan.keywords[mealTypeIndex], "servings" : autoPlan.servings, - "shared" : autoPlan.shared + "shared" : autoPlan.shared, + "addshopping": autoPlan.addshopping } await apiClient.createAutoPlanViewSet(data) diff --git a/vue/src/components/AutoMealPlanModal.vue b/vue/src/components/AutoMealPlanModal.vue index c559f32dc..a2997c561 100644 --- a/vue/src/components/AutoMealPlanModal.vue +++ b/vue/src/components/AutoMealPlanModal.vue @@ -47,6 +47,12 @@ > {{ $t("Share") }} + + + {{ + $t("AddToShopping") + }} +
@@ -77,11 +83,14 @@ import {BootstrapVue} from "bootstrap-vue" import GenericMultiselect from "@/components/GenericMultiselect" import {ApiMixin} from "@/utils/utils" import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; +import VueCookies from "vue-cookies"; const { ApiApiFactory } = require("@/utils/openapi/api") const { StandardToasts } = require("@/utils/utils") Vue.use(BootstrapVue) +Vue.use(VueCookies) +let MEALPLAN_COOKIE_NAME = "mealplan_settings" export default { name: "AutoMealPlanModal", @@ -106,16 +115,38 @@ export default { date: Date.now(), startDay: null, endDay: null, - shared: [] - } + shared: [], + addshopping: false + }, + mealplan_settings: { + addshopping: false, + } } }, - mounted: function () {}, + watch: { + mealplan_settings: { + handler(newVal) { + this.$cookies.set(MEALPLAN_COOKIE_NAME, this.mealplan_settings) + }, + deep: true, + }, + }, + mounted: function () { + useUserPreferenceStore().updateIfStaleOrEmpty() + }, + computed: { + autoMealPlan: function () { + return useUserPreferenceStore().getStaleData()?.mealplan_autoadd_shopping + }, + }, methods: { genericSelectChanged: function (obj) { this.AutoPlan.keywords[obj.var] = obj.val }, showModal() { + if (this.$cookies.isKey(MEALPLAN_COOKIE_NAME)) { + this.mealplan_settings = Object.assign({}, this.mealplan_settings, this.$cookies.get(MEALPLAN_COOKIE_NAME)) + } this.refreshMealTypes() this.AutoPlan.servings = 1 @@ -164,6 +195,7 @@ export default { }, createPlan() { this.$bvModal.hide(`autoplan-modal`) + this.AutoPlan.addshopping = this.mealplan_settings.addshopping this.$emit("create-plan", this.AutoPlan) }, updateStartDay(date){ From ffc96890acf48b02ebe0cd7295b557b341b72cad Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Wed, 16 Aug 2023 06:18:02 +0200 Subject: [PATCH 10/10] Delete recipes.iml --- .idea/recipes.iml | 35 ----------------------------------- 1 file changed, 35 deletions(-) delete mode 100644 .idea/recipes.iml diff --git a/.idea/recipes.iml b/.idea/recipes.iml deleted file mode 100644 index 1b96c9d80..000000000 --- a/.idea/recipes.iml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file