Merge pull request #2808 from smilerz/add_mealtype_filter

add ability to filter meal plans based on type
This commit is contained in:
vabene1111 2023-12-20 15:55:09 +01:00 committed by GitHub
commit 4de9be5c89
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 3855 deletions

View File

@ -61,6 +61,12 @@ def test_list_filter(obj_1, u1_s1):
response = json.loads(r.content)
assert len(response) == 1
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?meal_type={response[0]["meal_type"]["id"]}').content)
assert len(response) == 1
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?meal_type=0').content)
assert len(response) == 0
response = json.loads(
u1_s1.get(f'{reverse(LIST_URL)}?from_date={(datetime.now() + timedelta(days=2)).strftime("%Y-%m-%d")}').content)
assert len(response) == 0

View File

@ -26,7 +26,7 @@ from django.db.models import Case, Count, Exists, OuterRef, ProtectedError, Q, S
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, HttpResponseRedirect
from django.http import FileResponse, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils import timezone
@ -70,12 +70,13 @@ from cookbook.helper.recipe_url_import import (clean_dict, get_from_youtube_scra
from cookbook.helper.scrapers.scrapers import text_scraper
from cookbook.helper.shopping_helper import RecipeShoppingEditor, shopping_helper
from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilter, ExportLog, Food,
FoodInheritField, ImportLog, Ingredient, InviteLink, Keyword, MealPlan,
MealType, Property, PropertyType, Recipe, RecipeBook, RecipeBookEntry,
ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space,
Step, Storage, Supermarket, SupermarketCategory,
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
UserFile, UserPreference, UserSpace, ViewLog, FoodProperty)
FoodInheritField, FoodProperty, ImportLog, Ingredient, InviteLink,
Keyword, MealPlan, MealType, Property, PropertyType, Recipe,
RecipeBook, RecipeBookEntry, ShareLink, ShoppingList,
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
SyncLog, Unit, UnitConversion, UserFile, UserPreference, UserSpace,
ViewLog)
from cookbook.provider.dropbox import Dropbox
from cookbook.provider.local import Local
from cookbook.provider.nextcloud import Nextcloud
@ -640,9 +641,9 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
Property.objects.filter(space=self.request.space, import_food_id=food.id).update(import_food_id=None)
return self.retrieve(request, pk)
except Exception as e:
except Exception:
traceback.print_exc()
return JsonResponse({'msg': f'there was an error parsing the FDC data, please check the server logs'}, status=500, json_dumps_params={'indent': 4})
return JsonResponse({'msg': 'there was an error parsing the FDC data, please check the server logs'}, status=500, json_dumps_params={'indent': 4})
def destroy(self, *args, **kwargs):
try:
@ -698,11 +699,18 @@ class MealPlanViewSet(viewsets.ModelViewSet):
- **from_date**: filter from (inclusive) a certain date onward
- **to_date**: filter upward to (inclusive) certain date
- **meal_type**: filter meal plans based on meal_type ID
"""
queryset = MealPlan.objects
serializer_class = MealPlanSerializer
permission_classes = [(CustomIsOwner | CustomIsShared) & CustomTokenHasReadWriteScope]
query_params = [
QueryParam(name='from_date', description=_('Filter meal plans from date (inclusive) in the format of YYYY-MM-DD.'), qtype='string'),
QueryParam(name='to_date', description=_('Filter meal plans to date (inclusive) in the format of YYYY-MM-DD.'), qtype='string'),
QueryParam(name='meal_type', description=_('Filter meal plans with MealType ID. For multiple repeat parameter.'), qtype='int'),
]
schema = QueryParamAutoSchema()
def get_queryset(self):
queryset = self.queryset.filter(
@ -717,6 +725,11 @@ class MealPlanViewSet(viewsets.ModelViewSet):
to_date = self.request.query_params.get('to_date', None)
if to_date is not None:
queryset = queryset.filter(to_date__lte=to_date)
meal_type = self.request.query_params.getlist('meal_type', [])
if meal_type:
queryset = queryset.filter(meal_type__in=meal_type)
return queryset

View File

@ -3,8 +3,7 @@
<div id="switcher" class="align-center">
<i class="btn btn-primary fas fa-receipt fa-xl fa-fw shadow-none btn-circle" v-b-toggle.related-recipes />
</div>
<b-sidebar id="related-recipes" backdrop right bottom no-header shadow="sm" style="z-index: 10000"
@shown="updatePinnedRecipes()">
<b-sidebar id="related-recipes" backdrop right bottom no-header shadow="sm" style="z-index: 10000" @shown="updatePinnedRecipes()">
<template #default="{ hide }">
<div class="d-flex flex-column justify-content-end h-100 p-3 align-items-end">
<h5>{{ $t("Planned") }} <i class="fas fa-calendar fa-fw"></i></h5>
@ -36,8 +35,7 @@
<div v-for="r in pinned_recipes" :key="`pin${r.id}`">
<b-row class="pb-1 pt-1">
<b-col cols="2">
<a href="javascript:void(0)" @click="unPinRecipe(r)" class="text-muted"><i
class="fas fa-times"></i></a>
<a href="javascript:void(0)" @click="unPinRecipe(r)" class="text-muted"><i class="fas fa-times"></i></a>
</b-col>
<b-col cols="10">
<a
@ -160,12 +158,14 @@ export default {
// get related recipes and save them for later
if (this.$parent.recipe) {
this.related_recipes = [this.$parent.recipe]
return apiClient.relatedRecipe(this.$parent.recipe.id, {
return apiClient
.relatedRecipe(this.$parent.recipe.id, {
query: {
levels: 2,
format: "json"
}
}).then((result) => {
format: "json",
},
})
.then((result) => {
this.related_recipes = this.related_recipes.concat(result.data)
})
}
@ -179,7 +179,7 @@ export default {
// TODO move to utility function moment is in maintenance mode https://momentjs.com/docs/
var tzoffset = new Date().getTimezoneOffset() * 60000 //offset in milliseconds
let today = new Date(Date.now() - tzoffset).toISOString().split("T")[0]
return apiClient.listMealPlans({query: {from_date: today, to_date: today}}).then((result) => {
return apiClient.listMealPlans(today, today).then((result) => {
let promises = []
result.data.forEach((mealplan) => {
this.planned_recipes.push({ ...mealplan?.recipe, servings: mealplan?.servings })
@ -220,7 +220,6 @@ export default {
z-index: 9000;
}
@media (max-width: 991.98px) {
#switcher .btn-circle {
position: fixed;

View File

@ -1,10 +1,10 @@
import {defineStore} from 'pinia'
import {ApiApiFactory} from "@/utils/openapi/api";
const _STORE_ID = 'meal_plan_store'
const _LOCAL_STORAGE_KEY = 'MEAL_PLAN_CLIENT_SETTINGS'
import { ApiApiFactory } from "@/utils/openapi/api"
import { StandardToasts } from "@/utils/utils"
import { defineStore } from "pinia"
import Vue from "vue"
import {StandardToasts} from "@/utils/utils";
const _STORE_ID = "meal_plan_store"
const _LOCAL_STORAGE_KEY = "MEAL_PLAN_CLIENT_SETTINGS"
/*
* test store to play around with pinia and see if it can work for my usecases
* dont trust that all mealplans are in store as there is no cache validation logic, its just a shared data holder
@ -19,7 +19,7 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
plan_list: function () {
let plan_list = []
for (let key in this.plans) {
plan_list.push(this.plans[key]);
plan_list.push(this.plans[key])
}
return plan_list
},
@ -35,7 +35,7 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
servings: 1,
shared: [],
title: "",
title_placeholder: 'Title', // meal plan edit modal should be improved to not need this
title_placeholder: "Title", // meal plan edit modal should be improved to not need this
}
},
client_settings: function () {
@ -43,22 +43,15 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
this.settings = this.loadClientSettings()
}
return this.settings
}
},
},
actions: {
refreshFromAPI(from_date, to_date) {
if (this.currently_updating !== [from_date, to_date]) {
this.currently_updating = [from_date, to_date] // certainly no perfect check but better than nothing
let options = {
query: {
from_date: from_date,
to_date: to_date,
},
}
let apiClient = new ApiApiFactory()
apiClient.listMealPlans(options).then(r => {
apiClient.listMealPlans(from_date, to_date).then((r) => {
r.data.forEach((p) => {
Vue.set(this.plans, p.id, p)
})
@ -68,29 +61,38 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
},
createObject(object) {
let apiClient = new ApiApiFactory()
return apiClient.createMealPlan(object).then(r => {
return apiClient
.createMealPlan(object)
.then((r) => {
//StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
Vue.set(this.plans, r.data.id, r.data)
return r
}).catch(err => {
})
.catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE, err)
})
},
updateObject(object) {
let apiClient = new ApiApiFactory()
return apiClient.updateMealPlan(object.id, object).then(r => {
return apiClient
.updateMealPlan(object.id, object)
.then((r) => {
//StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
Vue.set(this.plans, object.id, object)
}).catch(err => {
})
.catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
})
},
deleteObject(object) {
let apiClient = new ApiApiFactory()
return apiClient.destroyMealPlan(object.id).then(r => {
return apiClient
.destroyMealPlan(object.id)
.then((r) => {
//StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_DELETE)
Vue.delete(this.plans, object.id)
}).catch(err => {
})
.catch((err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_DELETE, err)
})
},
@ -110,6 +112,6 @@ export const useMealPlanStore = defineStore(_STORE_ID, {
} else {
return JSON.parse(s)
}
}
},
},
})

File diff suppressed because it is too large Load Diff