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),
+ },
})
/**