diff --git a/cookbook/helper/recipe_search.py b/cookbook/helper/recipe_search.py index fa5feca5..ad37af6d 100644 --- a/cookbook/helper/recipe_search.py +++ b/cookbook/helper/recipe_search.py @@ -62,7 +62,7 @@ class RecipeSearch(): # TODO add created by # TODO image exists self._sort_order = self._params.get('sort_order', None) - self._internal = str2bool(self._params.get('internal', False)) + self._internal = str2bool(self._params.get('internal', None)) self._random = str2bool(self._params.get('random', False)) self._new = str2bool(self._params.get('new', False)) self._last_viewed = int(self._params.get('last_viewed', 0)) @@ -112,7 +112,7 @@ class RecipeSearch(): self.food_filters(**self._foods) self.book_filters(**self._books) self.rating_filter(rating=self._rating) - self.internal_filter() + self.internal_filter(internal=self._internal) self.step_filters(steps=self._steps) self.unit_filters(units=self._units) self._makenow_filter() @@ -335,8 +335,10 @@ class RecipeSearch(): else: self._queryset = self._queryset.filter(rating__gte=int(rating)) - def internal_filter(self): - self._queryset = self._queryset.filter(internal=True) + def internal_filter(self, internal=None): + if not internal: + return + self._queryset = self._queryset.filter(internal=internal) def book_filters(self, **kwargs): if all([kwargs[x] is None for x in kwargs]): diff --git a/cookbook/migrations/0172_food_child_inherit_fields.py b/cookbook/migrations/0172_food_child_inherit_fields.py new file mode 100644 index 00000000..70098b2b --- /dev/null +++ b/cookbook/migrations/0172_food_child_inherit_fields.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.11 on 2022-02-04 17:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('cookbook', '0171_auto_20220202_1340'), + ] + + operations = [ + migrations.AddField( + model_name='food', + name='child_inherit_fields', + field=models.ManyToManyField(blank=True, related_name='child_inherit', to='cookbook.FoodInheritField'), + ), + ] diff --git a/cookbook/tests/factories/__init__.py b/cookbook/tests/factories/__init__.py index 070b2b70..4a87081f 100644 --- a/cookbook/tests/factories/__init__.py +++ b/cookbook/tests/factories/__init__.py @@ -111,6 +111,15 @@ class FoodFactory(factory.django.DjangoModelFactory): ) space = factory.SubFactory(SpaceFactory) + @factory.post_generation + def users_onhand(self, create, extracted, **kwargs): + if not create: + return + + if extracted: + for user in extracted: + self.onhand_users.add(user) + class Params: has_category = False has_recipe = False diff --git a/cookbook/tests/other/test_makenow_filter.py b/cookbook/tests/other/test_makenow_filter.py new file mode 100644 index 00000000..e19ed0fa --- /dev/null +++ b/cookbook/tests/other/test_makenow_filter.py @@ -0,0 +1,135 @@ +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 +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 + + +@pytest.fixture +def recipes(space_1): + return RecipeFactory.create_batch(10, space=space_1) + + +@pytest.fixture +def makenow_recipe(request, space_1): + 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): + food.onhand_users.add(onhand_user) + return recipe + + +@pytest.fixture +def user1(u1_s1, u2_s1, space_1): + user1 = auth.get_user(u1_s1) + user2 = auth.get_user(u2_s1) + user1.userpreference.shopping_share.add(user2) + user2.userpreference.shopping_share.add(user1) + return user1 + + +@pytest.mark.parametrize("makenow_recipe", [ + ({'onhand_users': 'u1_s1'}), ({'onhand_users': 'u2_s1'}), +], indirect=['makenow_recipe']) +def test_makenow_onhand(recipes, makenow_recipe, user1, space_1): + request = type('', (object,), {'space': space_1, 'user': user1})() + search = RecipeSearch(request, makenow='true') + with scope(space=space_1): + search = search.get_queryset(Recipe.objects.all()) + assert search.count() == 1 + assert search.first().id == makenow_recipe.id + + +@pytest.mark.parametrize("makenow_recipe", [ + ({'onhand_users': 'u1_s1'}), ({'onhand_users': 'u2_s1'}), +], indirect=['makenow_recipe']) +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.onhand_users.clear() + assert search.get_queryset(Recipe.objects.all()) == 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 + search = search.get_queryset(Recipe.objects.all()) + assert search.count() == 1 + assert search.first().id == makenow_recipe.id + + +@pytest.mark.parametrize("makenow_recipe", [ + ({'onhand_users': 'u1_s1'}), ({'onhand_users': 'u2_s1'}), +], indirect=['makenow_recipe']) +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() + 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 + + search = search.get_queryset(Recipe.objects.all()) + assert search.count() == 1 + assert search.first().id == makenow_recipe.id + + +@pytest.mark.parametrize("makenow_recipe", [ + ({'onhand_users': 'u1_s1'}), ({'onhand_users': 'u2_s1'}), +], indirect=['makenow_recipe']) +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() + 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 + search = search.get_queryset(Recipe.objects.all()) + assert search.count() == 1 + assert search.first().id == makenow_recipe.id + + +@pytest.mark.parametrize("makenow_recipe", [ + ({'onhand_users': 'u1_s1'}), ({'onhand_users': 'u2_s1'}), +], indirect=['makenow_recipe']) +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() + 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 + 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 new file mode 100644 index 00000000..7504f6db --- /dev/null +++ b/cookbook/tests/other/test_recipe_full_text_search.py @@ -0,0 +1,27 @@ +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) + +# 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 + +# TODO fuzzy lookup on units, keywords, food when not configured in main search settings + +# TODO test combining any/all of the above +# TODO search rating as user or when another user rated +# TODO search last cooked +# TODO changing lsat_viewed ## to return on search +# TODO test sort_by +# TODO test sort_by new X number of recipes are new within last Y days +# TODO test loading custom filter +# TODO test loading custom filter with overrided params +# TODO makenow with above filters +# TODO test search for number of times cooked (self vs others)