diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index 9b8154936..074d4ea96 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -49,7 +49,11 @@ class RecipeSearch(): self._search_prefs = SearchPreference() self._string = self._params.get('query').strip( ) if self._params.get('query', None) else None + self._rating = self._params.get('rating', None) + self._rating_gte = self._params.get('rating_gte', None) + self._rating_lte = self._params.get('rating_lte', None) + self._keywords = { 'or': self._params.get('keywords_or', None) or self._params.get('keywords', None), 'and': self._params.get('keywords_and', None), @@ -140,7 +144,7 @@ class RecipeSearch(): self.keyword_filters(**self._keywords) self.food_filters(**self._foods) self.book_filters(**self._books) - self.rating_filter(rating=self._rating) + self.rating_filter() self.internal_filter(internal=self._internal) self.step_filters(steps=self._steps) self.unit_filters(units=self._units) @@ -413,25 +417,16 @@ class RecipeSearch(): units = [units] self._queryset = self._queryset.filter(steps__ingredients__unit__in=units) - def rating_filter(self, rating=None): - if rating or self._sort_includes('rating'): - lessthan = '-' in (rating or []) - reverse = 'rating' in (self._sort_order or []) and '-rating' not in (self._sort_order or []) - if lessthan or reverse: - default = 100 - else: - default = 0 - # TODO make ratings a settings user-only vs all-users - self._queryset = self._queryset.annotate(rating=Round(Avg(Case(When(cooklog__created_by=self._request.user, then='cooklog__rating'), default=default)))) - if rating is None: - return + def rating_filter(self): + if self._rating or self._rating_lte or self._rating_gte or self._sort_includes('rating'): + self._queryset = self._queryset.annotate(rating=Round(Avg(Case(When(cooklog__created_by=self._request.user, then='cooklog__rating'), default=0)))) - if rating == '0': - self._queryset = self._queryset.filter(rating=0) - elif lessthan: - self._queryset = self._queryset.filter(rating__lte=int(rating[1:])).exclude(rating=0) - else: - self._queryset = self._queryset.filter(rating__gte=int(rating)) + if self._rating: + self._queryset = self._queryset.filter(rating=round(int(self._rating))) + elif self._rating_gte: + self._queryset = self._queryset.filter(rating__gte=int(self._rating_gte)) + elif self._rating_lte: + self._queryset = self._queryset.filter(rating__gte=int(self._rating_lte)).exclude(rating=0) def internal_filter(self, internal=None): if not internal: diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 61419b48a..21dbc2f31 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -1110,9 +1110,6 @@ class RecipePagination(PageNumberPagination): OpenApiParameter(name='foods_and_not', description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), type=int, many=True), - OpenApiParameter(name='units', description=_('ID of unit a recipe should have.'), type=int), - OpenApiParameter(name='rating', description=_( - 'Rating a recipe should have or greater. [0 - 5] Negative value filters rating less than.'), type=int), OpenApiParameter(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.'), type=int, many=True), OpenApiParameter(name='books_or', @@ -1127,9 +1124,15 @@ class RecipePagination(PageNumberPagination): OpenApiParameter(name='books_and_not', description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), type=int, many=True), + OpenApiParameter(name='units', description=_('ID of unit a recipe should have.'), type=int), OpenApiParameter(name='internal', description=_('If only internal recipes should be returned. [''true''/''false'']'), type=bool), + + OpenApiParameter(name='rating', description=_( 'Exact rating of recipe'), type=int), + OpenApiParameter(name='rating_gte', description=_( 'Rating a recipe should have or greater. '), type=int), + OpenApiParameter(name='rating_lte', description=_( 'Rating a recipe should have or smaller.'), type=int), + OpenApiParameter(name='random', description=_('Returns the results in randomized order. [''true''/''false'']')), OpenApiParameter(name='new', diff --git a/vue3/src/components/inputs/RatingField.vue b/vue3/src/components/inputs/RatingField.vue new file mode 100644 index 000000000..798f4ff5a --- /dev/null +++ b/vue3/src/components/inputs/RatingField.vue @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/vue3/src/openapi/apis/ApiApi.ts b/vue3/src/openapi/apis/ApiApi.ts index cbef8d41c..65a7f59cb 100644 --- a/vue3/src/openapi/apis/ApiApi.ts +++ b/vue3/src/openapi/apis/ApiApi.ts @@ -1217,6 +1217,8 @@ export interface ApiRecipeListRequest { query?: string; random?: string; rating?: number; + ratingGte?: number; + ratingLte?: number; timescooked?: number; units?: number; updatedon?: string; @@ -8880,6 +8882,14 @@ export class ApiApi extends runtime.BaseAPI { queryParameters['rating'] = requestParameters['rating']; } + if (requestParameters['ratingGte'] != null) { + queryParameters['rating_gte'] = requestParameters['ratingGte']; + } + + if (requestParameters['ratingLte'] != null) { + queryParameters['rating_lte'] = requestParameters['ratingLte']; + } + if (requestParameters['timescooked'] != null) { queryParameters['timescooked'] = requestParameters['timescooked']; } diff --git a/vue3/src/pages/SearchPage.vue b/vue3/src/pages/SearchPage.vue index fd5c5d8cd..516e7282c 100644 --- a/vue3/src/pages/SearchPage.vue +++ b/vue3/src/pages/SearchPage.vue @@ -154,12 +154,14 @@ import {useUserPreferenceStore} from "@/stores/UserPreferenceStore"; import {useRouteQuery} from "@vueuse/router"; import {toNumberArray} from "@/utils/utils"; import RandomIcon from "@/components/display/RandomIcon.vue"; +import {VRating, VSelect} from "vuetify/components"; +import RatingField from "@/components/inputs/RatingField.vue"; const {t} = useI18n() const router = useRouter() const {mdAndUp} = useDisplay() -const query = useRouteQuery('query', "",) +const query = useRouteQuery('query', "") const page = useRouteQuery('page', 1, {transform: Number}) const pageSize = useRouteQuery('pageSize', useUserPreferenceStore().deviceSettings.general_tableItemsPerPage, {transform: Number}) @@ -323,6 +325,56 @@ const filters = ref({ object: false, searchOnLoad: true }, + units: { + value: 'units', + label: 'Unit (any)', + hint: 'Recipes that contain any of the given units', + enabled: false, + default: [], + is: ModelSelect, + model: 'Unit', + modelValue: useRouteQuery('units', [], {transform: toNumberArray}), + mode: 'tags', + object: false, + searchOnLoad: true + }, + internal: { + value: 'internal', + label: 'Hide External', + hint: 'Hide external recipes', + enabled: false, + default: [], + is: VSelect, + items: [{value: true, title: 'Yes'}, {value: false, title: 'No'}], + modelValue: useRouteQuery('internal ', "false"), + }, + rating: { + value: 'rating', + label: 'Rating (exact)', + hint: 'Recipes with the exact rating', + enabled: false, + default: 0, + is: RatingField, + modelValue: useRouteQuery('rating ', 0), + }, + rating_gte: { + value: 'rating_gte', + label: 'Rating (>=)', + hint: 'Recipes with the given or a greater rating', + enabled: false, + default: 0, + is: RatingField, + modelValue: useRouteQuery('rating_gte ', 0), + }, + rating_lte: { + value: 'rating_lte', + label: 'Rating (<=)', + hint: 'Recipes with the given or a smaller rating', + enabled: false, + default: 0, + is: RatingField, + modelValue: useRouteQuery('rating_lte ', 0), + }, }) /**