diff --git a/cookbook/helper/shopping_helper.py b/cookbook/helper/shopping_helper.py index 154cb4da9..1c51ec6d5 100644 --- a/cookbook/helper/shopping_helper.py +++ b/cookbook/helper/shopping_helper.py @@ -5,6 +5,7 @@ from django.contrib.postgres.aggregates import ArrayAgg from django.db.models import F, OuterRef, Q, Subquery, Value from django.db.models.functions import Coalesce from django.utils import timezone +from django.utils.translation import gettext as _ from cookbook.helper.HelperFunctions import Round, str2bool from cookbook.models import (Ingredient, ShoppingListEntry, ShoppingListRecipe, @@ -52,6 +53,7 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None if not r: raise ValueError(_("You must supply a recipe or mealplan")) + created_by = created_by or getattr(ShoppingListEntry.objects.filter(list_recipe=list_recipe).first(), 'created_by', None) if not created_by: raise ValueError(_("You must supply a created_by")) diff --git a/cookbook/signals.py b/cookbook/signals.py index c1e858ed0..242f9152a 100644 --- a/cookbook/signals.py +++ b/cookbook/signals.py @@ -94,17 +94,19 @@ def update_food_inheritance(sender, instance=None, created=False, **kwargs): @receiver(post_save, sender=MealPlan) def auto_add_shopping(sender, instance=None, created=False, weak=False, **kwargs): user = instance.get_owner() - if not created or not user.userpreference.mealplan_autoadd_shopping: + if not user.userpreference.mealplan_autoadd_shopping: return - # if creating a mealplan - perform shopping list activities - space = instance.space - if user.userpreference.mealplan_autoadd_shopping: + if not created and instance.shoppinglistrecipe_set.exists(): + for x in instance.shoppinglistrecipe_set.all(): + if instance.servings != x.servings: + list_recipe = list_from_recipe(list_recipe=x, servings=instance.servings, space=instance.space) + elif created: + # if creating a mealplan - perform shopping list activities kwargs = { 'mealplan': instance, - 'space': space, + 'space': instance.space, 'created_by': user, 'servings': instance.servings } - list_recipe = list_from_recipe(**kwargs) diff --git a/cookbook/tests/api/test_api_shopping_recipe.py b/cookbook/tests/api/test_api_shopping_recipe.py index 50bdaa84e..5007401bc 100644 --- a/cookbook/tests/api/test_api_shopping_recipe.py +++ b/cookbook/tests/api/test_api_shopping_recipe.py @@ -11,12 +11,27 @@ from django_scopes import scopes_disabled from pytest_factoryboy import LazyFixture, register from cookbook.models import Ingredient, ShoppingListEntry -from cookbook.tests.factories import RecipeFactory +from cookbook.tests.factories import MealPlanFactory, RecipeFactory, UserFactory SHOPPING_LIST_URL = 'api:shoppinglistentry-list' SHOPPING_RECIPE_URL = 'api:recipe-shopping' +@pytest.fixture() +def user2(request, u1_s1): + try: + params = request.param # request.param is a magic variable + except AttributeError: + params = {} + user = auth.get_user(u1_s1) + user.userpreference.mealplan_autoadd_shopping = True + user.userpreference.mealplan_autoinclude_related = True + key = list(params)[0] + setattr(user.userpreference, key, params[key]) + user.userpreference.save + return u1_s1 + + @pytest.fixture() def recipe(request, space_1, u1_s1): try: @@ -88,62 +103,97 @@ def test_shopping_recipe_method(request, arg, recipe, sle_count, u1_s1, u2_s1): ({'steps__food_recipe_count': {'step': 0, 'count': 1}}, 19), # shopping list from recipe with food recipe ({'steps__food_recipe_count': {'step': 0, 'count': 1}, 'steps__recipe_count': 1}, 29), # shopping list from recipe with StepRecipe and food recipe ], indirect=['recipe']) -def test_shopping_recipe_edit(request, recipe, sle_count, u1_s1, u2_s1): - with scopes_disabled(): # TODO take out +@pytest.mark.parametrize("use_mealplan", [(False), (True), ]) +def test_shopping_recipe_edit(request, recipe, sle_count, use_mealplan, u1_s1, u2_s1): + # tests editing shopping list via recipe or mealplan + user = auth.get_user(u1_s1) + user2 = auth.get_user(u2_s1) + user.userpreference.mealplan_autoinclude_related = True + user.userpreference.mealplan_autoadd_shopping = True + user.userpreference.shopping_share.add(user2) + user.userpreference.save() - user = auth.get_user(u1_s1) - user2 = auth.get_user(u2_s1) - user.userpreference.mealplan_autoinclude_related = True - user.userpreference.shopping_share.add(user2) - user.userpreference.save() + if use_mealplan: + mealplan = MealPlanFactory(space=recipe.space, created_by=user, servings=recipe.servings, recipe=recipe) + else: + u1_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id})) + r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) + assert [x['created_by']['id'] for x in r].count(user.id) == sle_count + all_ing = [x['ingredient'] for x in r] + keep_ing = all_ing[1:-1] # remove first and last element + del keep_ing[int(len(keep_ing)/2)] # remove a middle element + list_recipe = r[0]['list_recipe'] + amount_sum = sum([x['amount'] for x in r]) - r = u1_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id})) - r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) - assert [x['created_by']['id'] for x in r].count(user.id) == sle_count - all_ing = [x['ingredient'] for x in r] - keep_ing = [x['ingredient'] for x in r[2:]] # TODO change this to remove 1 from each third - list_recipe = r[0]['list_recipe'] - amount_sum = sum([x['amount'] for x in r]) - - # test modifying shopping list as different user - # test increasing servings size of recipe shopping list + # test modifying shopping list as different user + # test increasing servings size of recipe shopping list + if use_mealplan: + mealplan.servings = 2*recipe.servings + mealplan.save() + else: u2_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}), {'list_recipe': list_recipe, 'servings': 2*recipe.servings}, content_type='application/json' ) - r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) - assert sum([x['amount'] for x in r]) == amount_sum * 2 - assert len(r) == sle_count - assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count + r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) + assert sum([x['amount'] for x in r]) == amount_sum * 2 + assert len(r) == sle_count + assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count - # testing decreasing servings size of recipe shopping list + # testing decreasing servings size of recipe shopping list + if use_mealplan: + mealplan.servings = .5 * recipe.servings + mealplan.save() + else: u1_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}), {'list_recipe': list_recipe, 'servings': .5 * recipe.servings}, content_type='application/json' ) - r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) - assert sum([x['amount'] for x in r]) == amount_sum * .5 - assert len(r) == sle_count - assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count + r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) + assert sum([x['amount'] for x in r]) == amount_sum * .5 + assert len(r) == sle_count + assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count - # test removing 2 items from shopping list - u2_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}), - {'list_recipe': list_recipe, 'ingredients': keep_ing}, - content_type='application/json' - ) - r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) - assert len(r) == sle_count - 2 - assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count - 2 + # test removing 2 items from shopping list + u2_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}), + {'list_recipe': list_recipe, 'ingredients': keep_ing}, + content_type='application/json' + ) + r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) + assert len(r) == sle_count - 3 + assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count - 3 - # add all ingredients to existing shopping list - don't change serving size - u2_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}), - {'list_recipe': list_recipe, 'ingredients': all_ing}, - content_type='application/json' - ) - r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) - assert sum([x['amount'] for x in r]) == amount_sum * .5 - assert len(r) == sle_count - assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count + # add all ingredients to existing shopping list - don't change serving size + u2_s1.put(reverse(SHOPPING_RECIPE_URL, args={recipe.id}), + {'list_recipe': list_recipe, 'ingredients': all_ing}, + content_type='application/json' + ) + r = json.loads(u1_s1.get(reverse(SHOPPING_LIST_URL)).content) + assert sum([x['amount'] for x in r]) == amount_sum * .5 + assert len(r) == sle_count + assert len(json.loads(u2_s1.get(reverse(SHOPPING_LIST_URL)).content)) == sle_count + + +# auto add shopping, no +# auto include related, no +# auto ignore onhand +# ignore shopping food +@pytest.mark.parametrize("user2, sle_count", [ + ({'mealplan_autoadd_shopping': False}, 0), + ({'mealplan_autoinclude_related': False}, 9), + ({'mealplan_autoexclude_onhand': True}, 27), # shopping list from recipe with StepRecipe +], indirect=['user2']) +@pytest.mark.parametrize("use_mealplan", [(False), (True), ]) +@pytest.mark.parametrize("recipe", [({'steps__recipe_count': 1})], indirect=['recipe']) +def test_shopping_recipe_userpreference(request, recipe, sle_count, use_mealplan, user2): + with scopes_disabled(): + user = auth.get_user(user2) + # setup recipe with 10 ingredients, 1 step recipe with 10 ingredients, 1 food onhand, 1 food ignore shopping + recipe.step.all() + + +def test_shopping_recipe_mixed_authors(request, user2): + assert 1 == 1 # TODO test creating shopping list from recipe that includes recipes from multiple users diff --git a/cookbook/tests/factories/__init__.py b/cookbook/tests/factories/__init__.py index f64f73396..87a4f1e87 100644 --- a/cookbook/tests/factories/__init__.py +++ b/cookbook/tests/factories/__init__.py @@ -195,7 +195,7 @@ class MealPlanFactory(factory.django.DjangoModelFactory): space = factory.SubFactory(SpaceFactory) class Params: - has_recipe = False + has_recipe = True class Meta: model = 'cookbook.MealPlan'