diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index 074d4ea96..b80b1e8e7 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -82,12 +82,25 @@ class RecipeSearch(): self._num_recent = int(self._params.get('num_recent', 0)) self._include_children = str2bool( self._params.get('include_children', None)) - self._timescooked = self._params.get('timescooked', None) - self._cookedon = self._params.get('cookedon', None) + self._timescooked = self._params.get('timescooked_gte', None) + self._timescooked_gte = self._params.get('timescooked_gte', None) + self._timescooked_lte = self._params.get('timescooked_lte', None) + self._createdon = self._params.get('createdon', None) - self._createdby = self._params.get('createdby', None) + self._createdon_gte = self._params.get('createdon_gte', None) + self._createdon_lte = self._params.get('createdon_lte', None) + self._updatedon = self._params.get('updatedon', None) - self._viewedon = self._params.get('viewedon', None) + self._updatedon_gte = self._params.get('updatedon_gte', None) + self._updatedon_lte = self._params.get('updatedon_lte', None) + + self._viewedon_gte = self._params.get('viewedon_gte', None) + self._viewedon_lte = self._params.get('viewedon_lte', None) + + self._cookedon_gte = self._params.get('cookedon_gte', None) + self._cookedon_lte = self._params.get('cookedon_lte', None) + + self._createdby = self._params.get('createdby', None) self._makenow = self._params.get('makenow', None) # this supports hidden feature to find recipes missing X ingredients if isinstance(self._makenow, bool) and self._makenow == True: @@ -134,12 +147,14 @@ class RecipeSearch(): self._build_sort_order() self._recently_viewed(num_recent=self._num_recent) - self._cooked_on_filter(cooked_date=self._cookedon) - self._created_on_filter(created_date=self._createdon) + + self._cooked_on_filter() + self._created_on_filter() + self._updated_on_filter() + self._viewed_on_filter() + self._created_by_filter(created_by_user_id=self._createdby) - self._updated_on_filter(updated_date=self._updatedon) - self._viewed_on_filter(viewed_date=self._viewedon) - self._favorite_recipes(times_cooked=self._timescooked) + self._favorite_recipes() self._new_recipes() self.keyword_filters(**self._keywords) self.food_filters(**self._foods) @@ -232,9 +247,9 @@ class RecipeSearch(): query_filter |= Q(**{"%s" % f: self._string}) self._queryset = self._queryset.filter(query_filter).distinct() - def _cooked_on_filter(self, cooked_date=None): - if self._sort_includes('lastcooked') or cooked_date: - lessthan = self._sort_includes('-lastcooked') or '-' in (cooked_date or [])[:1] + def _cooked_on_filter(self): + if self._sort_includes('lastcooked') or self._cookedon_gte or self._cookedon_lte: + lessthan = self._sort_includes('-lastcooked') or self._cookedon_lte if lessthan: default = timezone.now() - timedelta(days=100000) else: @@ -242,15 +257,11 @@ class RecipeSearch(): self._queryset = self._queryset.annotate( lastcooked=Coalesce(Max(Case(When(cooklog__created_by=self._request.user, cooklog__space=self._request.space, then='cooklog__created_at'))), Value(default)) ) - if cooked_date is None: - return - cooked_date = date(*[int(x)for x in cooked_date.split('-') if x != '']) - - if lessthan: - self._queryset = self._queryset.filter(lastcooked__date__lte=cooked_date).exclude(lastcooked=default) - else: - self._queryset = self._queryset.filter(lastcooked__date__gte=cooked_date).exclude(lastcooked=default) + if self._cookedon_lte: + self._queryset = self._queryset.filter(lastcooked__date__lte=self._cookedon_lte).exclude(lastcooked=default) + elif self._cookedon_gte: + self._queryset = self._queryset.filter(lastcooked__date__gte=self._cookedon_gte ).exclude(lastcooked=default) def _created_on_filter(self, created_date=None): if created_date is None: @@ -317,9 +328,9 @@ class RecipeSearch(): ) self._queryset = self._queryset.annotate(recent=Coalesce(Max(Case(When(pk__in=num_recent_recipes.values('recipe'), then='viewlog__pk'))), Value(0))) - def _favorite_recipes(self, times_cooked=None): - if self._sort_includes('favorite') or times_cooked: - less_than = '-' in (str(times_cooked) or []) and not self._sort_includes('-favorite') + def _favorite_recipes(self): + if self._sort_includes('favorite') or self._timescooked_gte or self._timescooked_lte: + less_than = self._timescooked_lte and not self._sort_includes('-favorite') if less_than: default = 1000 else: @@ -331,15 +342,13 @@ class RecipeSearch(): .values('count') ) self._queryset = self._queryset.annotate(favorite=Coalesce(Subquery(favorite_recipes), default)) - if times_cooked is None: - return - if times_cooked == '0': + if (self._timescooked_lte == 0 and self._timescooked_gte is None) or (self._timescooked_gte == 0 and self._timescooked_lte is None): self._queryset = self._queryset.filter(favorite=0) - elif less_than: - self._queryset = self._queryset.filter(favorite__lte=int(times_cooked.replace('-', ''))).exclude(favorite=0) - else: - self._queryset = self._queryset.filter(favorite__gte=int(times_cooked)) + elif self._timescooked_lte: + self._queryset = self._queryset.filter(favorite__lte=int(self._timescooked_lte)).exclude(favorite=0) + elif self._timescooked_gte: + self._queryset = self._queryset.filter(favorite__gte=int(self._timescooked_gte)) def keyword_filters(self, **kwargs): if all([kwargs[x] is None for x in kwargs]): diff --git a/cookbook/views/api.py b/cookbook/views/api.py index 21dbc2f31..ba09cd095 100644 --- a/cookbook/views/api.py +++ b/cookbook/views/api.py @@ -1079,101 +1079,55 @@ class RecipePagination(PageNumberPagination): @extend_schema_view(retrieve=extend_schema(parameters=[ OpenApiParameter(name='share', type=str), ]), list=extend_schema(parameters=[ - OpenApiParameter(name='query', description=_( - 'Query string matched (fuzzy) against recipe name. In the future also fulltext search.'), type=str), - OpenApiParameter(name='keywords', description=_( - 'ID of keyword a recipe should have. For multiple repeat parameter. Equivalent to keywords_or'), type=int, - many=True), - OpenApiParameter(name='keywords_or', - description=_('Keyword IDs, repeat for multiple. Return recipes with any of the keywords'), - type=int, many=True), - OpenApiParameter(name='keywords_and', - description=_('Keyword IDs, repeat for multiple. Return recipes with all of the keywords.'), - type=int, many=True), - OpenApiParameter(name='keywords_or_not', - description=_('Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords.'), - type=int, many=True), - OpenApiParameter(name='keywords_and_not', - description=_('Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords.'), - type=int, many=True), - OpenApiParameter(name='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'), - type=int, many=True), - OpenApiParameter(name='foods_or', - description=_('Food IDs, repeat for multiple. Return recipes with any of the foods'), type=int, - many=True), - OpenApiParameter(name='foods_and', - description=_('Food IDs, repeat for multiple. Return recipes with all of the foods.'), type=int, - many=True), - OpenApiParameter(name='foods_or_not', - description=_('Food IDs, repeat for multiple. Exclude recipes with any of the foods.'), type=int, - many=True), - OpenApiParameter(name='foods_and_not', - description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), type=int, - many=True), - OpenApiParameter(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.'), - type=int, many=True), - OpenApiParameter(name='books_or', - description=_('Book IDs, repeat for multiple. Return recipes with any of the books'), type=int, - many=True), - OpenApiParameter(name='books_and', - description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), type=int, - many=True), - OpenApiParameter(name='books_or_not', - description=_('Book IDs, repeat for multiple. Exclude recipes with any of the books.'), type=int, - many=True), - OpenApiParameter(name='books_and_not', - description=_('Book IDs, repeat for multiple. Exclude recipes with all of the books.'), type=int, - many=True), + OpenApiParameter(name='query', description=_('Query string matched (fuzzy) against recipe name. In the future also fulltext search.'), type=str), + + OpenApiParameter(name='keywords', description=_('ID of keyword a recipe should have. For multiple repeat parameter. Equivalent to keywords_or'), type=int, many=True), + OpenApiParameter(name='keywords_or', description=_('Keyword IDs, repeat for multiple. Return recipes with any of the keywords'), type=int, many=True), + OpenApiParameter(name='keywords_and', description=_('Keyword IDs, repeat for multiple. Return recipes with all of the keywords.'), type=int, many=True), + OpenApiParameter(name='keywords_or_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with any of the keywords.'), type=int, many=True), + OpenApiParameter(name='keywords_and_not', description=_('Keyword IDs, repeat for multiple. Exclude recipes with all of the keywords.'), type=int, many=True), + + OpenApiParameter(name='foods', description=_('ID of food a recipe should have. For multiple repeat parameter.'), type=int, many=True), + OpenApiParameter(name='foods_or', description=_('Food IDs, repeat for multiple. Return recipes with any of the foods'), type=int, many=True), + OpenApiParameter(name='foods_and', description=_('Food IDs, repeat for multiple. Return recipes with all of the foods.'), type=int, many=True), + OpenApiParameter(name='foods_or_not', description=_('Food IDs, repeat for multiple. Exclude recipes with any of the foods.'), type=int, many=True), + OpenApiParameter(name='foods_and_not', description=_('Food IDs, repeat for multiple. Exclude recipes with all of the foods.'), type=int, many=True), + + OpenApiParameter(name='books', description=_('ID of book a recipe should be in. For multiple repeat parameter.'), type=int, many=True), + OpenApiParameter(name='books_or', description=_('Book IDs, repeat for multiple. Return recipes with any of the books'), type=int, many=True), + OpenApiParameter(name='books_and', description=_('Book IDs, repeat for multiple. Return recipes with all of the books.'), type=int, many=True), + OpenApiParameter(name='books_or_not', description=_('Book IDs, repeat for multiple. Exclude recipes with any of the books.'), type=int, many=True), + 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='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', - description=_('Returns new results first in search results. [''true''/''false'']')), - OpenApiParameter(name='num_recent', description=_( - 'Returns the given number of recently viewed recipes before search results (if given)'), type=int), - OpenApiParameter(name='timescooked', description=_( - 'Filter recipes cooked X times or more. Negative values returns cooked less than X times'), type=int), - OpenApiParameter( - name='cookedon', - description=_('Filter recipes last cooked on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.'), - type=str, - examples=[DateExample, BeforeDateExample] - ), - OpenApiParameter( - name='createdon', - description=_('Filter recipes created on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.'), - type=str, - examples=[DateExample, BeforeDateExample] - ), - OpenApiParameter( - name='createdby', - description=_('Filter recipes for ones created by the given user ID'), - type=int, - ), - OpenApiParameter( - name='updatedon', - description=_('Filter recipes updated on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.'), - type=str, - examples=[DateExample, BeforeDateExample] - ), - OpenApiParameter( - name='viewedon', - description=_( - 'Filter recipes lasts viewed on or after YYYY-MM-DD. Prepending ''-'' filters on or before date.'), - type=str, - examples=[DateExample, BeforeDateExample] - ), - OpenApiParameter(name='makenow', - description=_('Filter recipes that can be made with OnHand food. [''true''/''false'']'), - type=bool), + OpenApiParameter(name='timescooked_gte', description=_('Filter recipes cooked X times or more.'), type=int), + OpenApiParameter(name='timescooked_lte', description=_('Filter recipes cooked X times or less.'), type=int), + + OpenApiParameter(name='createdon', description=_('Filter recipes created on the given date.'), type=OpenApiTypes.DATE, ), + OpenApiParameter(name='createdon_gte', description=_('Filter recipes created on the given date or after.'), type=OpenApiTypes.DATE, ), + OpenApiParameter(name='createdon_lte', description=_('Filter recipes created on the given date or before.'), type=OpenApiTypes.DATE, ), + + OpenApiParameter(name='updatedon', description=_('Filter recipes updated on the given date.'), type=OpenApiTypes.DATE, ), + OpenApiParameter(name='updatedon_gte', description=_('Filter recipes updated on the given date.'), type=OpenApiTypes.DATE, ), + OpenApiParameter(name='updatedon_lte', description=_('Filter recipes updated on the given date.'), type=OpenApiTypes.DATE, ), + + OpenApiParameter(name='cookedon_gte', description=_('Filter recipes last cooked on the given date or after.'), type=OpenApiTypes.DATE), + OpenApiParameter(name='cookedon_lte', description=_('Filter recipes last cooked on the given date or before.'), type=OpenApiTypes.DATE), + + OpenApiParameter(name='viewedon_gte', description=_('Filter recipes lasts viewed on the given date.'), type=OpenApiTypes.DATE, ), + OpenApiParameter(name='viewedon_lte', description=_('Filter recipes lasts viewed on the given date.'), type=OpenApiTypes.DATE, ), + + OpenApiParameter(name='createdby', description=_('Filter recipes for ones created by the given user ID'), type=int), + OpenApiParameter(name='internal', description=_('If only internal recipes should be returned. [''true''/''false'']'), type=bool), + OpenApiParameter(name='random', description=_('Returns the results in randomized order. [''true''/''false'']')), + OpenApiParameter(name='new', description=_('Returns new results first in search results. [''true''/''false'']')), + OpenApiParameter(name='num_recent', description=_('Returns the given number of recently viewed recipes before search results (if given)'), type=int), + OpenApiParameter(name='makenow', description=_('Filter recipes that can be made with OnHand food. [''true''/''false'']'), type=bool), ])) class RecipeViewSet(LoggingMixin, viewsets.ModelViewSet): queryset = Recipe.objects @@ -1944,7 +1898,8 @@ class FdcSearchView(APIView): permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope] @extend_schema(responses=FdcQuerySerializer(many=False), - parameters=[OpenApiParameter(name='query', type=str), OpenApiParameter(name='dataType', description='options: Branded,Foundation,Survey (FNDDS),SR Legacy', type=str, many=True)]) + parameters=[OpenApiParameter(name='query', type=str), + OpenApiParameter(name='dataType', description='options: Branded,Foundation,Survey (FNDDS),SR Legacy', type=str, many=True)]) def get(self, request, format=None): query = self.request.query_params.get('query', None) if query is not None: @@ -1952,7 +1907,6 @@ class FdcSearchView(APIView): response = requests.get(f'https://api.nal.usda.gov/fdc/v1/foods/search?api_key={FDC_API_KEY}&query={query}&dataType={",".join(data_types)}') - if response.status_code == 429: return JsonResponse( { diff --git a/vue3/src/openapi/apis/ApiApi.ts b/vue3/src/openapi/apis/ApiApi.ts index 65a7f59cb..b048c2bdf 100644 --- a/vue3/src/openapi/apis/ApiApi.ts +++ b/vue3/src/openapi/apis/ApiApi.ts @@ -1195,9 +1195,12 @@ export interface ApiRecipeListRequest { booksAndNot?: Array; booksOr?: Array; booksOrNot?: Array; - cookedon?: string; + cookedonGte?: Date; + cookedonLte?: Date; createdby?: number; - createdon?: string; + createdon?: Date; + createdonGte?: Date; + createdonLte?: Date; foods?: Array; foodsAnd?: Array; foodsAndNot?: Array; @@ -1219,10 +1222,14 @@ export interface ApiRecipeListRequest { rating?: number; ratingGte?: number; ratingLte?: number; - timescooked?: number; + timescookedGte?: number; + timescookedLte?: number; units?: number; - updatedon?: string; - viewedon?: string; + updatedon?: Date; + updatedonGte?: Date; + updatedonLte?: Date; + viewedonGte?: Date; + viewedonLte?: Date; } export interface ApiRecipePartialUpdateRequest { @@ -8794,8 +8801,12 @@ export class ApiApi extends runtime.BaseAPI { queryParameters['books_or_not'] = requestParameters['booksOrNot']; } - if (requestParameters['cookedon'] != null) { - queryParameters['cookedon'] = requestParameters['cookedon']; + if (requestParameters['cookedonGte'] != null) { + queryParameters['cookedon_gte'] = (requestParameters['cookedonGte'] as any).toISOString().substring(0,10); + } + + if (requestParameters['cookedonLte'] != null) { + queryParameters['cookedon_lte'] = (requestParameters['cookedonLte'] as any).toISOString().substring(0,10); } if (requestParameters['createdby'] != null) { @@ -8803,7 +8814,15 @@ export class ApiApi extends runtime.BaseAPI { } if (requestParameters['createdon'] != null) { - queryParameters['createdon'] = requestParameters['createdon']; + queryParameters['createdon'] = (requestParameters['createdon'] as any).toISOString().substring(0,10); + } + + if (requestParameters['createdonGte'] != null) { + queryParameters['createdon_gte'] = (requestParameters['createdonGte'] as any).toISOString().substring(0,10); + } + + if (requestParameters['createdonLte'] != null) { + queryParameters['createdon_lte'] = (requestParameters['createdonLte'] as any).toISOString().substring(0,10); } if (requestParameters['foods'] != null) { @@ -8890,8 +8909,12 @@ export class ApiApi extends runtime.BaseAPI { queryParameters['rating_lte'] = requestParameters['ratingLte']; } - if (requestParameters['timescooked'] != null) { - queryParameters['timescooked'] = requestParameters['timescooked']; + if (requestParameters['timescookedGte'] != null) { + queryParameters['timescooked_gte'] = requestParameters['timescookedGte']; + } + + if (requestParameters['timescookedLte'] != null) { + queryParameters['timescooked_lte'] = requestParameters['timescookedLte']; } if (requestParameters['units'] != null) { @@ -8899,11 +8922,23 @@ export class ApiApi extends runtime.BaseAPI { } if (requestParameters['updatedon'] != null) { - queryParameters['updatedon'] = requestParameters['updatedon']; + queryParameters['updatedon'] = (requestParameters['updatedon'] as any).toISOString().substring(0,10); } - if (requestParameters['viewedon'] != null) { - queryParameters['viewedon'] = requestParameters['viewedon']; + if (requestParameters['updatedonGte'] != null) { + queryParameters['updatedon_gte'] = (requestParameters['updatedonGte'] as any).toISOString().substring(0,10); + } + + if (requestParameters['updatedonLte'] != null) { + queryParameters['updatedon_lte'] = (requestParameters['updatedonLte'] as any).toISOString().substring(0,10); + } + + if (requestParameters['viewedonGte'] != null) { + queryParameters['viewedon_gte'] = (requestParameters['viewedonGte'] as any).toISOString().substring(0,10); + } + + if (requestParameters['viewedonLte'] != null) { + queryParameters['viewedon_lte'] = (requestParameters['viewedonLte'] as any).toISOString().substring(0,10); } const headerParameters: runtime.HTTPHeaders = {}; diff --git a/vue3/src/pages/SearchPage.vue b/vue3/src/pages/SearchPage.vue index 516e7282c..7e8ec5fba 100644 --- a/vue3/src/pages/SearchPage.vue +++ b/vue3/src/pages/SearchPage.vue @@ -23,14 +23,16 @@ - +
+ +
@@ -87,7 +89,9 @@ >