mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-03 13:19:16 -05:00
WIP search page
This commit is contained in:
@@ -49,7 +49,11 @@ class RecipeSearch():
|
|||||||
self._search_prefs = SearchPreference()
|
self._search_prefs = SearchPreference()
|
||||||
self._string = self._params.get('query').strip(
|
self._string = self._params.get('query').strip(
|
||||||
) if self._params.get('query', None) else None
|
) if self._params.get('query', None) else None
|
||||||
|
|
||||||
self._rating = self._params.get('rating', 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 = {
|
self._keywords = {
|
||||||
'or': self._params.get('keywords_or', None) or self._params.get('keywords', None),
|
'or': self._params.get('keywords_or', None) or self._params.get('keywords', None),
|
||||||
'and': self._params.get('keywords_and', None),
|
'and': self._params.get('keywords_and', None),
|
||||||
@@ -140,7 +144,7 @@ class RecipeSearch():
|
|||||||
self.keyword_filters(**self._keywords)
|
self.keyword_filters(**self._keywords)
|
||||||
self.food_filters(**self._foods)
|
self.food_filters(**self._foods)
|
||||||
self.book_filters(**self._books)
|
self.book_filters(**self._books)
|
||||||
self.rating_filter(rating=self._rating)
|
self.rating_filter()
|
||||||
self.internal_filter(internal=self._internal)
|
self.internal_filter(internal=self._internal)
|
||||||
self.step_filters(steps=self._steps)
|
self.step_filters(steps=self._steps)
|
||||||
self.unit_filters(units=self._units)
|
self.unit_filters(units=self._units)
|
||||||
@@ -413,25 +417,16 @@ class RecipeSearch():
|
|||||||
units = [units]
|
units = [units]
|
||||||
self._queryset = self._queryset.filter(steps__ingredients__unit__in=units)
|
self._queryset = self._queryset.filter(steps__ingredients__unit__in=units)
|
||||||
|
|
||||||
def rating_filter(self, rating=None):
|
def rating_filter(self):
|
||||||
if rating or self._sort_includes('rating'):
|
if self._rating or self._rating_lte or self._rating_gte or self._sort_includes('rating'):
|
||||||
lessthan = '-' in (rating or [])
|
self._queryset = self._queryset.annotate(rating=Round(Avg(Case(When(cooklog__created_by=self._request.user, then='cooklog__rating'), default=0))))
|
||||||
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
|
|
||||||
|
|
||||||
if rating == '0':
|
if self._rating:
|
||||||
self._queryset = self._queryset.filter(rating=0)
|
self._queryset = self._queryset.filter(rating=round(int(self._rating)))
|
||||||
elif lessthan:
|
elif self._rating_gte:
|
||||||
self._queryset = self._queryset.filter(rating__lte=int(rating[1:])).exclude(rating=0)
|
self._queryset = self._queryset.filter(rating__gte=int(self._rating_gte))
|
||||||
else:
|
elif self._rating_lte:
|
||||||
self._queryset = self._queryset.filter(rating__gte=int(rating))
|
self._queryset = self._queryset.filter(rating__gte=int(self._rating_lte)).exclude(rating=0)
|
||||||
|
|
||||||
def internal_filter(self, internal=None):
|
def internal_filter(self, internal=None):
|
||||||
if not internal:
|
if not internal:
|
||||||
|
|||||||
@@ -1110,9 +1110,6 @@ class RecipePagination(PageNumberPagination):
|
|||||||
OpenApiParameter(name='foods_and_not',
|
OpenApiParameter(name='foods_and_not',
|
||||||
description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), type=int,
|
description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), type=int,
|
||||||
many=True),
|
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.'),
|
OpenApiParameter(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.'),
|
||||||
type=int, many=True),
|
type=int, many=True),
|
||||||
OpenApiParameter(name='books_or',
|
OpenApiParameter(name='books_or',
|
||||||
@@ -1127,9 +1124,15 @@ class RecipePagination(PageNumberPagination):
|
|||||||
OpenApiParameter(name='books_and_not',
|
OpenApiParameter(name='books_and_not',
|
||||||
description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), type=int,
|
description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), type=int,
|
||||||
many=True),
|
many=True),
|
||||||
|
OpenApiParameter(name='units', description=_('ID of unit a recipe should have.'), type=int),
|
||||||
OpenApiParameter(name='internal',
|
OpenApiParameter(name='internal',
|
||||||
description=_('If only internal recipes should be returned. [''true''/''<b>false</b>'']'),
|
description=_('If only internal recipes should be returned. [''true''/''<b>false</b>'']'),
|
||||||
type=bool),
|
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',
|
OpenApiParameter(name='random',
|
||||||
description=_('Returns the results in randomized order. [''true''/''<b>false</b>'']')),
|
description=_('Returns the results in randomized order. [''true''/''<b>false</b>'']')),
|
||||||
OpenApiParameter(name='new',
|
OpenApiParameter(name='new',
|
||||||
|
|||||||
26
vue3/src/components/inputs/RatingField.vue
Normal file
26
vue3/src/components/inputs/RatingField.vue
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<template>
|
||||||
|
<v-input label="Test" hint="Test hint" persistent-hint>
|
||||||
|
<template #prepend v-if="$slots.prepend">
|
||||||
|
<slot name="prepend">
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-rating v-model="model"></v-rating>
|
||||||
|
|
||||||
|
<template #append v-if="$slots.append">
|
||||||
|
<slot name="append">
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
|
</v-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
const model = defineModel<boolean>({required: true})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1217,6 +1217,8 @@ export interface ApiRecipeListRequest {
|
|||||||
query?: string;
|
query?: string;
|
||||||
random?: string;
|
random?: string;
|
||||||
rating?: number;
|
rating?: number;
|
||||||
|
ratingGte?: number;
|
||||||
|
ratingLte?: number;
|
||||||
timescooked?: number;
|
timescooked?: number;
|
||||||
units?: number;
|
units?: number;
|
||||||
updatedon?: string;
|
updatedon?: string;
|
||||||
@@ -8880,6 +8882,14 @@ export class ApiApi extends runtime.BaseAPI {
|
|||||||
queryParameters['rating'] = requestParameters['rating'];
|
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) {
|
if (requestParameters['timescooked'] != null) {
|
||||||
queryParameters['timescooked'] = requestParameters['timescooked'];
|
queryParameters['timescooked'] = requestParameters['timescooked'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,12 +154,14 @@ import {useUserPreferenceStore} from "@/stores/UserPreferenceStore";
|
|||||||
import {useRouteQuery} from "@vueuse/router";
|
import {useRouteQuery} from "@vueuse/router";
|
||||||
import {toNumberArray} from "@/utils/utils";
|
import {toNumberArray} from "@/utils/utils";
|
||||||
import RandomIcon from "@/components/display/RandomIcon.vue";
|
import RandomIcon from "@/components/display/RandomIcon.vue";
|
||||||
|
import {VRating, VSelect} from "vuetify/components";
|
||||||
|
import RatingField from "@/components/inputs/RatingField.vue";
|
||||||
|
|
||||||
const {t} = useI18n()
|
const {t} = useI18n()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const {mdAndUp} = useDisplay()
|
const {mdAndUp} = useDisplay()
|
||||||
|
|
||||||
const query = useRouteQuery('query', "",)
|
const query = useRouteQuery('query', "")
|
||||||
const page = useRouteQuery('page', 1, {transform: Number})
|
const page = useRouteQuery('page', 1, {transform: Number})
|
||||||
const pageSize = useRouteQuery('pageSize', useUserPreferenceStore().deviceSettings.general_tableItemsPerPage, {transform: Number})
|
const pageSize = useRouteQuery('pageSize', useUserPreferenceStore().deviceSettings.general_tableItemsPerPage, {transform: Number})
|
||||||
|
|
||||||
@@ -323,6 +325,56 @@ const filters = ref({
|
|||||||
object: false,
|
object: false,
|
||||||
searchOnLoad: true
|
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),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user