merge ignore shopping with onhand

This commit is contained in:
smilerz
2021-12-29 16:32:19 -06:00
parent 2787b64a96
commit 3fafd43e58
22 changed files with 191 additions and 228 deletions

View File

@@ -82,7 +82,7 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None
ingredients = Ingredient.objects.filter(step__recipe=r, space=space)
if exclude_onhand := created_by.userpreference.mealplan_autoexclude_onhand:
ingredients = ingredients.exclude(food__on_hand=True)
ingredients = ingredients.exclude(food__food_onhand=True)
if related := created_by.userpreference.mealplan_autoinclude_related:
# TODO: add levels of related recipes (related recipes of related recipes) to use when auto-adding mealplans
@@ -93,7 +93,7 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None
# TODO once/if Steps can have a serving size this needs to be refactored
if exclude_onhand:
# if steps are used more than once in a recipe or subrecipe - I don' think this results in the desired behavior
related_step_ing += Ingredient.objects.filter(step__recipe=x, food__on_hand=False, space=space).values_list('id', flat=True)
related_step_ing += Ingredient.objects.filter(step__recipe=x, food__food_onhand=False, space=space).values_list('id', flat=True)
else:
related_step_ing += Ingredient.objects.filter(step__recipe=x, space=space).values_list('id', flat=True)
@@ -101,10 +101,10 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None
if ingredients.filter(food__recipe=x).exists():
for ing in ingredients.filter(food__recipe=x):
if exclude_onhand:
x_ing = Ingredient.objects.filter(step__recipe=x, food__on_hand=False, space=space)
x_ing = Ingredient.objects.filter(step__recipe=x, food__food_onhand=False, space=space)
else:
x_ing = Ingredient.objects.filter(step__recipe=x, space=space)
for i in [x for x in x_ing if not x.food.ignore_shopping]:
for i in [x for x in x_ing]:
ShoppingListEntry.objects.create(
list_recipe=list_recipe,
food=i.food,
@@ -139,7 +139,7 @@ def list_from_recipe(list_recipe=None, recipe=None, mealplan=None, servings=None
sle.save()
# add any missing Entrys
for i in [x for x in add_ingredients if x.food and not x.food.ignore_shopping]:
for i in [x for x in add_ingredients if x.food]:
ShoppingListEntry.objects.create(
list_recipe=list_recipe,

View File

@@ -28,11 +28,11 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AddField(
model_name='food',
name='on_hand',
field=models.BooleanField(default=False),
),
# migrations.AddField(
# model_name='food',
# name='on_hand',
# field=models.BooleanField(default=False),
# ),
migrations.AddField(
model_name='shoppinglistentry',
name='completed_at',

View File

@@ -21,7 +21,7 @@ def delete_orphaned_sle(apps, schema_editor):
def create_inheritfields(apps, schema_editor):
FoodInheritField.objects.create(name='Supermarket Category', field='supermarket_category')
FoodInheritField.objects.create(name='Ignore Shopping', field='ignore_shopping')
FoodInheritField.objects.create(name='On Hand', field='food_onhand')
FoodInheritField.objects.create(name='Diet', field='diet')
FoodInheritField.objects.create(name='Substitute', field='substitute')
FoodInheritField.objects.create(name='Substitute Children', field='substitute_children')

View File

@@ -0,0 +1,35 @@
# Generated by Django 3.2.10 on 2021-12-29 20:11
from django.db import migrations
from cookbook.models import FoodInheritField
# TODO this can be deleted
# def temp_migration(apps, schema_editor):
# x = FoodInheritField.objects.get(name='Ignore Shopping', field='ignore_shopping')
# x.name = 'On Hand'
# x.field = 'food_onhand'
# x.save
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0162_userpreference_csv_delim'),
]
operations = [
# TODO this can be deleted
# migrations.RunPython(temp_migration),
# TODO this stays
migrations.RenameField(
model_name='food',
old_name='ignore_shopping',
new_name='food_onhand',
),
# TODO this can be deleted
# migrations.RemoveField(
# model_name='food',
# name='on_hand',
# ),
]

View File

@@ -490,9 +490,9 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
recipe = models.ForeignKey('Recipe', null=True, blank=True, on_delete=models.SET_NULL)
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL)
ignore_shopping = models.BooleanField(default=False) # inherited field
food_onhand = models.BooleanField(default=False) # inherited field
description = models.TextField(default='', blank=True)
on_hand = models.BooleanField(default=False)
# on_hand = models.BooleanField(default=False)
inherit = models.BooleanField(default=False)
ignore_inherit = models.ManyToManyField(FoodInheritField, blank=True) # inherited field: is this name better as inherit instead of ignore inherit? which is more intuitive?
@@ -528,10 +528,10 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
])
inherit = inherit.values_list('field', flat=True)
if 'ignore_shopping' in inherit:
if 'food_onhand' in inherit:
# get food at root that have children that need updated
Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, space=space, ignore_shopping=True)).update(ignore_shopping=True)
Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, space=space, ignore_shopping=False)).update(ignore_shopping=False)
Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, space=space, food_onhand=True)).update(food_onhand=True)
Food.include_descendants(queryset=Food.objects.filter(depth=1, numchild__gt=0, space=space, food_onhand=False)).update(food_onhand=False)
if 'supermarket_category' in inherit:
# when supermarket_category is null or blank assuming it is not set and not intended to be blank for all descedants
# find top node that has category set

View File

@@ -402,8 +402,8 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
class Meta:
model = Food
fields = (
'id', 'name', 'description', 'shopping', 'recipe', 'ignore_shopping', 'supermarket_category',
'image', 'parent', 'numchild', 'numrecipe', 'on_hand', 'inherit', 'ignore_inherit', 'full_name'
'id', 'name', 'description', 'shopping', 'recipe', 'food_onhand', 'supermarket_category',
'image', 'parent', 'numchild', 'numrecipe', 'inherit', 'ignore_inherit', 'full_name'
)
read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe')
@@ -867,7 +867,7 @@ class FoodExportSerializer(FoodSerializer):
class Meta:
model = Food
fields = ('name', 'ignore_shopping', 'supermarket_category', 'on_hand')
fields = ('name', 'food_onhand', 'supermarket_category',)
class IngredientExportSerializer(WritableNestedModelSerializer):

View File

@@ -75,8 +75,8 @@ def update_food_inheritance(sender, instance=None, created=False, **kwargs):
# apply changes from parent to instance for each inheritted field
if instance.inherit and instance.parent and inherit.count() > 0:
parent = instance.get_parent()
if 'ignore_shopping' in inherit:
instance.ignore_shopping = parent.ignore_shopping
if 'food_onhand' in inherit:
instance.food_onhand = parent.food_onhand
# if supermarket_category is not set, do not cascade - if this becomes non-intuitive can change
if 'supermarket_category' in inherit and parent.supermarket_category:
instance.supermarket_category = parent.supermarket_category
@@ -89,8 +89,8 @@ def update_food_inheritance(sender, instance=None, created=False, **kwargs):
# TODO figure out how to generalize this
# apply changes to direct children - depend on save signals for those objects to cascade inheritance down
_save = []
for child in instance.get_children().filter(inherit=True).exclude(ignore_inherit__field='ignore_shopping'):
child.ignore_shopping = instance.ignore_shopping
for child in instance.get_children().filter(inherit=True).exclude(ignore_inherit__field='food_onhand'):
child.food_onhand = instance.food_onhand
_save.append(child)
# don't cascade empty supermarket category
if instance.supermarket_category:

View File

@@ -834,7 +834,7 @@
this.$http.get('{% url 'api:recipe-detail' 123456 %}'.replace('123456', recipe.id)).then((response) => {
for (let s of response.data.steps) {
for (let i of s.ingredients) {
if (!i.is_header && i.food !== null && i.food.ignore_shopping === false) {
if (!i.is_header && i.food !== null && i.food.food_onhand === false) {
this.shopping_list.entries.push({
'list_recipe': slr.id,
'food': i.food,

View File

@@ -471,8 +471,8 @@ def test_tree_filter(obj_tree_1, obj_2, obj_3, u1_s1):
@pytest.mark.parametrize("obj_tree_1, field, inherit, new_val", [
({'has_category': True, 'inherit': True}, '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': False}, 'ignore_shopping', False, 'false'),
({'food_onhand': True, 'inherit': True}, 'food_onhand', True, 'false'),
({'food_onhand': True, 'inherit': False}, 'food_onhand', False, 'false'),
], indirect=['obj_tree_1']) # indirect=True populates magic variable request.param of obj_tree_1 with the parameter
def test_inherit(request, obj_tree_1, field, inherit, new_val, u1_s1):
with scope(space=obj_tree_1.space):
@@ -498,7 +498,7 @@ def test_inherit(request, obj_tree_1, field, inherit, new_val, u1_s1):
@pytest.mark.parametrize("obj_tree_1, field, inherit, new_val", [
({'has_category': True, 'inherit': True, }, 'supermarket_category', True, 'cat_1'),
({'ignore_shopping': True, 'inherit': True, }, 'ignore_shopping', True, 'false'),
({'food_onhand': True, 'inherit': True, }, 'food_onhand', True, 'false'),
], indirect=['obj_tree_1'])
# This is more about the model than the API - should this be moved to a different test?
def test_ignoreinherit_field(request, obj_tree_1, field, inherit, new_val, u1_s1):
@@ -527,16 +527,16 @@ def test_ignoreinherit_field(request, obj_tree_1, field, inherit, new_val, u1_s1
@pytest.mark.parametrize("obj_tree_1", [
({'has_category': True, 'inherit': False, 'ignore_shopping': True}),
({'has_category': True, 'inherit': False, 'food_onhand': True}),
], indirect=['obj_tree_1'])
def test_reset_inherit(obj_tree_1, space_1):
with scope(space=space_1):
space_1.food_inherit.add(*Food.inherit_fields.values_list('id', flat=True)) # set default inherit fields
parent = obj_tree_1.get_parent()
child = obj_tree_1.get_descendants()[0]
obj_tree_1.ignore_shopping = False
assert parent.ignore_shopping == child.ignore_shopping
assert parent.ignore_shopping != obj_tree_1.ignore_shopping
obj_tree_1.food_onhand = False
assert parent.food_onhand == child.food_onhand
assert parent.food_onhand != obj_tree_1.food_onhand
assert parent.supermarket_category != child.supermarket_category
assert parent.supermarket_category != obj_tree_1.supermarket_category
@@ -545,5 +545,5 @@ def test_reset_inherit(obj_tree_1, space_1):
obj_tree_1 = Food.objects.get(id=obj_tree_1.id)
parent = obj_tree_1.get_parent()
child = obj_tree_1.get_descendants()[0]
assert parent.ignore_shopping == obj_tree_1.ignore_shopping == child.ignore_shopping
assert parent.food_onhand == obj_tree_1.food_onhand == child.food_onhand
assert parent.supermarket_category == obj_tree_1.supermarket_category == child.supermarket_category

View File

@@ -185,27 +185,24 @@ def test_shopping_recipe_edit(request, recipe, sle_count, use_mealplan, u1_s1, u
@pytest.mark.parametrize("user2, sle_count", [
({'mealplan_autoadd_shopping': False}, (0, 17)),
({'mealplan_autoinclude_related': False}, (8, 8)),
({'mealplan_autoexclude_onhand': False}, (19, 19)),
({'mealplan_autoexclude_onhand': False, 'mealplan_autoinclude_related': False}, (9, 9)),
({'mealplan_autoadd_shopping': False}, (0, 18)),
({'mealplan_autoinclude_related': False}, (9, 9)),
({'mealplan_autoexclude_onhand': False}, (20, 20)),
({'mealplan_autoexclude_onhand': False, 'mealplan_autoinclude_related': False}, (10, 10)),
], indirect=['user2'])
@pytest.mark.parametrize("use_mealplan", [(False), (True), ])
@pytest.mark.parametrize("recipe", [({'steps__recipe_count': 1})], indirect=['recipe'])
def test_shopping_recipe_userpreference(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, 2 food onhand(from recipe and step_recipe), 1 food ignore shopping
# setup recipe with 10 ingredients, 1 step recipe with 10 ingredients, 2 food onhand(from recipe and step_recipe)
ingredients = Ingredient.objects.filter(step__recipe=recipe)
food = Food.objects.get(id=ingredients[2].food.id)
food.on_hand = True
food.food_onhand = True
food.save()
food = recipe.steps.filter(type=Step.RECIPE).first().step_recipe.steps.first().ingredients.first().food
food = Food.objects.get(id=food.id)
food.on_hand = True
food.save()
food = Food.objects.get(id=ingredients[4].food.id)
food.ignore_shopping = True
food.food_onhand = True
food.save()
if use_mealplan: