From 14696e3ce8815652d76ee1738defe41917ce87e4 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Wed, 19 Nov 2025 21:02:16 +0100 Subject: [PATCH] fixed times cooked in saved serarch and improved frontend query binding --- cookbook/helper/recipe_search.py | 10 +++++----- vue3/src/pages/SearchPage.vue | 20 +++++++++++++------- vue3/src/utils/utils.ts | 18 +++++++++++++++++- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index 4507d9241..23fdf7b0b 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -324,9 +324,9 @@ class RecipeSearch(): self._queryset = self._queryset.annotate(recent=Coalesce(Max(Case(When(pk__in=num_recent_recipes.values('recipe'), then='viewlog__pk'))), Value(0))) def _favorite_recipes(self): - if self._sort_includes('favorite') or self._timescooked or self._timescooked_gte or self._timescooked_lte: + if self._sort_includes('favorite') or self._timescooked is not None or self._timescooked_gte is not None or self._timescooked_lte is not None: less_than = self._timescooked_lte and not self._sort_includes('-favorite') - if less_than or self._timescooked == 0: + if less_than: default = 1000 else: default = 0 @@ -338,11 +338,11 @@ class RecipeSearch(): ) self._queryset = self._queryset.annotate(favorite=Coalesce(Subquery(favorite_recipes), default)) - if self._timescooked: + if self._timescooked is not None: self._queryset = self._queryset.filter(favorite=self._timescooked) - elif self._timescooked_lte: + elif self._timescooked_lte is not None: self._queryset = self._queryset.filter(favorite__lte=int(self._timescooked_lte)).exclude(favorite=0) - elif self._timescooked_gte: + elif self._timescooked_gte is not None: self._queryset = self._queryset.filter(favorite__gte=int(self._timescooked_gte)) def keyword_filters(self, **kwargs): diff --git a/vue3/src/pages/SearchPage.vue b/vue3/src/pages/SearchPage.vue index 7456c5c13..de28f877d 100644 --- a/vue3/src/pages/SearchPage.vue +++ b/vue3/src/pages/SearchPage.vue @@ -184,7 +184,7 @@ import RecipeCard from "@/components/display/RecipeCard.vue"; import {useDisplay} from "vuetify"; import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; import {useRouteQuery} from "@vueuse/router"; -import {routeQueryDateTransformer, stringToBool, toNumberArray} from "@/utils/utils"; +import {numberOrUndefinedTransformer, routeQueryDateTransformer, stringToBool, toNumberArray} from "@/utils/utils"; import RandomIcon from "@/components/display/RandomIcon.vue"; import {VSelect, VTextField, VNumberInput} from "vuetify/components"; import RatingField from "@/components/inputs/RatingField.vue"; @@ -759,27 +759,30 @@ const filters = ref({ label: `${t('Rating')} (${t('exact')})`, hint: '', enabled: false, + clearable: true, default: undefined, is: RatingField, - modelValue: useRouteQuery('rating', undefined, {transform: Number}), + modelValue: useRouteQuery('rating', undefined, {transform: numberOrUndefinedTransformer}), }, ratingGte: { id: 'ratingGte', label: `${t('Rating')} (>=)`, hint: '', enabled: false, + clearable: true, default: undefined, is: RatingField, - modelValue: useRouteQuery('ratingGte', undefined, {transform: Number}), + modelValue: useRouteQuery('ratingGte', undefined, {transform: numberOrUndefinedTransformer}), }, ratingLte: { id: 'ratingLte', label: `${t('Rating')} (<=)`, hint: '', enabled: false, + clearable: true, default: undefined, is: RatingField, - modelValue: useRouteQuery('ratingLte', undefined, {transform: Number}), + modelValue: useRouteQuery('ratingLte', undefined, {transform: numberOrUndefinedTransformer}), }, timescooked: { id: 'timescooked', @@ -787,26 +790,29 @@ const filters = ref({ hint: 'Recipes that were cooked at least X times', enabled: false, default: undefined, + clearable: true, is: VNumberInput, - modelValue: useRouteQuery('timescooked', undefined, {transform: Number}), + modelValue: useRouteQuery('timescooked', undefined, {transform: numberOrUndefinedTransformer}), }, timescookedGte: { id: 'timescookedGte', label: `${t('times_cooked')} (>=)`, hint: '', enabled: false, + clearable: true, default: undefined, is: VNumberInput, - modelValue: useRouteQuery('timescookedGte', undefined, {transform: Number}), + modelValue: useRouteQuery('timescookedGte', undefined, {transform: numberOrUndefinedTransformer}), }, timescookedLte: { id: 'timescookedLte', label: `${t('times_cooked')} (<=)`, hint: '', enabled: false, + clearable: true, default: undefined, is: VNumberInput, - modelValue: useRouteQuery('timescookedLte', undefined, {transform: Number}), + modelValue: useRouteQuery('timescookedLte', undefined, {transform: numberOrUndefinedTransformer}), }, makenow: { id: 'makenow', diff --git a/vue3/src/utils/utils.ts b/vue3/src/utils/utils.ts index b5d733795..67cad25ae 100644 --- a/vue3/src/utils/utils.ts +++ b/vue3/src/utils/utils.ts @@ -79,4 +79,20 @@ export function stringToBool(param: string): boolean | undefined { export const routeQueryDateTransformer = { get: (value: string | null | Date) => ((value == null) ? null : (new Date(value))), set: (value: string | null | Date) => ((value == null) ? null : (DateTime.fromJSDate(new Date(value)).toISODate())) -} \ No newline at end of file +} + +/** + * routeQueryParam transformer for boolean fields converting string bools to real bools + */ +export const boolOrUndefinedTransformer = { + get: (value: string | null | undefined) => ((value == null) ? undefined : value == 'true'), + set: (value: boolean | null | undefined) => ((value == null) ? undefined : value.toString()) +} + +/** + * routeQueryParam transformer for number fields converting string numbers to real numbers and allowing undefined for resettable parameters + */ +export const numberOrUndefinedTransformer = { + get: (value: string | null | undefined) => ((value == null) ? undefined : Number(value)), + set: (value: string | null | undefined) => ((value == null) ? undefined : value.toString()) +}