diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py
index 24e2faadf..d5e0bd692 100644
--- a/cookbook/helper/recipe_search.py
+++ b/cookbook/helper/recipe_search.py
@@ -38,7 +38,12 @@ class RecipeSearch():
'and_not': self._params.get('keywords_and_not', None)
}
- self._foods = self._params.get('foods', None)
+ self._foods = {
+ 'or': self._params.get('foods_or', None),
+ 'and': self._params.get('foods_and', None),
+ 'or_not': self._params.get('foods_or_not', None),
+ 'and_not': self._params.get('foods_and_not', None)
+ }
self._books = self._params.get('books', None)
self._steps = self._params.get('steps', None)
self._units = self._params.get('units', None)
@@ -48,7 +53,6 @@ class RecipeSearch():
self._sort_order = self._params.get('sort_order', None)
# TODO add save
- self._foods_or = str2bool(self._params.get('foods_or', True))
self._books_or = str2bool(self._params.get('books_or', True))
self._internal = str2bool(self._params.get('internal', False))
@@ -94,7 +98,7 @@ class RecipeSearch():
# self._last_viewed()
# self._last_cooked()
self.keyword_filters(**self._keywords)
- self.food_filters(foods=self._foods, operator=self._foods_or)
+ self.food_filters(**self._foods)
self.book_filters(books=self._books, operator=self._books_or)
self.rating_filter(rating=self._rating)
self.internal_filter()
@@ -213,19 +217,31 @@ class RecipeSearch():
if 'not' in kw_filter:
self._queryset = self._queryset.exclude(id__in=recipes.values('id'))
- def food_filters(self, foods=None, operator=True):
- if not foods:
+ def food_filters(self, **kwargs):
+ if all([kwargs[x] is None for x in kwargs]):
return
- if not isinstance(foods, list):
- foods = [foods]
- if operator == True:
- # TODO creating setting to include descendants of food a setting
- self._queryset = self._queryset.filter(steps__ingredients__food__in=Food.include_descendants(Food.objects.filter(pk__in=foods)))
- else:
- # when performing an 'and' search returned recipes should include a parent OR any of its descedants
- # AND other foods selected so filters are appended using steps__ingredients__food__id__in the list of foods and descendants
- for fd in Food.objects.filter(pk__in=foods):
- self._queryset = self._queryset.filter(steps__ingredients__food__in=list(fd.get_descendants_and_self()))
+ for fd_filter in kwargs:
+ if not kwargs[fd_filter]:
+ continue
+ if not isinstance(kwargs[fd_filter], list):
+ kwargs[fd_filter] = [kwargs[fd_filter]]
+
+ foods = Food.objects.filter(pk__in=kwargs[fd_filter])
+ if 'or' in fd_filter:
+ f = Q(steps__ingredients__food__in=Food.include_descendants(foods))
+ if 'not' in fd_filter:
+ self._queryset = self._queryset.exclude(f)
+ else:
+ self._queryset = self._queryset.filter(f)
+ elif 'and' in fd_filter:
+ recipes = Recipe.objects.all()
+ for food in foods:
+ if 'not' in fd_filter:
+ recipes = recipes.filter(steps__ingredients__food__in=food.get_descendants_and_self())
+ else:
+ self._queryset = self._queryset.filter(steps__ingredients__food__in=food.get_descendants_and_self())
+ if 'not' in fd_filter:
+ self._queryset = self._queryset.exclude(id__in=recipes.values('id'))
def unit_filters(self, units=None, operator=True):
if operator != True:
diff --git a/cookbook/views/api.py b/cookbook/views/api.py
index 9c9801246..36460e3e0 100644
--- a/cookbook/views/api.py
+++ b/cookbook/views/api.py
@@ -639,10 +639,13 @@ class RecipeViewSet(viewsets.ModelViewSet):
QueryParam(name='keywords_or_not', description=_('Keyword IDs, repeat for multiple Exclude recipes with any of the keywords.'), qtype='int'),
QueryParam(name='keywords_and_not', description=_('Keyword IDs, repeat for multiple Exclude recipes with all of the keywords.'), qtype='int'),
QueryParam(name='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'), qtype='int'),
+ QueryParam(name='foods_or', description=_('Food IDs, repeat for multiple Return recipes with any of the foods'), qtype='int'),
+ QueryParam(name='foods_and', description=_('Food IDs, repeat for multiple Return recipes with all of the foods.'), qtype='int'),
+ QueryParam(name='foods_or_not', description=_('Food IDs, repeat for multiple Exclude recipes with any of the foods.'), qtype='int'),
+ QueryParam(name='foods_and_not', description=_('Food IDs, repeat for multiple Exclude recipes with all of the foods.'), qtype='int'),
QueryParam(name='units', description=_('ID of unit a recipe should have.'), qtype='int'),
QueryParam(name='rating', description=_('Rating a recipe should have. [0 - 5]'), qtype='int'),
QueryParam(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.')),
- QueryParam(name='foods_or', description=_('If recipe should have all (AND=''false'') or any (OR=''true'') of the provided foods.')),
QueryParam(name='books_or', description=_('If recipe should be in all (AND=''false'') or any (OR=''true'') of the provided books.')),
QueryParam(name='internal', description=_('If only internal recipes should be returned. [''true''/''false'']')),
QueryParam(name='random', description=_('Returns the results in randomized order. [''true''/''false'']')),
diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue
index c609e9404..dc0103591 100644
--- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue
+++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue
@@ -141,28 +141,28 @@
-
{{ $t("Keywords") }}
+ {{ $t("Keywords") }}
-
+
-
+
-
+
+ />
- {{ $t("or") }}
+ {{ $t("or") }}
{{ $t("and") }}
-
+
{{ $t("not") }}
@@ -211,12 +211,28 @@
+
{{ $t("Foods") }}
-
+
+
+
+
+
+
+
+
+
+ />
-
- {{ $t("or") }}
+
+ {{ $t("or") }}
{{ $t("and") }}
+
+
+
+ {{ $t("not") }}
+
+
+
@@ -409,22 +432,25 @@ export default {
{ items: [], operator: true, not: false },
{ items: [], operator: true, not: false },
],
- search_foods: [],
+ search_foods: [
+ { items: [], operator: true, not: false },
+ { items: [], operator: true, not: false },
+ { items: [], operator: true, not: false },
+ { items: [], operator: true, not: false },
+ ],
search_books: [],
search_units: [],
search_rating: undefined,
search_rating_gte: true,
- search_keywords_or: true,
- search_foods_or: true,
search_books_or: true,
search_units_or: true,
pagination_page: 1,
expert_mode: false,
keywords_fields: 1,
- food_fields: 1,
- book_fields: 1,
+ foods_fields: 1,
+ books_fields: 1,
rating_fields: 1,
- unit_fields: 1,
+ units_fields: 1,
},
ui: {
show_meal_plan: true,
@@ -497,16 +523,16 @@ export default {
return !this.expertMode ? 1 : this.search.keywords_fields
},
foodFields: function () {
- return !this.expertMode ? 1 : this.search.food_fields
+ return !this.expertMode ? 1 : this.search.foods_fields
},
bookFields: function () {
- return !this.expertMode ? 1 : this.search.book_fields
+ return !this.expertMode ? 1 : this.search.books_fields
},
ratingFields: function () {
return !this.expertMode ? 1 : this.search.rating_fields
},
unitFields: function () {
- return !this.expertMode ? 1 : this.search.unit_fields
+ return !this.expertMode ? 1 : this.search.units_fields
},
},
mounted() {
@@ -544,7 +570,7 @@ export default {
}
this.facets.Foods = []
- for (let x of this.search.search_foods) {
+ for (let x of this.search.search_foods.map((x) => x.items).flat()) {
this.facets.Foods.push({ id: x, name: "loading..." })
}
@@ -663,7 +689,9 @@ export default {
this.search.search_keywords = this.search.search_keywords.map((x) => {
return { ...x, items: [] }
})
- this.search.search_foods = []
+ this.search.search_foods = this.search.search_foods.map((x) => {
+ return { ...x, items: [] }
+ })
this.search.search_books = []
this.search.search_units = []
this.search.search_rating = undefined
@@ -735,15 +763,12 @@ export default {
this.addFields("keywords")
let params = {
...this.addFields("keywords"),
+ ...this.addFields("foods"),
query: this.search.search_input,
- foods: this.search.search_foods.map(function (A) {
- return A?.["id"] ?? A
- }),
rating: rating,
books: this.search.search_books.map(function (A) {
return A["id"]
}),
- foodsOr: this.search.search_foods_or,
booksOr: this.search.search_books_or,
internal: this.search.search_internal,
random: this.random_search,
@@ -760,7 +785,7 @@ export default {
searchFiltered: function (ignore_string = false) {
let filtered =
this.search?.search_keywords[0].items?.length === 0 &&
- this.search?.search_foods?.length === 0 &&
+ this.search?.search_foods[0].items?.length === 0 &&
this.search?.search_books?.length === 0 &&
// this.settings?.pagination_page === 1 &&
!this.random_search &&
diff --git a/vue/src/utils/models.js b/vue/src/utils/models.js
index c4845aed9..2639c5c33 100644
--- a/vue/src/utils/models.js
+++ b/vue/src/utils/models.js
@@ -441,10 +441,13 @@ export class Models {
"keywords_or_not",
"keywords_and_not",
"foods",
+ "foods_or",
+ "foods_and",
+ "foods_or_not",
+ "foods_and_not",
"units",
"rating",
"books",
- "foodsOr",
"booksOr",
"internal",
"random",
diff --git a/vue/src/utils/openapi/api.ts b/vue/src/utils/openapi/api.ts
index 0d78c8301..84f411686 100644
--- a/vue/src/utils/openapi/api.ts
+++ b/vue/src/utils/openapi/api.ts
@@ -5288,10 +5288,13 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
* @param {number} [keywordsOrNot] Keyword IDs, repeat for multiple Exclude recipes with any of the keywords.
* @param {number} [keywordsAndNot] Keyword IDs, repeat for multiple Exclude recipes with all of the keywords.
* @param {number} [foods] ID of food a recipe should have. For multiple repeat parameter.
+ * @param {number} [foodsOr] Food IDs, repeat for multiple Return recipes with any of the foods
+ * @param {number} [foodsAnd] Food IDs, repeat for multiple Return recipes with all of the foods.
+ * @param {number} [foodsOrNot] Food IDs, repeat for multiple Exclude recipes with any of the foods.
+ * @param {number} [foodsAndNot] Food IDs, repeat for multiple Exclude recipes with all of the foods.
* @param {number} [units] ID of unit a recipe should have.
* @param {number} [rating] Rating a recipe should have. [0 - 5]
* @param {string} [books] ID of book a recipe should be in. For multiple repeat parameter.
- * @param {string} [foodsOr] If recipe should have all (AND=false) or any (OR=<b>true</b>) of the provided foods.
* @param {string} [booksOr] If recipe should be in all (AND=false) or any (OR=<b>true</b>) of the provided books.
* @param {string} [internal] If only internal recipes should be returned. [true/<b>false</b>]
* @param {string} [random] Returns the results in randomized order. [true/<b>false</b>]
@@ -5301,7 +5304,7 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
- listRecipes: async (query?: string, keywords?: number, keywordsOr?: number, keywordsAnd?: number, keywordsOrNot?: number, keywordsAndNot?: number, foods?: number, units?: number, rating?: number, books?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options: any = {}): Promise
=> {
+ listRecipes: async (query?: string, keywords?: number, keywordsOr?: number, keywordsAnd?: number, keywordsOrNot?: number, keywordsAndNot?: number, foods?: number, foodsOr?: number, foodsAnd?: number, foodsOrNot?: number, foodsAndNot?: number, units?: number, rating?: number, books?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options: any = {}): Promise => {
const localVarPath = `/api/recipe/`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
@@ -5342,6 +5345,22 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
localVarQueryParameter['foods'] = foods;
}
+ if (foodsOr !== undefined) {
+ localVarQueryParameter['foods_or'] = foodsOr;
+ }
+
+ if (foodsAnd !== undefined) {
+ localVarQueryParameter['foods_and'] = foodsAnd;
+ }
+
+ if (foodsOrNot !== undefined) {
+ localVarQueryParameter['foods_or_not'] = foodsOrNot;
+ }
+
+ if (foodsAndNot !== undefined) {
+ localVarQueryParameter['foods_and_not'] = foodsAndNot;
+ }
+
if (units !== undefined) {
localVarQueryParameter['units'] = units;
}
@@ -5354,10 +5373,6 @@ export const ApiApiAxiosParamCreator = function (configuration?: Configuration)
localVarQueryParameter['books'] = books;
}
- if (foodsOr !== undefined) {
- localVarQueryParameter['foods_or'] = foodsOr;
- }
-
if (booksOr !== undefined) {
localVarQueryParameter['books_or'] = booksOr;
}
@@ -9693,10 +9708,13 @@ export const ApiApiFp = function(configuration?: Configuration) {
* @param {number} [keywordsOrNot] Keyword IDs, repeat for multiple Exclude recipes with any of the keywords.
* @param {number} [keywordsAndNot] Keyword IDs, repeat for multiple Exclude recipes with all of the keywords.
* @param {number} [foods] ID of food a recipe should have. For multiple repeat parameter.
+ * @param {number} [foodsOr] Food IDs, repeat for multiple Return recipes with any of the foods
+ * @param {number} [foodsAnd] Food IDs, repeat for multiple Return recipes with all of the foods.
+ * @param {number} [foodsOrNot] Food IDs, repeat for multiple Exclude recipes with any of the foods.
+ * @param {number} [foodsAndNot] Food IDs, repeat for multiple Exclude recipes with all of the foods.
* @param {number} [units] ID of unit a recipe should have.
* @param {number} [rating] Rating a recipe should have. [0 - 5]
* @param {string} [books] ID of book a recipe should be in. For multiple repeat parameter.
- * @param {string} [foodsOr] If recipe should have all (AND=false) or any (OR=<b>true</b>) of the provided foods.
* @param {string} [booksOr] If recipe should be in all (AND=false) or any (OR=<b>true</b>) of the provided books.
* @param {string} [internal] If only internal recipes should be returned. [true/<b>false</b>]
* @param {string} [random] Returns the results in randomized order. [true/<b>false</b>]
@@ -9706,8 +9724,8 @@ export const ApiApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
- async listRecipes(query?: string, keywords?: number, keywordsOr?: number, keywordsAnd?: number, keywordsOrNot?: number, keywordsAndNot?: number, foods?: number, units?: number, rating?: number, books?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
- const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, keywordsOr, keywordsAnd, keywordsOrNot, keywordsAndNot, foods, units, rating, books, foodsOr, booksOr, internal, random, _new, page, pageSize, options);
+ async listRecipes(query?: string, keywords?: number, keywordsOr?: number, keywordsAnd?: number, keywordsOrNot?: number, keywordsAndNot?: number, foods?: number, foodsOr?: number, foodsAnd?: number, foodsOrNot?: number, foodsAndNot?: number, units?: number, rating?: number, books?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> {
+ const localVarAxiosArgs = await localVarAxiosParamCreator.listRecipes(query, keywords, keywordsOr, keywordsAnd, keywordsOrNot, keywordsAndNot, foods, foodsOr, foodsAnd, foodsOrNot, foodsAndNot, units, rating, books, booksOr, internal, random, _new, page, pageSize, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@@ -11381,10 +11399,13 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
* @param {number} [keywordsOrNot] Keyword IDs, repeat for multiple Exclude recipes with any of the keywords.
* @param {number} [keywordsAndNot] Keyword IDs, repeat for multiple Exclude recipes with all of the keywords.
* @param {number} [foods] ID of food a recipe should have. For multiple repeat parameter.
+ * @param {number} [foodsOr] Food IDs, repeat for multiple Return recipes with any of the foods
+ * @param {number} [foodsAnd] Food IDs, repeat for multiple Return recipes with all of the foods.
+ * @param {number} [foodsOrNot] Food IDs, repeat for multiple Exclude recipes with any of the foods.
+ * @param {number} [foodsAndNot] Food IDs, repeat for multiple Exclude recipes with all of the foods.
* @param {number} [units] ID of unit a recipe should have.
* @param {number} [rating] Rating a recipe should have. [0 - 5]
* @param {string} [books] ID of book a recipe should be in. For multiple repeat parameter.
- * @param {string} [foodsOr] If recipe should have all (AND=false) or any (OR=<b>true</b>) of the provided foods.
* @param {string} [booksOr] If recipe should be in all (AND=false) or any (OR=<b>true</b>) of the provided books.
* @param {string} [internal] If only internal recipes should be returned. [true/<b>false</b>]
* @param {string} [random] Returns the results in randomized order. [true/<b>false</b>]
@@ -11394,8 +11415,8 @@ export const ApiApiFactory = function (configuration?: Configuration, basePath?:
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
- listRecipes(query?: string, keywords?: number, keywordsOr?: number, keywordsAnd?: number, keywordsOrNot?: number, keywordsAndNot?: number, foods?: number, units?: number, rating?: number, books?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): AxiosPromise {
- return localVarFp.listRecipes(query, keywords, keywordsOr, keywordsAnd, keywordsOrNot, keywordsAndNot, foods, units, rating, books, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(axios, basePath));
+ listRecipes(query?: string, keywords?: number, keywordsOr?: number, keywordsAnd?: number, keywordsOrNot?: number, keywordsAndNot?: number, foods?: number, foodsOr?: number, foodsAnd?: number, foodsOrNot?: number, foodsAndNot?: number, units?: number, rating?: number, books?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any): AxiosPromise {
+ return localVarFp.listRecipes(query, keywords, keywordsOr, keywordsAnd, keywordsOrNot, keywordsAndNot, foods, foodsOr, foodsAnd, foodsOrNot, foodsAndNot, units, rating, books, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(axios, basePath));
},
/**
*
@@ -13093,10 +13114,13 @@ export class ApiApi extends BaseAPI {
* @param {number} [keywordsOrNot] Keyword IDs, repeat for multiple Exclude recipes with any of the keywords.
* @param {number} [keywordsAndNot] Keyword IDs, repeat for multiple Exclude recipes with all of the keywords.
* @param {number} [foods] ID of food a recipe should have. For multiple repeat parameter.
+ * @param {number} [foodsOr] Food IDs, repeat for multiple Return recipes with any of the foods
+ * @param {number} [foodsAnd] Food IDs, repeat for multiple Return recipes with all of the foods.
+ * @param {number} [foodsOrNot] Food IDs, repeat for multiple Exclude recipes with any of the foods.
+ * @param {number} [foodsAndNot] Food IDs, repeat for multiple Exclude recipes with all of the foods.
* @param {number} [units] ID of unit a recipe should have.
* @param {number} [rating] Rating a recipe should have. [0 - 5]
* @param {string} [books] ID of book a recipe should be in. For multiple repeat parameter.
- * @param {string} [foodsOr] If recipe should have all (AND=false) or any (OR=<b>true</b>) of the provided foods.
* @param {string} [booksOr] If recipe should be in all (AND=false) or any (OR=<b>true</b>) of the provided books.
* @param {string} [internal] If only internal recipes should be returned. [true/<b>false</b>]
* @param {string} [random] Returns the results in randomized order. [true/<b>false</b>]
@@ -13107,8 +13131,8 @@ export class ApiApi extends BaseAPI {
* @throws {RequiredError}
* @memberof ApiApi
*/
- public listRecipes(query?: string, keywords?: number, keywordsOr?: number, keywordsAnd?: number, keywordsOrNot?: number, keywordsAndNot?: number, foods?: number, units?: number, rating?: number, books?: string, foodsOr?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any) {
- return ApiApiFp(this.configuration).listRecipes(query, keywords, keywordsOr, keywordsAnd, keywordsOrNot, keywordsAndNot, foods, units, rating, books, foodsOr, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(this.axios, this.basePath));
+ public listRecipes(query?: string, keywords?: number, keywordsOr?: number, keywordsAnd?: number, keywordsOrNot?: number, keywordsAndNot?: number, foods?: number, foodsOr?: number, foodsAnd?: number, foodsOrNot?: number, foodsAndNot?: number, units?: number, rating?: number, books?: string, booksOr?: string, internal?: string, random?: string, _new?: string, page?: number, pageSize?: number, options?: any) {
+ return ApiApiFp(this.configuration).listRecipes(query, keywords, keywordsOr, keywordsAnd, keywordsOrNot, keywordsAndNot, foods, foodsOr, foodsAnd, foodsOrNot, foodsAndNot, units, rating, books, booksOr, internal, random, _new, page, pageSize, options).then((request) => request(this.axios, this.basePath));
}
/**