diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 010439b17..f991db85e 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -16,6 +16,7 @@ import PIL.Image import redis import requests from PIL import UnidentifiedImageError +from PIL.ImImagePlugin import number from PIL.features import check from django.contrib import messages from django.contrib.auth.models import Group, User @@ -1361,6 +1362,9 @@ class PropertyViewSet(LoggingMixin, viewsets.ModelViewSet): return self.queryset.filter(space=self.request.space) +@extend_schema_view(list=extend_schema(parameters=[ + OpenApiParameter(name='mealplan', description=_('Returns only entries associated with the given mealplan id'), type=int) +])) class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet): queryset = ShoppingListRecipe.objects serializer_class = ShoppingListRecipeSerializer @@ -1369,6 +1373,14 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet): def get_queryset(self): self.queryset = self.queryset.filter(Q(entries__space=self.request.space) | Q(recipe__space=self.request.space)) + + # TODO implement test for this + if not self.detail: + mealplan = self.request.query_params.get('mealplan', None) + + if mealplan is not None: + self.queryset = self.queryset.filter(mealplan_id=mealplan) + return self.queryset.filter(Q(entries__isnull=True) | Q(entries__created_by=self.request.user) | Q(entries__created_by__in=list(self.request.user.get_shopping_share()))).distinct().all() @@ -1405,6 +1417,7 @@ class ShoppingListRecipeViewSet(LoggingMixin, viewsets.ModelViewSet): OpenApiParameter(name='updated_after', description=_('Returns only elements updated after the given timestamp in ISO 8601 format.'), type=datetime.datetime), + OpenApiParameter(name='mealplan', description=_('Returns only entries associated with the given mealplan id'), type=int) ])) class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet): """ @@ -1435,6 +1448,7 @@ class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet): ).distinct().all() updated_after = self.request.query_params.get('updated_after', None) + mealplan = self.request.query_params.get('mealplan', None) if not self.detail: # to keep the endpoint small, only return entries as old as user preference recent days @@ -1442,10 +1456,12 @@ class ShoppingListEntryViewSet(LoggingMixin, viewsets.ModelViewSet): week_ago = today_start - datetime.timedelta(days=min(self.request.user.userpreference.shopping_recent_days, 14)) self.queryset = self.queryset.filter((Q(checked=False) | Q(completed_at__gte=week_ago))) + if mealplan is not None: + self.queryset = self.queryset.filter(list_recipe__mealplan_id=mealplan) + try: if updated_after: updated_after = parse_datetime(updated_after) - print('adding filter updated_after', updated_after) self.queryset = self.queryset.filter(updated_at__gte=updated_after) except Exception: traceback.print_exc() @@ -1588,9 +1604,8 @@ class AutomationViewSet(LoggingMixin, StandardFilterModelViewSet): return self.queryset.filter(space=self.request.space).all() -# TODO explain what internal_note is for @extend_schema_view(list=extend_schema(parameters=[ - OpenApiParameter(name='internal_note', description=_('I have no idea what internal_note is for.'), type=str) + OpenApiParameter(name='internal_note', description=_('Text field to store data that gets carried over to the UserSpace created from the InviteLink'), type=str) ])) class InviteLinkViewSet(LoggingMixin, StandardFilterModelViewSet): queryset = InviteLink.objects @@ -1806,7 +1821,7 @@ class ImageToRecipeView(APIView): """ serializer = ImportImageSerializer(data=request.data, partial=True) if serializer.is_valid(): - #generativeai.configure(api_key=GOOGLE_AI_API_KEY) + # generativeai.configure(api_key=GOOGLE_AI_API_KEY) # model = generativeai.GenerativeModel('gemini-1.5-flash-latest') # img = PIL.Image.open('') diff --git a/vue3/src/components/display/ShoppingLineItem.vue b/vue3/src/components/display/ShoppingLineItem.vue index 310ed3f60..8a7925586 100644 --- a/vue3/src/components/display/ShoppingLineItem.vue +++ b/vue3/src/components/display/ShoppingLineItem.vue @@ -7,14 +7,13 @@ -
- + {{ $n(a.amount) }} {{ a.unit.name }} @@ -25,7 +24,7 @@
-
+
{{ shoppingListFood.food.name }}
{{ infoRow }}
@@ -62,8 +61,12 @@ import {isDelayed, isShoppingListFoodDelayed} from "@/utils/logic_utils"; const emit = defineEmits(['clicked']) const props = defineProps({ - entries: {type: Array as PropType>, required: true}, shoppingListFood: {type: {} as PropType, required: true}, + hideInfoRow: {type: Boolean, default: false} +}) + +const entries = computed(() => { + return Array.from(props.shoppingListFood.entries.values()) }) /** @@ -71,7 +74,7 @@ const props = defineProps({ */ const itemContainerId = computed(() => { let id = 'id_sli_' - for (let i in props.entries) { + for (let i in entries.value) { id += i + '_' } return id @@ -82,8 +85,8 @@ const itemContainerId = computed(() => { * tests if all entries of the given food are checked */ const isChecked = computed(() => { - for (let i in props.entries) { - if (!props.entries[i].checked) { + for (let i in entries.value) { + if (!entries.value[i].checked) { return false } } @@ -101,7 +104,7 @@ const isShoppingLineDelayed = computed(() => { * style action button depending on if all items are checked or not */ const actionButtonIcon = computed(() => { - if (isChecked.value){ + if (isChecked.value) { return 'fa-solid fa-plus' } return 'fa-solid fa-check' @@ -116,8 +119,8 @@ const actionButtonIcon = computed(() => { const amounts = computed((): Map => { let unitAmounts = new Map() - for (let i in props.entries) { - let e = props.entries[i] + for (let i in entries.value) { + let e = entries.value[i] if (!e.checked && !isDelayed(e) || (e.checked && useUserPreferenceStore().deviceSettings.shopping_show_checked_entries) @@ -147,15 +150,22 @@ const amounts = computed((): Map => { return unitAmounts }) +/** + * compute the second (info) row of the line item based on the entries and the device settings + */ const infoRow = computed(() => { + if(props.hideInfoRow){ + return '' + } + let info_row = [] let authors = [] let recipes = [] let meal_pans = [] - for (let i in props.entries) { - let e = props.entries[i] + for (let i in entries.value) { + let e = entries.value[i] if (authors.indexOf(e.createdBy.displayName) === -1) { authors.push(e.createdBy.displayName) @@ -203,7 +213,7 @@ function setFoodIgnoredAndChecked(food: Food) { useMessageStore().addError(ErrorMessageType.UPDATE_ERROR, err) }) - useShoppingStore().setEntriesCheckedState(props.entries, true, false) + useShoppingStore().setEntriesCheckedState(entries.value, true, false) } /** diff --git a/vue3/src/components/display/ShoppingListView.vue b/vue3/src/components/display/ShoppingListView.vue index 21160e6bf..6c2946002 100644 --- a/vue3/src/components/display/ShoppingListView.vue +++ b/vue3/src/components/display/ShoppingListView.vue @@ -109,7 +109,7 @@ diff --git a/vue3/src/components/model_editors/MealPlanEditor.vue b/vue3/src/components/model_editors/MealPlanEditor.vue index 17791690b..580a04cda 100644 --- a/vue3/src/components/model_editors/MealPlanEditor.vue +++ b/vue3/src/components/model_editors/MealPlanEditor.vue @@ -8,54 +8,89 @@ :is-update="isUpdate()" :model-class="modelClass" :object-name="editingObjName()"> + + + {{ $t('Meal_Plan') }} + {{ $t('Shopping_list') }} + + - - - - - + + + - - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - @@ -64,7 +99,7 @@