diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index c0a4a108..110ac2c4 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -29,6 +29,7 @@ class RecipeSearch(): filter = CustomFilter.objects.filter(id=f, space=self._request.space).filter(Q(created_by=self._request.user) | Q(shared=self._request.user)).first() if filter: self._params = {**json.loads(filter.search)} + self._original_params = {**(params or {})} else: self._params = {**(params or {})} else: @@ -69,7 +70,14 @@ class RecipeSearch(): self._include_children = str2bool(self._params.get('include_children', None)) self._timescooked = self._params.get('timescooked', None) self._lastcooked = self._params.get('lastcooked', None) - self._makenow = str2bool(self._params.get('makenow', None)) + # this supports hidden feature to find recipes missing X ingredients + try: + self._makenow = int(makenow := self._params.get('makenow', None)) + except (ValueError, TypeError): + if str2bool(makenow): + self._makenow = 0 + else: + self._makenow = None self._search_type = self._search_prefs.search or 'plain' if self._string: @@ -115,7 +123,7 @@ class RecipeSearch(): self.internal_filter(internal=self._internal) self.step_filters(steps=self._steps) self.unit_filters(units=self._units) - self._makenow_filter() + self._makenow_filter(missing=self._makenow) self.string_filters(string=self._string) return self._queryset.filter(space=self._request.space).distinct().order_by(*self.orderby) @@ -420,8 +428,8 @@ 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: + def _makenow_filter(self, missing=None): + if missing is None: return shopping_users = [*self._request.user.get_shopping_share(), self._request.user] @@ -434,7 +442,7 @@ class RecipeSearch(): makenow_recipes = Recipe.objects.annotate( count_food=Count('steps__ingredients__food', filter=Q(steps__ingredients__food__ignore_shopping=False, steps__ingredients__food__isnull=False), distinct=True), count_onhand=Count('pk', filter=onhand_filter) - ).annotate(missingfood=F('count_food')-F('count_onhand')).filter(missingfood__lte=0) + ).annotate(missingfood=F('count_food')-F('count_onhand')).filter(missingfood=missing) self._queryset = self._queryset.filter(id__in=makenow_recipes.values('id')) @staticmethod diff --git a/cookbook/migrations/0169_exportlog.py b/cookbook/migrations/0169_exportlog.py index 0fa6c20e..1bdd42cc 100644 --- a/cookbook/migrations/0169_exportlog.py +++ b/cookbook/migrations/0169_exportlog.py @@ -1,9 +1,10 @@ # Generated by Django 3.2.11 on 2022-02-03 15:03 -import cookbook.models +import django.db.models.deletion from django.conf import settings from django.db import migrations, models -import django.db.models.deletion + +import cookbook.models class Migration(migrations.Migration): diff --git a/cookbook/tests/factories/__init__.py b/cookbook/tests/factories/__init__.py index 4a87081f..cd885c35 100644 --- a/cookbook/tests/factories/__init__.py +++ b/cookbook/tests/factories/__init__.py @@ -129,6 +129,39 @@ class FoodFactory(factory.django.DjangoModelFactory): django_get_or_create = ('name', 'space',) +@register +class RecipeBookEntryFactory(factory.django.DjangoModelFactory): + """RecipeBookEntry factory.""" + book = None + recipe = None + + class Meta: + model = 'cookbook.RecipeBookEntry' + + +@register +class RecipeBookFactory(factory.django.DjangoModelFactory): + """RecipeBook factory.""" + name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=2, variable_nb_words=False)) + # icon = models.CharField(max_length=16, blank=True, null=True) + description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10)) + created_by = factory.SubFactory(UserFactory, space=factory.SelfAttribute('..space')) + space = factory.SubFactory(SpaceFactory) + recipe = None # used to add to RecipeBookEntry + recipe_book_entry = factory.RelatedFactory( + RecipeBookEntryFactory, + factory_related_name='book', + recipe=factory.LazyAttribute(lambda x: x.recipe), + ) + + class Params: + recipe = None + + class Meta: + model = 'cookbook.RecipeBook' + django_get_or_create = ('name', 'space',) + + @register class UnitFactory(factory.django.DjangoModelFactory): """Unit factory.""" diff --git a/cookbook/tests/other/test_makenow_filter.py b/cookbook/tests/other/test_makenow_filter.py index 9293975f..f812b0ed 100644 --- a/cookbook/tests/other/test_makenow_filter.py +++ b/cookbook/tests/other/test_makenow_filter.py @@ -1,10 +1,8 @@ -import json import pytest from django.contrib import auth from django.urls import reverse from django_scopes import scope -from pytest_factoryboy import LazyFixture, register from cookbook.helper.recipe_search import RecipeSearch from cookbook.models import Food, Recipe diff --git a/cookbook/tests/other/test_recipe_full_text_search.py b/cookbook/tests/other/test_recipe_full_text_search.py index 7504f6db..9416ea53 100644 --- a/cookbook/tests/other/test_recipe_full_text_search.py +++ b/cookbook/tests/other/test_recipe_full_text_search.py @@ -1,14 +1,10 @@ -import json - import pytest from django.contrib import auth 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.tests.factories import (FoodFactory, IngredientFactory, ShoppingListEntryFactory, - SupermarketCategoryFactory) +from cookbook.models import Food, Recipe +from cookbook.tests.factories import FoodFactory, RecipeBookEntryFactory, RecipeFactory # TODO food/keyword/book test or, and, or_not, and_not search # TODO recipe name/description/instructions/keyword/book/food test search with icontains, istarts with/ full text(?? probably when word changes based on conjugation??), trigram, unaccent @@ -25,3 +21,60 @@ from cookbook.tests.factories import (FoodFactory, IngredientFactory, ShoppingLi # TODO test loading custom filter with overrided params # TODO makenow with above filters # TODO test search for number of times cooked (self vs others) +LIST_URL = 'api:recipe-list' + + +@pytest.fixture +def accent(): + return "àèìòù" + + +@pytest.fixture +def unaccent(): + return "aeiou" + + +@pytest.fixture +def recipes(space_1): + return RecipeFactory.create_batch(10, space=space_1) + + +@pytest.fixture +def found_recipe(request, space_1, accent, unaccent): + recipe1 = RecipeFactory.create(space=space_1) + recipe2 = RecipeFactory.create(space=space_1) + recipe3 = RecipeFactory.create(space=space_1) + related = request.param.get('related', None) + # name = request.getfixturevalue(request.param.get('name', "unaccent")) + + if related == 'food': + obj1 = Food.objects.filter(ingredient__step__recipe=recipe.id).first() + obj2 = Food.objects.filter(ingredient__step__recipe=recipe.id).last() + obj1.name = unaccent + obj1.save() + obj2.name = accent + obj2.save() + elif related == 'keyword': + obj1 = recipe.keywords.first() + obj2 = recipe.keywords.last() + obj1.name = unaccent + obj1.save() + obj2.name = accent + obj2.save() + elif related == 'book': + obj1 = RecipeBookEntryFactory.create(recipe=recipe) + + return (recipe1, recipe2, recipe3, obj1, obj2) + + +@pytest.mark.parametrize("found_recipe, param_type", [ + ({'related': 'food'}, 'foods'), + ({'related': 'keyword'}, 'keywords'), + ({'related': 'book'}, 'books'), +], indirect=['found_recipe']) +@pytest.mark.parametrize('operator', ['_or', '_and', ]) +def test_search_lists(found_recipe, param_type, operator, recipes, u1_s1, space_1): + with scope(space=space_1): + assert 1 == 2 + pass + assert u1_s1.get(reverse(LIST_URL) + f'?parm={share.uuid}') diff --git a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue index d60d4690..eada2392 100644 --- a/vue/src/apps/RecipeSearchView/RecipeSearchView.vue +++ b/vue/src/apps/RecipeSearchView/RecipeSearchView.vue @@ -1012,6 +1012,10 @@ export default { params.options.query.last_viewed = this.ui.recently_viewed params._new = this.ui.sort_by_new } + if (this.search.search_filter) { + params.options.query.filter = this.search.search_filter.id + } + console.log(params) return params }, searchFiltered: function (ignore_string = false) {