From 595de0c5a123a1f13864f7046ebe2fff9db0da20 Mon Sep 17 00:00:00 2001 From: smilerz Date: Sat, 22 Apr 2023 17:56:01 -0500 Subject: [PATCH] fixed pytests --- cookbook/helper/recipe_search.py | 247 +++++++++++------- cookbook/tests/api/test_api_food.py | 6 +- cookbook/tests/conftest.py | 43 +-- cookbook/tests/factories/__init__.py | 4 +- cookbook/tests/other/test_makenow_filter.py | 59 +++-- .../other/test_recipe_full_text_search.py | 158 +++++++---- vue/src/components/IngredientComponent.vue | 6 +- 7 files changed, 334 insertions(+), 189 deletions(-) diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index a618efb1..93dbe2de 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -3,9 +3,9 @@ from collections import Counter from datetime import date, timedelta from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector, TrigramSimilarity -from django.core.cache import cache -from django.core.cache import caches -from django.db.models import (Avg, Case, Count, Exists, F, Func, Max, OuterRef, Q, Subquery, Value, When, FilteredRelation) +from django.core.cache import cache, caches +from django.db.models import (Avg, Case, Count, Exists, F, Func, Max, OuterRef, Q, Subquery, Value, + When) from django.db.models.functions import Coalesce, Lower, Substr from django.utils import timezone, translation from django.utils.translation import gettext as _ @@ -20,7 +20,8 @@ from recipes import settings # TODO create extensive tests to make sure ORs ANDs and various filters, sorting, etc work as expected # TODO consider creating a simpleListRecipe API that only includes minimum of recipe info and minimal filtering class RecipeSearch(): - _postgres = settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql'] + _postgres = settings.DATABASES['default']['ENGINE'] in [ + 'django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql'] def __init__(self, request, **params): self._request = request @@ -45,7 +46,8 @@ class RecipeSearch(): cache.set(CACHE_KEY, self._search_prefs, timeout=10) else: self._search_prefs = SearchPreference() - self._string = self._params.get('query').strip() if self._params.get('query', None) else None + self._string = self._params.get('query').strip( + ) if self._params.get('query', None) else None self._rating = self._params.get('rating', None) self._keywords = { 'or': self._params.get('keywords_or', None) or self._params.get('keywords', None), @@ -74,7 +76,8 @@ class RecipeSearch(): self._random = str2bool(self._params.get('random', False)) self._new = str2bool(self._params.get('new', False)) self._num_recent = int(self._params.get('num_recent', 0)) - self._include_children = str2bool(self._params.get('include_children', None)) + 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._createdon = self._params.get('createdon', None) @@ -95,18 +98,24 @@ class RecipeSearch(): self._search_type = self._search_prefs.search or 'plain' if self._string: if self._postgres: - self._unaccent_include = self._search_prefs.unaccent.values_list('field', flat=True) + self._unaccent_include = self._search_prefs.unaccent.values_list( + 'field', flat=True) else: self._unaccent_include = [] - self._icontains_include = [x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.icontains.values_list('field', flat=True)] - self._istartswith_include = [x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.istartswith.values_list('field', flat=True)] + self._icontains_include = [ + x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.icontains.values_list('field', flat=True)] + self._istartswith_include = [ + x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.istartswith.values_list('field', flat=True)] self._trigram_include = None self._fulltext_include = None self._trigram = False if self._postgres and self._string: - self._language = DICTIONARY.get(translation.get_language(), 'simple') - self._trigram_include = [x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.trigram.values_list('field', flat=True)] - self._fulltext_include = self._search_prefs.fulltext.values_list('field', flat=True) or None + self._language = DICTIONARY.get( + translation.get_language(), 'simple') + self._trigram_include = [ + x + '__unaccent' if x in self._unaccent_include else x for x in self._search_prefs.trigram.values_list('field', flat=True)] + self._fulltext_include = self._search_prefs.fulltext.values_list( + 'field', flat=True) or None if self._search_type not in ['websearch', 'raw'] and self._trigram_include: self._trigram = True @@ -182,8 +191,10 @@ class RecipeSearch(): # otherwise sort by the remaining order_by attributes or favorite by default else: order += default_order - order[:] = [Lower('name').asc() if x == 'name' else x for x in order] - order[:] = [Lower('name').desc() if x == '-name' else x for x in order] + order[:] = [Lower('name').asc() if x == + 'name' else x for x in order] + order[:] = [Lower('name').desc() if x == + '-name' else x for x in order] self.orderby = order def string_filters(self, string=None): @@ -200,21 +211,28 @@ class RecipeSearch(): for f in self._filters: query_filter |= f - self._queryset = self._queryset.filter(query_filter).distinct() # this creates duplicate records which can screw up other aggregates, see makenow for workaround + # this creates duplicate records which can screw up other aggregates, see makenow for workaround + self._queryset = self._queryset.filter(query_filter).distinct() if self._fulltext_include: if self._fuzzy_match is None: - self._queryset = self._queryset.annotate(score=Coalesce(Max(self.search_rank), 0.0)) + self._queryset = self._queryset.annotate( + score=Coalesce(Max(self.search_rank), 0.0)) else: - self._queryset = self._queryset.annotate(rank=Coalesce(Max(self.search_rank), 0.0)) + self._queryset = self._queryset.annotate( + rank=Coalesce(Max(self.search_rank), 0.0)) if self._fuzzy_match is not None: - simularity = self._fuzzy_match.filter(pk=OuterRef('pk')).values('simularity') + simularity = self._fuzzy_match.filter( + pk=OuterRef('pk')).values('simularity') if not self._fulltext_include: - self._queryset = self._queryset.annotate(score=Coalesce(Subquery(simularity), 0.0)) + self._queryset = self._queryset.annotate( + score=Coalesce(Subquery(simularity), 0.0)) else: - self._queryset = self._queryset.annotate(simularity=Coalesce(Subquery(simularity), 0.0)) + self._queryset = self._queryset.annotate( + simularity=Coalesce(Subquery(simularity), 0.0)) if self._sort_includes('score') and self._fulltext_include and self._fuzzy_match is not None: - self._queryset = self._queryset.annotate(score=F('rank') + F('simularity')) + self._queryset = self._queryset.annotate( + score=F('rank') + F('simularity')) else: query_filter = Q() for f in [x + '__unaccent__iexact' if x in self._unaccent_include else x + '__iexact' for x in SearchFields.objects.all().values_list('field', flat=True)]: @@ -223,7 +241,8 @@ class RecipeSearch(): 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] + lessthan = self._sort_includes( + '-lastcooked') or '-' in (cooked_date or [])[:1] if lessthan: default = timezone.now() - timedelta(days=100000) else: @@ -233,32 +252,41 @@ class RecipeSearch(): if cooked_date is None: return - cooked_date = date(*[int(x) for x in cooked_date.split('-') if x != '']) + 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) + 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) + self._queryset = self._queryset.filter( + lastcooked__date__gte=cooked_date).exclude(lastcooked=default) def _created_on_filter(self, created_date=None): if created_date is None: return lessthan = '-' in created_date[:1] - created_date = date(*[int(x) for x in created_date.split('-') if x != '']) + created_date = date(*[int(x) + for x in created_date.split('-') if x != '']) if lessthan: - self._queryset = self._queryset.filter(created_at__date__lte=created_date) + self._queryset = self._queryset.filter( + created_at__date__lte=created_date) else: - self._queryset = self._queryset.filter(created_at__date__gte=created_date) + self._queryset = self._queryset.filter( + created_at__date__gte=created_date) def _updated_on_filter(self, updated_date=None): if updated_date is None: return lessthan = '-' in updated_date[:1] - updated_date = date(*[int(x) for x in updated_date.split('-') if x != '']) + updated_date = date(*[int(x) + for x in updated_date.split('-') if x != '']) if lessthan: - self._queryset = self._queryset.filter(updated_at__date__lte=updated_date) + self._queryset = self._queryset.filter( + updated_at__date__lte=updated_date) else: - self._queryset = self._queryset.filter(updated_at__date__gte=updated_date) + self._queryset = self._queryset.filter( + updated_at__date__gte=updated_date) def _viewed_on_filter(self, viewed_date=None): if self._sort_includes('lastviewed') or viewed_date: @@ -268,12 +296,15 @@ class RecipeSearch(): if viewed_date is None: return lessthan = '-' in viewed_date[:1] - viewed_date = date(*[int(x) for x in viewed_date.split('-') if x != '']) + viewed_date = date(*[int(x) + for x in viewed_date.split('-') if x != '']) if lessthan: - self._queryset = self._queryset.filter(lastviewed__date__lte=viewed_date).exclude(lastviewed=longTimeAgo) + self._queryset = self._queryset.filter( + lastviewed__date__lte=viewed_date).exclude(lastviewed=longTimeAgo) else: - self._queryset = self._queryset.filter(lastviewed__date__gte=viewed_date).exclude(lastviewed=longTimeAgo) + self._queryset = self._queryset.filter( + lastviewed__date__gte=viewed_date).exclude(lastviewed=longTimeAgo) def _new_recipes(self, new_days=7): # TODO make new days a user-setting @@ -293,27 +324,32 @@ class RecipeSearch(): num_recent_recipes = ViewLog.objects.filter(created_by=self._request.user, space=self._request.space).values( 'recipe').annotate(recent=Max('created_at')).order_by('-recent')[:num_recent] - self._queryset = self._queryset.annotate(recent=Coalesce(Max(Case(When(pk__in=num_recent_recipes.values('recipe'), then='viewlog__pk'))), Value(0))) + 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 (times_cooked or []) or not self._sort_includes('-favorite') + less_than = '-' in (times_cooked or [] + ) and not self._sort_includes('-favorite') if less_than: default = 1000 else: default = 0 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), default)) + self._queryset = self._queryset.annotate( + favorite=Coalesce(Subquery(favorite_recipes), default)) if times_cooked is None: return if times_cooked == '0': self._queryset = self._queryset.filter(favorite=0) elif less_than: - self._queryset = self._queryset.filter(favorite__lte=int(times_cooked[1:])).exclude(favorite=0) + 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)) + self._queryset = self._queryset.filter( + favorite__gte=int(times_cooked)) def keyword_filters(self, **kwargs): if all([kwargs[x] is None for x in kwargs]): @@ -346,7 +382,8 @@ class RecipeSearch(): else: self._queryset = self._queryset.filter(f_and) if 'not' in kw_filter: - self._queryset = self._queryset.exclude(id__in=recipes.values('id')) + self._queryset = self._queryset.exclude( + id__in=recipes.values('id')) def food_filters(self, **kwargs): if all([kwargs[x] is None for x in kwargs]): @@ -360,7 +397,8 @@ class RecipeSearch(): foods = Food.objects.filter(pk__in=kwargs[fd_filter]) if 'or' in fd_filter: if self._include_children: - f_or = Q(steps__ingredients__food__in=Food.include_descendants(foods)) + f_or = Q( + steps__ingredients__food__in=Food.include_descendants(foods)) else: f_or = Q(steps__ingredients__food__in=foods) @@ -372,7 +410,8 @@ class RecipeSearch(): recipes = Recipe.objects.all() for food in foods: if self._include_children: - f_and = Q(steps__ingredients__food__in=food.get_descendants_and_self()) + f_and = Q( + steps__ingredients__food__in=food.get_descendants_and_self()) else: f_and = Q(steps__ingredients__food=food) if 'not' in fd_filter: @@ -380,7 +419,8 @@ class RecipeSearch(): else: self._queryset = self._queryset.filter(f_and) if 'not' in fd_filter: - self._queryset = self._queryset.exclude(id__in=recipes.values('id')) + self._queryset = self._queryset.exclude( + id__in=recipes.values('id')) def unit_filters(self, units=None, operator=True): if operator != True: @@ -389,7 +429,8 @@ class RecipeSearch(): return if not isinstance(units, list): 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): if rating or self._sort_includes('rating'): @@ -399,14 +440,16 @@ class RecipeSearch(): 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)))) + 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': self._queryset = self._queryset.filter(rating=0) elif lessthan: - self._queryset = self._queryset.filter(rating__lte=int(rating[1:])).exclude(rating=0) + self._queryset = self._queryset.filter( + rating__lte=int(rating[1:])).exclude(rating=0) else: self._queryset = self._queryset.filter(rating__gte=int(rating)) @@ -434,11 +477,14 @@ class RecipeSearch(): recipes = Recipe.objects.all() for book in kwargs[bk_filter]: if 'not' in bk_filter: - recipes = recipes.filter(recipebookentry__book__id=book) + recipes = recipes.filter( + recipebookentry__book__id=book) else: - self._queryset = self._queryset.filter(recipebookentry__book__id=book) + self._queryset = self._queryset.filter( + recipebookentry__book__id=book) if 'not' in bk_filter: - self._queryset = self._queryset.exclude(id__in=recipes.values('id')) + self._queryset = self._queryset.exclude( + id__in=recipes.values('id')) def step_filters(self, steps=None, operator=True): if operator != True: @@ -446,7 +492,7 @@ class RecipeSearch(): if not steps: return if not isinstance(steps, list): - steps = [unistepsts] + steps = [steps] self._queryset = self._queryset.filter(steps__id__in=steps) def build_fulltext_filters(self, string=None): @@ -457,20 +503,25 @@ class RecipeSearch(): rank = [] if 'name' in self._fulltext_include: vectors.append('name_search_vector') - rank.append(SearchRank('name_search_vector', self.search_query, cover_density=True)) + rank.append(SearchRank('name_search_vector', + self.search_query, cover_density=True)) if 'description' in self._fulltext_include: vectors.append('desc_search_vector') - rank.append(SearchRank('desc_search_vector', self.search_query, cover_density=True)) + rank.append(SearchRank('desc_search_vector', + self.search_query, cover_density=True)) if 'steps__instruction' in self._fulltext_include: vectors.append('steps__search_vector') - rank.append(SearchRank('steps__search_vector', self.search_query, cover_density=True)) + rank.append(SearchRank('steps__search_vector', + self.search_query, cover_density=True)) if 'keywords__name' in self._fulltext_include: # explicitly settings unaccent on keywords and foods so that they behave the same as search_vector fields vectors.append('keywords__name__unaccent') - rank.append(SearchRank('keywords__name__unaccent', self.search_query, cover_density=True)) + rank.append(SearchRank('keywords__name__unaccent', + self.search_query, cover_density=True)) if 'steps__ingredients__food__name' in self._fulltext_include: vectors.append('steps__ingredients__food__name__unaccent') - rank.append(SearchRank('steps__ingredients__food__name', self.search_query, cover_density=True)) + rank.append(SearchRank('steps__ingredients__food__name', + self.search_query, cover_density=True)) for r in rank: if self.search_rank is None: @@ -478,7 +529,8 @@ class RecipeSearch(): else: self.search_rank += r # modifying queryset will annotation creates duplicate results - self._filters.append(Q(id__in=Recipe.objects.annotate(vector=SearchVector(*vectors)).filter(Q(vector=self.search_query)))) + self._filters.append(Q(id__in=Recipe.objects.annotate( + vector=SearchVector(*vectors)).filter(Q(vector=self.search_query)))) def build_text_filters(self, string=None): if not string: @@ -510,23 +562,30 @@ class RecipeSearch(): def _makenow_filter(self, missing=None): if missing is None or (type(missing) == bool and missing == False): return - shopping_users = [*self._request.user.get_shopping_share(), self._request.user] + shopping_users = [ + *self._request.user.get_shopping_share(), self._request.user] onhand_filter = ( - Q(steps__ingredients__food__onhand_users__in=shopping_users) # food onhand - | Q(steps__ingredients__food__substitute__onhand_users__in=shopping_users) # or substitute food onhand - | Q(steps__ingredients__food__in=self.__children_substitute_filter(shopping_users)) - | Q(steps__ingredients__food__in=self.__sibling_substitute_filter(shopping_users)) + Q(steps__ingredients__food__onhand_users__in=shopping_users) # food onhand + # or substitute food onhand + | Q(steps__ingredients__food__substitute__onhand_users__in=shopping_users) + | Q(steps__ingredients__food__in=self.__children_substitute_filter(shopping_users)) + | Q(steps__ingredients__food__in=self.__sibling_substitute_filter(shopping_users)) ) makenow_recipes = Recipe.objects.annotate( - count_food=Count('steps__ingredients__food__pk', filter=Q(steps__ingredients__food__isnull=False), distinct=True), - count_onhand=Count('steps__ingredients__food__pk', filter=onhand_filter, distinct=True), + count_food=Count('steps__ingredients__food__pk', filter=Q( + steps__ingredients__food__isnull=False), distinct=True), + count_onhand=Count('steps__ingredients__food__pk', + filter=onhand_filter, distinct=True), count_ignore_shopping=Count('steps__ingredients__food__pk', filter=Q(steps__ingredients__food__ignore_shopping=True, steps__ingredients__food__recipe__isnull=True), distinct=True), - has_child_sub=Case(When(steps__ingredients__food__in=self.__children_substitute_filter(shopping_users), then=Value(1)), default=Value(0)), - has_sibling_sub=Case(When(steps__ingredients__food__in=self.__sibling_substitute_filter(shopping_users), then=Value(1)), default=Value(0)) + has_child_sub=Case(When(steps__ingredients__food__in=self.__children_substitute_filter( + shopping_users), then=Value(1)), default=Value(0)), + has_sibling_sub=Case(When(steps__ingredients__food__in=self.__sibling_substitute_filter( + shopping_users), then=Value(1)), default=Value(0)) ).annotate(missingfood=F('count_food') - F('count_onhand') - F('count_ignore_shopping')).filter(missingfood=missing) - self._queryset = self._queryset.distinct().filter(id__in=makenow_recipes.values('id')) + self._queryset = self._queryset.distinct().filter( + id__in=makenow_recipes.values('id')) @staticmethod def __children_substitute_filter(shopping_users=None): @@ -547,7 +606,8 @@ class RecipeSearch(): @staticmethod def __sibling_substitute_filter(shopping_users=None): sibling_onhand_subquery = Food.objects.filter( - path__startswith=Substr(OuterRef('path'), 1, Food.steplen * (OuterRef('depth') - 1)), + path__startswith=Substr( + OuterRef('path'), 1, Food.steplen * (OuterRef('depth') - 1)), depth=OuterRef('depth'), onhand_users__in=shopping_users ) @@ -586,7 +646,8 @@ class RecipeFacet(): self.Recent = self._cache.get('Recent', None) if self._queryset is not None: - self._recipe_list = list(self._queryset.values_list('id', flat=True)) + self._recipe_list = list( + self._queryset.values_list('id', flat=True)) self._search_params = { 'keyword_list': self._request.query_params.getlist('keywords', []), 'food_list': self._request.query_params.getlist('foods', []), @@ -618,7 +679,8 @@ class RecipeFacet(): 'Books': self.Books } - caches['default'].set(self._SEARCH_CACHE_KEY, self._cache, self._cache_timeout) + caches['default'].set(self._SEARCH_CACHE_KEY, + self._cache, self._cache_timeout) def get_facets(self, from_cache=False): if from_cache: @@ -655,13 +717,16 @@ class RecipeFacet(): def get_keywords(self): if self.Keywords is None: if self._search_params['search_keywords_or']: - keywords = Keyword.objects.filter(space=self._request.space).distinct() + keywords = Keyword.objects.filter( + space=self._request.space).distinct() else: - keywords = Keyword.objects.filter(Q(recipe__in=self._recipe_list) | Q(depth=1)).filter(space=self._request.space).distinct() + keywords = Keyword.objects.filter(Q(recipe__in=self._recipe_list) | Q( + depth=1)).filter(space=self._request.space).distinct() # set keywords to root objects only keywords = self._keyword_queryset(keywords) - self.Keywords = [{**x, 'children': None} if x['numchild'] > 0 else x for x in list(keywords)] + self.Keywords = [{**x, 'children': None} + if x['numchild'] > 0 else x for x in list(keywords)] self.set_cache('Keywords', self.Keywords) return self.Keywords @@ -669,28 +734,28 @@ class RecipeFacet(): if self.Foods is None: # # if using an OR search, will annotate all keywords, otherwise, just those that appear in results if self._search_params['search_foods_or']: - foods = Food.objects.filter(space=self._request.space).distinct() + foods = Food.objects.filter( + space=self._request.space).distinct() else: - foods = Food.objects.filter(Q(ingredient__step__recipe__in=self._recipe_list) | Q(depth=1)).filter(space=self._request.space).distinct() + foods = Food.objects.filter(Q(ingredient__step__recipe__in=self._recipe_list) | Q( + depth=1)).filter(space=self._request.space).distinct() # set keywords to root objects only foods = self._food_queryset(foods) - self.Foods = [{**x, 'children': None} if x['numchild'] > 0 else x for x in list(foods)] + self.Foods = [{**x, 'children': None} + if x['numchild'] > 0 else x for x in list(foods)] self.set_cache('Foods', self.Foods) return self.Foods - def get_books(self): - if self.Books is None: - self.Books = [] - return self.Books - def get_ratings(self): if self.Ratings is None: if not self._request.space.demo and self._request.space.show_facet_count: if self._queryset is None: - self._queryset = Recipe.objects.filter(id__in=self._recipe_list) - rating_qs = self._queryset.annotate(rating=Round(Avg(Case(When(cooklog__created_by=self._request.user, then='cooklog__rating'), default=Value(0))))) + self._queryset = Recipe.objects.filter( + id__in=self._recipe_list) + rating_qs = self._queryset.annotate(rating=Round(Avg(Case(When( + cooklog__created_by=self._request.user, then='cooklog__rating'), default=Value(0))))) self.Ratings = dict(Counter(r.rating for r in rating_qs)) else: self.Rating = {} @@ -715,10 +780,13 @@ class RecipeFacet(): foods = self._food_queryset(food.get_children(), food) deep_search = self.Foods for node in nodes: - index = next((i for i, x in enumerate(deep_search) if x["id"] == node.id), None) + index = next((i for i, x in enumerate( + deep_search) if x["id"] == node.id), None) deep_search = deep_search[index]['children'] - index = next((i for i, x in enumerate(deep_search) if x["id"] == food.id), None) - deep_search[index]['children'] = [{**x, 'children': None} if x['numchild'] > 0 else x for x in list(foods)] + index = next((i for i, x in enumerate( + deep_search) if x["id"] == food.id), None) + deep_search[index]['children'] = [ + {**x, 'children': None} if x['numchild'] > 0 else x for x in list(foods)] self.set_cache('Foods', self.Foods) return self.get_facets() @@ -731,10 +799,13 @@ class RecipeFacet(): keywords = self._keyword_queryset(keyword.get_children(), keyword) deep_search = self.Keywords for node in nodes: - index = next((i for i, x in enumerate(deep_search) if x["id"] == node.id), None) + index = next((i for i, x in enumerate( + deep_search) if x["id"] == node.id), None) deep_search = deep_search[index]['children'] - index = next((i for i, x in enumerate(deep_search) if x["id"] == keyword.id), None) - deep_search[index]['children'] = [{**x, 'children': None} if x['numchild'] > 0 else x for x in list(keywords)] + index = next((i for i, x in enumerate(deep_search) + if x["id"] == keyword.id), None) + deep_search[index]['children'] = [ + {**x, 'children': None} if x['numchild'] > 0 else x for x in list(keywords)] self.set_cache('Keywords', self.Keywords) return self.get_facets() diff --git a/cookbook/tests/api/test_api_food.py b/cookbook/tests/api/test_api_food.py index 05060034..79c2d05d 100644 --- a/cookbook/tests/api/test_api_food.py +++ b/cookbook/tests/api/test_api_food.py @@ -6,7 +6,7 @@ from django.urls import reverse from django_scopes import scope, scopes_disabled from pytest_factoryboy import LazyFixture, register -from cookbook.models import Food, FoodInheritField, Ingredient, ShoppingList, ShoppingListEntry +from cookbook.models import Food, Ingredient, ShoppingListEntry from cookbook.tests.factories import (FoodFactory, IngredientFactory, ShoppingListEntryFactory, SupermarketCategoryFactory) @@ -433,6 +433,8 @@ def test_merge(u1_s1, obj_tree_1, obj_1, obj_3, space_1): def test_merge_errors(u1_s1, obj_tree_1, obj_3, space_1): with scope(space=space_1): + # for some reason the 'path' attribute changes between the factory and the test when using both obj_tree and obj + obj_tree_1 = Food.objects.get(id=obj_tree_1.id) parent = obj_tree_1.get_parent() # attempt to merge with non-existent parent @@ -507,7 +509,7 @@ def test_tree_filter(obj_tree_1, obj_2, obj_3, u1_s1): 'supermarket_category', True, 'cat_1'), ({'has_category': True, 'inherit': False}, 'supermarket_category', False, 'cat_1'), - ({'ignore_shopping': True, 'inherit': True}, 'ignore_shopping', True, 'false'), + ({'ignore_shopping': True, 'inherit': True}, 'ignore_shopping', True, 'false'), ({'ignore_shopping': True, 'inherit': False}, 'ignore_shopping', False, 'false'), ({'substitute_children': True, 'inherit': True}, diff --git a/cookbook/tests/conftest.py b/cookbook/tests/conftest.py index 2cfe7541..85f33145 100644 --- a/cookbook/tests/conftest.py +++ b/cookbook/tests/conftest.py @@ -5,12 +5,11 @@ import uuid import pytest from django.contrib import auth -from django.contrib.auth.models import Group, User from django_scopes import scopes_disabled -from pytest_factoryboy import LazyFixture, register +from pytest_factoryboy import register -from cookbook.models import Food, Ingredient, Recipe, Space, Step, Unit -from cookbook.tests.factories import FoodFactory, SpaceFactory, UserFactory +from cookbook.models import Food, Ingredient, Recipe, Step, Unit +from cookbook.tests.factories import SpaceFactory, UserFactory register(SpaceFactory, 'space_1') register(SpaceFactory, 'space_2') @@ -60,8 +59,10 @@ def get_random_recipe(space_1, u1_s1): internal=True, ) - s1 = Step.objects.create(name=str(uuid.uuid4()), instruction=str(uuid.uuid4()), space=space_1, ) - s2 = Step.objects.create(name=str(uuid.uuid4()), instruction=str(uuid.uuid4()), space=space_1, ) + s1 = Step.objects.create(name=str(uuid.uuid4()), + instruction=str(uuid.uuid4()), space=space_1, ) + s2 = Step.objects.create(name=str(uuid.uuid4()), + instruction=str(uuid.uuid4()), space=space_1, ) r.steps.add(s1) r.steps.add(s2) @@ -70,8 +71,10 @@ def get_random_recipe(space_1, u1_s1): s1.ingredients.add( Ingredient.objects.create( amount=1, - food=Food.objects.get_or_create(name=str(uuid.uuid4()), space=space_1)[0], - unit=Unit.objects.create(name=str(uuid.uuid4()), space=space_1, ), + food=Food.objects.get_or_create( + name=str(uuid.uuid4()), space=space_1)[0], + unit=Unit.objects.create( + name=str(uuid.uuid4()), space=space_1, ), note=str(uuid.uuid4()), space=space_1, ) @@ -80,8 +83,10 @@ def get_random_recipe(space_1, u1_s1): s2.ingredients.add( Ingredient.objects.create( amount=1, - food=Food.objects.get_or_create(name=str(uuid.uuid4()), space=space_1)[0], - unit=Unit.objects.create(name=str(uuid.uuid4()), space=space_1, ), + food=Food.objects.get_or_create( + name=str(uuid.uuid4()), space=space_1)[0], + unit=Unit.objects.create( + name=str(uuid.uuid4()), space=space_1, ), note=str(uuid.uuid4()), space=space_1, ) @@ -99,8 +104,10 @@ def get_random_json_recipe(): { "instruction": str(uuid.uuid4()), "ingredients": [ - {"food": {"name": str(uuid.uuid4())}, "unit": {"name": str(uuid.uuid4())}, "amount": random.randint(0, 10)}, - {"food": {"name": str(uuid.uuid4())}, "unit": {"name": str(uuid.uuid4())}, "amount": random.randint(0, 10)}, + {"food": {"name": str(uuid.uuid4())}, "unit": {"name": str( + uuid.uuid4())}, "amount": random.randint(0, 10)}, + {"food": {"name": str(uuid.uuid4())}, "unit": {"name": str( + uuid.uuid4())}, "amount": random.randint(0, 10)}, ], } ], @@ -133,7 +140,8 @@ def validate_recipe(expected, recipe): for key in expected_lists: for k in expected_lists[key]: try: - print('comparing ', any([dict_compare(k, i) for i in target_lists[key]])) + print('comparing ', any([dict_compare(k, i) + for i in target_lists[key]])) assert any([dict_compare(k, i) for i in target_lists[key]]) except AssertionError: for result in [dict_compare(k, i, details=True) for i in target_lists[key]]: @@ -152,7 +160,8 @@ def dict_compare(d1, d2, details=False): added = d1_keys - d2_keys removed = d2_keys - d1_keys modified = {o: (d1[o], d2[o]) for o in not_dicts if d1[o] != d2[o]} - modified_dicts = {o: (d1[o], d2[o]) for o in sub_dicts if not d1[o].items() <= d2[o].items()} + modified_dicts = {o: (d1[o], d2[o]) + for o in sub_dicts if not d1[o].items() <= d2[o].items()} if details: return added, removed, modified, modified_dicts else: @@ -173,12 +182,12 @@ def transpose(text, number=2): positions = random.sample(range(len(tokens[token_pos])), number) # swap the positions - l = list(tokens[token_pos]) + lt = list(tokens[token_pos]) for first, second in zip(positions[::2], positions[1::2]): - l[first], l[second] = l[second], l[first] + lt[first], lt[second] = lt[second], lt[first] # replace original tokens with swapped - tokens[token_pos] = ''.join(l) + tokens[token_pos] = ''.join(lt) # return text with the swapped token return ' '.join(tokens) diff --git a/cookbook/tests/factories/__init__.py b/cookbook/tests/factories/__init__.py index 1b514b4f..66e511f4 100644 --- a/cookbook/tests/factories/__init__.py +++ b/cookbook/tests/factories/__init__.py @@ -77,7 +77,7 @@ class UserFactory(factory.django.DjangoModelFactory): if extracted: for prefs in extracted: # intentionally break so it can be debugged later - self.userpreference[prefs] = extracted[prefs]/0 + self.userpreference[prefs] = extracted[prefs] / 0 class Meta: model = User @@ -233,7 +233,7 @@ class MealTypeFactory(factory.django.DjangoModelFactory): @register class MealPlanFactory(factory.django.DjangoModelFactory): recipe = factory.Maybe( - factory.LazyAttribute(lambda x: x.has_recipe), + factory.LazyAttribute(lambda x: x.has_recipe), yes_declaration=factory.SubFactory( 'cookbook.tests.factories.RecipeFactory', space=factory.SelfAttribute('..space')), no_declaration=None diff --git a/cookbook/tests/other/test_makenow_filter.py b/cookbook/tests/other/test_makenow_filter.py index 4da5e092..9e43df50 100644 --- a/cookbook/tests/other/test_makenow_filter.py +++ b/cookbook/tests/other/test_makenow_filter.py @@ -11,6 +11,11 @@ from cookbook.tests.factories import FoodFactory, RecipeFactory # TODO returns recipes with all ingredients via child substitute # TODO returns recipes with all ingredients via sibling substitute +if (Food.node_order_by): + node_location = 'sorted-child' +else: + node_location = 'last-child' + @pytest.fixture def recipes(space_1): @@ -19,7 +24,8 @@ def recipes(space_1): @pytest.fixture def makenow_recipe(request, space_1): - onhand_user = auth.get_user(request.getfixturevalue(request.param.get('onhand_users', 'u1_s1'))) + onhand_user = auth.get_user(request.getfixturevalue( + request.param.get('onhand_users', 'u1_s1'))) recipe = RecipeFactory.create(space=space_1) for food in Food.objects.filter(ingredient__step__recipe=recipe.id): @@ -55,13 +61,16 @@ def test_makenow_ignoreshopping(recipes, makenow_recipe, user1, space_1): request = type('', (object,), {'space': space_1, 'user': user1})() search = RecipeSearch(request, makenow='true') with scope(space=space_1): - food = Food.objects.filter(ingredient__step__recipe=makenow_recipe.id).first() + food = Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id).first() food.onhand_users.clear() assert search.get_queryset(Recipe.objects.all()).count() == 0 food.ignore_shopping = True food.save() - assert Food.objects.filter(ingredient__step__recipe=makenow_recipe.id, onhand_users__isnull=False).count() == 9 - assert Food.objects.filter(ingredient__step__recipe=makenow_recipe.id, ignore_shopping=True).count() == 1 + assert Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id, onhand_users__isnull=False).count() == 9 + assert Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id, ignore_shopping=True).count() == 1 search = search.get_queryset(Recipe.objects.all()) assert search.count() == 1 assert search.first().id == makenow_recipe.id @@ -74,13 +83,17 @@ def test_makenow_substitute(recipes, makenow_recipe, user1, space_1): request = type('', (object,), {'space': space_1, 'user': user1})() search = RecipeSearch(request, makenow='true') with scope(space=space_1): - food = Food.objects.filter(ingredient__step__recipe=makenow_recipe.id).first() + food = Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id).first() onhand_user = food.onhand_users.first() food.onhand_users.clear() assert search.get_queryset(Recipe.objects.all()).count() == 0 - food.substitute.add(FoodFactory.create(space=space_1, onhand_users=[onhand_user])) - assert Food.objects.filter(ingredient__step__recipe=makenow_recipe.id, onhand_users__isnull=False).count() == 9 - assert Food.objects.filter(ingredient__step__recipe=makenow_recipe.id, substitute__isnull=False).count() == 1 + food.substitute.add(FoodFactory.create( + space=space_1, onhand_users=[onhand_user])) + assert Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id, onhand_users__isnull=False).count() == 9 + assert Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id, substitute__isnull=False).count() == 1 search = search.get_queryset(Recipe.objects.all()) assert search.count() == 1 @@ -94,16 +107,20 @@ def test_makenow_child_substitute(recipes, makenow_recipe, user1, space_1): request = type('', (object,), {'space': space_1, 'user': user1})() search = RecipeSearch(request, makenow='true') with scope(space=space_1): - food = Food.objects.filter(ingredient__step__recipe=makenow_recipe.id).first() + food = Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id).first() onhand_user = food.onhand_users.first() food.onhand_users.clear() food.substitute_children = True food.save() assert search.get_queryset(Recipe.objects.all()).count() == 0 - new_food = FoodFactory.create(space=space_1, onhand_users=[onhand_user]) - new_food.move(food, 'first-child') - assert Food.objects.filter(ingredient__step__recipe=makenow_recipe.id, onhand_users__isnull=False).count() == 9 - assert Food.objects.filter(ingredient__step__recipe=makenow_recipe.id, numchild__gt=0).count() == 1 + new_food = FoodFactory.create( + space=space_1, onhand_users=[onhand_user]) + new_food.move(food, node_location) + assert Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id, onhand_users__isnull=False).count() == 9 + assert Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id, numchild__gt=0).count() == 1 search = search.get_queryset(Recipe.objects.all()) assert search.count() == 1 assert search.first().id == makenow_recipe.id @@ -116,18 +133,22 @@ def test_makenow_sibling_substitute(recipes, makenow_recipe, user1, space_1): request = type('', (object,), {'space': space_1, 'user': user1})() search = RecipeSearch(request, makenow='true') with scope(space=space_1): - food = Food.objects.filter(ingredient__step__recipe=makenow_recipe.id).first() + food = Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id).first() onhand_user = food.onhand_users.first() food.onhand_users.clear() food.substitute_siblings = True food.save() assert search.get_queryset(Recipe.objects.all()).count() == 0 new_parent = FoodFactory.create(space=space_1) - new_sibling = FoodFactory.create(space=space_1, onhand_users=[onhand_user]) - new_sibling.move(new_parent, 'first-child') - food.move(new_parent, 'first-child') - assert Food.objects.filter(ingredient__step__recipe=makenow_recipe.id, onhand_users__isnull=False).count() == 9 - assert Food.objects.filter(ingredient__step__recipe=makenow_recipe.id, depth=2).count() == 1 + new_sibling = FoodFactory.create( + space=space_1, onhand_users=[onhand_user]) + new_sibling.move(new_parent, node_location) + food.move(new_parent, node_location) + assert Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id, onhand_users__isnull=False).count() == 9 + assert Food.objects.filter( + ingredient__step__recipe=makenow_recipe.id, depth=2).count() == 1 search = search.get_queryset(Recipe.objects.all()) assert search.count() == 1 assert search.first().id == makenow_recipe.id diff --git a/cookbook/tests/other/test_recipe_full_text_search.py b/cookbook/tests/other/test_recipe_full_text_search.py index 60ca6162..3e119585 100644 --- a/cookbook/tests/other/test_recipe_full_text_search.py +++ b/cookbook/tests/other/test_recipe_full_text_search.py @@ -7,9 +7,9 @@ from django.conf import settings from django.contrib import auth from django.urls import reverse from django.utils import timezone -from django_scopes import scope, scopes_disabled +from django_scopes import scope -from cookbook.models import Food, Recipe, SearchFields +from cookbook.models import Recipe, SearchFields from cookbook.tests.conftest import transpose from cookbook.tests.factories import (CookLogFactory, FoodFactory, IngredientFactory, KeywordFactory, RecipeBookEntryFactory, RecipeFactory, @@ -23,7 +23,8 @@ from cookbook.tests.factories import (CookLogFactory, FoodFactory, IngredientFac # TODO makenow with above filters # TODO test search food/keywords including/excluding children LIST_URL = 'api:recipe-list' -sqlite = settings.DATABASES['default']['ENGINE'] not in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql'] +sqlite = settings.DATABASES['default']['ENGINE'] not in [ + 'django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql'] @pytest.fixture @@ -50,26 +51,43 @@ def user1(request, space_1, u1_s1, unaccent): if params.get('fuzzy_lookups', False): user.searchpreference.lookup = True misspelled_result = 1 + else: + user.searchpreference.lookup = False + if params.get('fuzzy_search', False): user.searchpreference.trigram.set(SearchFields.objects.all()) misspelled_result = 1 + else: + user.searchpreference.trigram.set([]) if params.get('icontains', False): user.searchpreference.icontains.set(SearchFields.objects.all()) search_term = 'ghijklmn' + else: + user.searchpreference.icontains.set([]) + if params.get('istartswith', False): user.searchpreference.istartswith.set(SearchFields.objects.all()) search_term = 'abcdef' + else: + user.searchpreference.istartswith.set([]) + if params.get('unaccent', False): user.searchpreference.unaccent.set(SearchFields.objects.all()) misspelled_result *= 2 result *= 2 + else: + user.searchpreference.unaccent.set([]) + # full text vectors are hard coded to use unaccent - put this after unaccent to override result if params.get('fulltext', False): user.searchpreference.fulltext.set(SearchFields.objects.all()) # user.searchpreference.search = 'websearch' search_term = 'ghijklmn uvwxyz' result = 2 + else: + user.searchpreference.fulltext.set([]) + user.searchpreference.save() misspelled_term = transpose(search_term, number=3) return (u1_s1, result, misspelled_result, search_term, misspelled_term, params) @@ -104,7 +122,8 @@ def found_recipe(request, space_1, accent, unaccent, u1_s1, u2_s1): obj2 = FoodFactory.create(name=accent, space=space_1) recipe1.steps.first().ingredients.add(IngredientFactory.create(food=obj1)) recipe2.steps.first().ingredients.add(IngredientFactory.create(food=obj2)) - recipe3.steps.first().ingredients.add(IngredientFactory.create(food=obj1), IngredientFactory.create(food=obj2)) + recipe3.steps.first().ingredients.add(IngredientFactory.create( + food=obj1), IngredientFactory.create(food=obj2)) if request.param.get('keyword', None): obj1 = KeywordFactory.create(name=unaccent, space=space_1) obj2 = KeywordFactory.create(name=accent, space=space_1) @@ -125,7 +144,8 @@ def found_recipe(request, space_1, accent, unaccent, u1_s1, u2_s1): obj2 = UnitFactory.create(name=accent, space=space_1) recipe1.steps.first().ingredients.add(IngredientFactory.create(unit=obj1)) recipe2.steps.first().ingredients.add(IngredientFactory.create(unit=obj2)) - recipe3.steps.first().ingredients.add(IngredientFactory.create(unit=obj1), IngredientFactory.create(unit=obj2)) + recipe3.steps.first().ingredients.add(IngredientFactory.create( + unit=obj1), IngredientFactory.create(unit=obj2)) if request.param.get('name', None): recipe1.name = unaccent recipe2.name = accent @@ -145,21 +165,32 @@ def found_recipe(request, space_1, accent, unaccent, u1_s1, u2_s1): i2.save() if request.param.get('viewedon', None): - ViewLogFactory.create(recipe=recipe1, created_by=user1, created_at=days_3, space=space_1) - ViewLogFactory.create(recipe=recipe2, created_by=user1, created_at=days_30, space=space_1) - ViewLogFactory.create(recipe=recipe3, created_by=user2, created_at=days_15, space=space_1) + ViewLogFactory.create(recipe=recipe1, created_by=user1, + created_at=days_3, space=space_1) + ViewLogFactory.create(recipe=recipe2, created_by=user1, + created_at=days_30, space=space_1) + ViewLogFactory.create(recipe=recipe3, created_by=user2, + created_at=days_15, space=space_1) if request.param.get('cookedon', None): - CookLogFactory.create(recipe=recipe1, created_by=user1, created_at=days_3, space=space_1) - CookLogFactory.create(recipe=recipe2, created_by=user1, created_at=days_30, space=space_1) - CookLogFactory.create(recipe=recipe3, created_by=user2, created_at=days_15, space=space_1) + CookLogFactory.create(recipe=recipe1, created_by=user1, + created_at=days_3, space=space_1) + CookLogFactory.create(recipe=recipe2, created_by=user1, + created_at=days_30, space=space_1) + CookLogFactory.create(recipe=recipe3, created_by=user2, + created_at=days_15, space=space_1) if request.param.get('timescooked', None): - CookLogFactory.create_batch(5, recipe=recipe1, created_by=user1, space=space_1) - CookLogFactory.create(recipe=recipe2, created_by=user1, space=space_1) - CookLogFactory.create_batch(3, recipe=recipe3, created_by=user2, space=space_1) + CookLogFactory.create_batch( + 5, recipe=recipe1, created_by=user1, space=space_1) + CookLogFactory.create(recipe=recipe2, created_by=user1, space=space_1) + CookLogFactory.create_batch( + 3, recipe=recipe3, created_by=user2, space=space_1) if request.param.get('rating', None): - CookLogFactory.create(recipe=recipe1, created_by=user1, rating=5.0, space=space_1) - CookLogFactory.create(recipe=recipe2, created_by=user1, rating=1.0, space=space_1) - CookLogFactory.create(recipe=recipe3, created_by=user2, rating=3.0, space=space_1) + CookLogFactory.create( + recipe=recipe1, created_by=user1, rating=5.0, space=space_1) + CookLogFactory.create( + recipe=recipe2, created_by=user1, rating=1.0, space=space_1) + CookLogFactory.create( + recipe=recipe3, created_by=user2, rating=3.0, space=space_1) return (recipe1, recipe2, recipe3, obj1, obj2, request.param) @@ -188,7 +219,8 @@ def test_search_or_and_not(found_recipe, param_type, operator, recipes, u1_s1, s assert found_recipe[1].id in [x['id'] for x in r['results']] assert found_recipe[2].id in [x['id'] for x in r['results']] - r = json.loads(u1_s1.get(reverse(LIST_URL) + f'?{param1}&{param2}').content) + r = json.loads(u1_s1.get(reverse(LIST_URL) + + f'?{param1}&{param2}').content) assert r['count'] == operator[1] assert found_recipe[2].id in [x['id'] for x in r['results']] @@ -203,7 +235,8 @@ def test_search_or_and_not(found_recipe, param_type, operator, recipes, u1_s1, s assert found_recipe[1].id not in [x['id'] for x in r['results']] assert found_recipe[2].id not in [x['id'] for x in r['results']] - r = json.loads(u1_s1.get(reverse(LIST_URL) + f'?{param1_not}&{param2_not}').content) + r = json.loads(u1_s1.get(reverse(LIST_URL) + + f'?{param1_not}&{param2_not}').content) assert r['count'] == 10 + operator[2] assert found_recipe[2].id not in [x['id'] for x in r['results']] @@ -227,13 +260,14 @@ def test_search_units(found_recipe, recipes, u1_s1, space_1): assert found_recipe[1].id in [x['id'] for x in r['results']] assert found_recipe[2].id in [x['id'] for x in r['results']] - r = json.loads(u1_s1.get(reverse(LIST_URL) + f'?{param1}&{param2}').content) + r = json.loads(u1_s1.get(reverse(LIST_URL) + + f'?{param1}&{param2}').content) assert r['count'] == 3 assert found_recipe[2].id in [x['id'] for x in r['results']] @pytest.mark.skipif(sqlite, reason="requires PostgreSQL") -@pytest.mark.parametrize("user1", itertools.product( +@pytest.mark.parametrize("user1", itertools.product( [ ('fuzzy_search', True), ('fuzzy_search', False), ('fuzzy_lookups', True), ('fuzzy_lookups', False) @@ -245,62 +279,70 @@ def test_search_units(found_recipe, recipes, u1_s1, space_1): ({'keyword': True}, 'keyword'), ({'food': True}, 'food'), ], indirect=['found_recipe']) -def test_fuzzy_lookup(found_recipe, recipes, param_type, user1, space_1): +def test_fuzzy_lookup(found_recipe, recipes, param_type, user1, space_1): with scope(space=space_1): list_url = f'api:{param_type}-list' param1 = f"query={user1[3]}" param2 = f"query={user1[4]}" - r = json.loads(user1[0].get(reverse(list_url) + f'?{param1}&limit=2').content) - assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[3].id, found_recipe[4].id]]) == user1[1] + r = json.loads(user1[0].get(reverse(list_url) + + f'?{param1}&limit=2').content) + assert len([x['id'] for x in r['results'] if x['id'] in [ + found_recipe[3].id, found_recipe[4].id]]) == user1[1] - r = json.loads(user1[0].get(reverse(list_url) + f'?{param2}&limit=10').content) - assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[3].id, found_recipe[4].id]]) == user1[2] + r = json.loads(user1[0].get(reverse(list_url) + + f'?{param2}&limit=10').content) + assert len([x['id'] for x in r['results'] if x['id'] in [ + found_recipe[3].id, found_recipe[4].id]]) == user1[2] # commenting this out for general use - it is really slow # it should be run on occasion to ensure everything still works +# @pytest.mark.skipif(sqlite and True, reason="requires PostgreSQL") +# @pytest.mark.parametrize("user1", itertools.product( +# [ +# ('fuzzy_search', True), ('fuzzy_search', False), +# ('fulltext', True), ('fulltext', False), +# ('icontains', True), ('icontains', False), +# ('istartswith', True), ('istartswith', False), +# ], +# [('unaccent', True), ('unaccent', False)] +# ), indirect=['user1']) +# @pytest.mark.parametrize("found_recipe", [ +# ({'name': True}), +# ({'description': True}), +# ({'instruction': True}), +# ({'keyword': True}), +# ({'food': True}), +# ], indirect=['found_recipe']) +# # user array contains: user client, expected count of search, expected count of mispelled search, search string, mispelled search string, user search preferences +# def test_search_string(found_recipe, recipes, user1, space_1): +# with scope(space=space_1): +# param1 = f"query={user1[3]}" +# param2 = f"query={user1[4]}" +# r = json.loads(user1[0].get(reverse(LIST_URL) + f'?{param1}').content) +# assert len([x['id'] for x in r['results'] if x['id'] in [ +# found_recipe[0].id, found_recipe[1].id]]) == user1[1] -@pytest.mark.skipif(sqlite and True, reason="requires PostgreSQL") -@pytest.mark.parametrize("user1", itertools.product( - [ - ('fuzzy_search', True), ('fuzzy_search', False), - ('fulltext', True), ('fulltext', False), - ('icontains', True), ('icontains', False), - ('istartswith', True), ('istartswith', False), - ], - [('unaccent', True), ('unaccent', False)] -), indirect=['user1']) -@pytest.mark.parametrize("found_recipe", [ - ({'name': True}), - ({'description': True}), - ({'instruction': True}), - ({'keyword': True}), - ({'food': True}), -], indirect=['found_recipe']) -def test_search_string(found_recipe, recipes, user1, space_1): - with scope(space=space_1): - param1 = f"query={user1[3]}" - param2 = f"query={user1[4]}" - - r = json.loads(user1[0].get(reverse(LIST_URL) + f'?{param1}').content) - assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[0].id, found_recipe[1].id]]) == user1[1] - - r = json.loads(user1[0].get(reverse(LIST_URL) + f'?{param2}').content) - assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[0].id, found_recipe[1].id]]) == user1[2] +# r = json.loads(user1[0].get(reverse(LIST_URL) + f'?{param2}').content) +# assert len([x['id'] for x in r['results'] if x['id'] in [ +# found_recipe[0].id, found_recipe[1].id]]) == user1[2] @pytest.mark.parametrize("found_recipe, param_type, result", [ ({'viewedon': True}, 'viewedon', (1, 1)), ({'cookedon': True}, 'cookedon', (1, 1)), - ({'createdon': True}, 'createdon', (2, 12)), # created dates are not filtered by user - ({'createdon': True}, 'updatedon', (2, 12)), # updated dates are not filtered by user + # created dates are not filtered by user + ({'createdon': True}, 'createdon', (2, 12)), + # updated dates are not filtered by user + ({'createdon': True}, 'updatedon', (2, 12)), ], indirect=['found_recipe']) def test_search_date(found_recipe, recipes, param_type, result, u1_s1, u2_s1, space_1): # force updated_at to equal created_at datetime with scope(space=space_1): for recipe in Recipe.objects.all(): - Recipe.objects.filter(id=recipe.id).update(updated_at=recipe.created_at) + Recipe.objects.filter(id=recipe.id).update( + updated_at=recipe.created_at) date = (timezone.now() - timedelta(days=15)).strftime("%Y-%m-%d") param1 = f"?{param_type}={date}" @@ -323,7 +365,6 @@ def test_search_date(found_recipe, recipes, param_type, result, u1_s1, u2_s1, sp assert found_recipe[2].id in [x['id'] for x in r['results']] -# TODO this is somehow screwed, probably the search itself, dont want to fix it for now @pytest.mark.parametrize("found_recipe, param_type", [ ({'rating': True}, 'rating'), ({'timescooked': True}, 'timescooked'), @@ -344,7 +385,8 @@ def test_search_count(found_recipe, recipes, param_type, u1_s1, u2_s1, space_1): # test search for not rated/cooked r = json.loads(u1_s1.get(reverse(LIST_URL) + param3).content) assert r['count'] == 11 - assert (found_recipe[0].id or found_recipe[1].id) not in [x['id'] for x in r['results']] + assert (found_recipe[0].id or found_recipe[1].id) not in [ + x['id'] for x in r['results']] # test matched returns for lte and gte searches r = json.loads(u2_s1.get(reverse(LIST_URL) + param1).content) diff --git a/vue/src/components/IngredientComponent.vue b/vue/src/components/IngredientComponent.vue index 1998ceab..17daefab 100644 --- a/vue/src/components/IngredientComponent.vue +++ b/vue/src/components/IngredientComponent.vue @@ -50,7 +50,7 @@
- + @@ -101,7 +101,7 @@ export default { .touchable { padding-right: 2em; padding-left: 3em; - margin-right: -3em; - margin-left: -2em; + margin-right: -2em; + margin-left: -3em; }