From 5959914932023ac476101ac3bd546b103ea057ee Mon Sep 17 00:00:00 2001 From: Chris Scoggins Date: Mon, 24 Jan 2022 14:36:25 -0600 Subject: [PATCH] makenow filter --- cookbook/helper/recipe_search.py | 51 +++++-- cookbook/views/api.py | 5 +- .../RecipeSearchView/RecipeSearchView.vue | 127 ++++++++++++++++-- vue/src/components/Modals/ShoppingModal.vue | 14 +- vue/src/components/RecipeContextMenu.vue | 3 +- vue/src/locales/en.json | 3 +- vue/src/utils/models.js | 8 +- vue/src/utils/openapi/api.ts | 48 +++++-- 8 files changed, 207 insertions(+), 52 deletions(-) diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index a7ff1f5c8..0704b2067 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -37,7 +37,6 @@ class RecipeSearch(): 'or_not': self._params.get('keywords_or_not', None), 'and_not': self._params.get('keywords_and_not', None) } - self._foods = { 'or': self._params.get('foods_or', None), 'and': self._params.get('foods_and', None), @@ -53,7 +52,6 @@ class RecipeSearch(): self._steps = self._params.get('steps', None) self._units = self._params.get('units', None) # TODO add created by - # TODO add created before/after # TODO image exists self._sort_order = self._params.get('sort_order', None) self._books_or = str2bool(self._params.get('books_or', True)) @@ -61,6 +59,9 @@ class RecipeSearch(): self._random = str2bool(self._params.get('random', False)) self._new = str2bool(self._params.get('new', False)) self._last_viewed = int(self._params.get('last_viewed', 0)) + self._timescooked = self._params.get('timescooked', None) + self._lastcooked = self._params.get('lastcooked', None) + self._makenow = self._params.get('makenow', None) self._search_type = self._search_prefs.search or 'plain' if self._string: @@ -96,8 +97,8 @@ class RecipeSearch(): self._queryset = queryset self._build_sort_order() self._recently_viewed(num_recent=self._last_viewed) - self._last_cooked() - self._favorite_recipes() + self._last_cooked(lastcooked=self._lastcooked) + self._favorite_recipes(timescooked=self._timescooked) self._new_recipes() self.keyword_filters(**self._keywords) self.food_filters(**self._foods) @@ -107,6 +108,8 @@ class RecipeSearch(): self.step_filters(steps=self._steps) self.unit_filters(units=self._units) self.string_filters(string=self._string) + self._makenow_filter() + # self._queryset = self._queryset.distinct() # TODO 2x check. maybe add filter of recipe__in after orderby return self._queryset.filter(space=self._request.space).order_by(*self.orderby) @@ -185,10 +188,19 @@ class RecipeSearch(): else: self._queryset = self._queryset.filter(name__icontains=self._string) - def _last_cooked(self): - if self._sort_includes('lastcooked'): + def _last_cooked(self, lastcooked=None): + if self._sort_includes('lastcooked') or lastcooked: + longTimeAgo = timezone.now() - timedelta(days=100000) self._queryset = self._queryset.annotate(lastcooked=Coalesce( - Max(Case(When(created_by=self._request.user, space=self._request.space, then='cooklog__pk'))), Value(0))) + Max(Case(When(created_by=self._request.user, space=self._request.space, then='cooklog__created_at'))), Value(longTimeAgo))) + if lastcooked is None: + return + lessthan = '-' in lastcooked[:1] + + if lessthan: + self._queryset = self._queryset.filter(lastcooked__lte=lastcooked[1:]).exclude(lastcooked=longTimeAgo) + else: + self._queryset = self._queryset.filter(lastcooked__gte=lastcooked).exclude(lastcooked=longTimeAgo) def _new_recipes(self, new_days=7): # TODO make new days a user-setting @@ -210,11 +222,20 @@ class RecipeSearch(): 'recipe').annotate(recent=Max('created_at')).order_by('-recent')[:num_recent] self._queryset = self._queryset.annotate(recent=Coalesce(Max(Case(When(pk__in=last_viewed_recipes.values('recipe'), then='viewlog__pk'))), Value(0))) - def _favorite_recipes(self): - if self._sort_includes('favorite'): + def _favorite_recipes(self, timescooked=None): + if self._sort_includes('favorite') or timescooked: favorite_recipes = CookLog.objects.filter(created_by=self._request.user, space=self._request.space, recipe=OuterRef('pk') ).values('recipe').annotate(count=Count('pk', distinct=True)).values('count') self._queryset = self._queryset.annotate(favorite=Coalesce(Subquery(favorite_recipes), 0)) + if timescooked is None: + return + lessthan = '-' in timescooked + if timescooked == '0': + self._queryset = self._queryset.filter(favorite=0) + elif lessthan: + self._queryset = self._queryset.filter(favorite__lte=int(timescooked[1:])).exclude(favorite=0) + else: + self._queryset = self._queryset.filter(favorite__gte=int(timescooked)) def keyword_filters(self, **kwargs): if all([kwargs[x] is None for x in kwargs]): @@ -285,7 +306,7 @@ class RecipeSearch(): return lessthan = '-' in rating - if rating == 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) @@ -373,6 +394,16 @@ class RecipeSearch(): ).annotate(simularity=Max('trigram')).values('id', 'simularity').filter(simularity__gt=self._search_prefs.trigram_threshold) self._filters += [Q(pk__in=self._fuzzy_match.values('pk'))] + def _makenow_filter(self): + if not self._makenow: + return + shopping_users = [*self._request.user.get_shopping_share(), self._request.user] + self._queryset = self._queryset.annotate( + count_food=Count('steps__ingredients__food'), + count_onhand=Count('pk', filter=Q(steps__ingredients__food__onhand_users__in=shopping_users, steps__ingredients__food__ignore_shopping=False)), + count_ignore=Count('pk', filter=Q(steps__ingredients__food__ignore_shopping=True)) + ).annotate(missingfood=F('count_food')-F('count_onhand')-F('count_ignore')).filter(missingfood=0) + class RecipeFacet(): class CacheEmpty(Exception): diff --git a/cookbook/views/api.py b/cookbook/views/api.py index e6f7bca80..832293e93 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -645,7 +645,7 @@ class RecipeViewSet(viewsets.ModelViewSet): 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='rating', description=_('Rating a recipe should have or greater. [0 - 5] Negative value filters rating less than.'), qtype='int'), QueryParam(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.')), QueryParam(name='books_or', description=_('Book IDs, repeat for multiple. Return recipes with any of the books'), qtype='int'), QueryParam(name='books_and', description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), qtype='int'), @@ -654,6 +654,9 @@ class RecipeViewSet(viewsets.ModelViewSet): 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'']')), QueryParam(name='new', description=_('Returns new results first in search results. [''true''/''false'']')), + QueryParam(name='timescooked', description=_('Filter recipes cooked X times or more. Negative values returns cooked less than X times'), qtype='int'), + QueryParam(name='lastcooked', description=_('Filter recipes last cooked on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.')), + QueryParam(name='makenow', description=_('Filter recipes that can be made with OnHand food. [''true''/''false'']'), qtype='int'), ] schema = QueryParamAutoSchema() diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue index fff46bd2d..052910294 100644 --- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue +++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue @@ -115,6 +115,15 @@ + + + + + + + + + @@ -356,8 +365,8 @@ -
-
+
+
+ + +
+
+ + + + {{ $t("times_cooked") }} + + + + + + >= + <= + + + + + + + + + >= + <= + + + + + + + {{ $t("make_now") }} + + + + +
+
@@ -418,7 +474,7 @@
- +
{ @@ -764,7 +838,7 @@ export default { this.getFacets(this.facets?.cache_key) }) }) - }, + }, 300), openRandom: function () { this.refreshData(true) }, @@ -818,6 +892,9 @@ export default { this.search.search_rating = filter?.rating ?? undefined this.search.sort_order = filter?.options?.query?.sort_order ?? [] this.search.pagination_page = 1 + this.search.timescooked = undefined + this.search.makenow = false + this.search.lastcooked = undefined let fieldnum = { keywords: 1, @@ -916,6 +993,16 @@ export default { if (rating !== undefined && !this.search.search_rating_gte) { rating = rating * -1 } + let lastcooked = this.search.lastcooked || undefined + if (lastcooked !== undefined && !this.search.lastcooked_gte) { + lastcooked = "-" + lastcooked + } + let timescooked = parseInt(this.search.timescooked) + if (isNaN(timescooked)) { + timescooked = undefined + } else if (!this.search.timescooked_gte) { + timescooked = timescooked * -1 + } // when a filter is selected - added search params will be added to the filter let params = { options: { query: {} }, @@ -928,15 +1015,19 @@ export default { internal: this.search.search_internal, random: this.random_search, _new: this.ui.sort_by_new, + timescooked: timescooked, + makenow: this.search.makenow || undefined, + lastcooked: lastcooked, page: this.search.pagination_page, pageSize: this.ui.page_size, } - params.options.query.sort_order = this.search.sort_order.map((x) => x.value) + params.options.query = { + sort_order: this.search.sort_order.map((x) => x.value), + } if (!this.searchFiltered()) { params.options.query.last_viewed = this.ui.recently_viewed } - console.log(params) return params }, searchFiltered: function (ignore_string = false) { @@ -948,7 +1039,12 @@ export default { this.random_search || this.search?.search_filter || this.search.sort_order.length !== 0 || - this.search?.search_rating !== undefined + this.search?.search_rating !== undefined || + (this.search.timescooked !== undefined && this.search.timescooked !== "") || + this.search.makenow !== false || + (this.search.lastcooked !== undefined && this.search.lastcooked !== "") + + console.log() if (ignore_string) { return filtered @@ -1008,9 +1104,12 @@ export default {