WIP search date based filters

This commit is contained in:
vabene1111
2025-03-30 14:00:29 +02:00
parent dd1975817e
commit 22aa0d2cb7
5 changed files with 300 additions and 203 deletions

View File

@@ -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]):

View File

@@ -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''/''<b>false</b>'']'),
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''/''<b>false</b>'']')),
OpenApiParameter(name='new',
description=_('Returns new results first in search results. [''true''/''<b>false</b>'']')),
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''/''<b>false</b>'']'),
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''/''<b>false</b>'']'), type=bool),
OpenApiParameter(name='random', description=_('Returns the results in randomized order. [''true''/''<b>false</b>'']')),
OpenApiParameter(name='new', description=_('Returns new results first in search results. [''true''/''<b>false</b>'']')),
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''/''<b>false</b>'']'), 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(
{