mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-01 12:18:45 -05:00
change ingore_inherit to inherit_fields
This commit is contained in:
@@ -7,7 +7,7 @@ from django_scopes import scopes_disabled
|
||||
from django_scopes.forms import SafeModelChoiceField, SafeModelMultipleChoiceField
|
||||
from hcaptcha.fields import hCaptchaField
|
||||
|
||||
from .models import (Comment, InviteLink, Keyword, MealPlan, MealType, Recipe, RecipeBook, Food,
|
||||
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, MealType, Recipe, RecipeBook,
|
||||
RecipeBookEntry, SearchPreference, Space, Storage, Sync, User, UserPreference)
|
||||
|
||||
|
||||
@@ -525,7 +525,7 @@ class SpacePreferenceForm(forms.ModelForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs) # populates the post
|
||||
self.fields['food_inherit'].queryset = Food.inherit_fields
|
||||
self.fields['food_inherit'].queryset = Food.inheritable_fields
|
||||
|
||||
class Meta:
|
||||
model = Space
|
||||
|
||||
@@ -28,11 +28,6 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
# migrations.AddField(
|
||||
# model_name='food',
|
||||
# name='on_hand',
|
||||
# field=models.BooleanField(default=False),
|
||||
# ),
|
||||
migrations.AddField(
|
||||
model_name='shoppinglistentry',
|
||||
name='completed_at',
|
||||
@@ -105,11 +100,6 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
bases=(models.Model, PermissionModelMixin),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='food',
|
||||
name='inherit',
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='userpreference',
|
||||
name='mealplan_autoinclude_related',
|
||||
@@ -117,7 +107,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='food',
|
||||
name='ignore_inherit',
|
||||
name='inherit_fields',
|
||||
field=models.ManyToManyField(blank=True, to='cookbook.FoodInheritField'),
|
||||
),
|
||||
migrations.AddField(
|
||||
@@ -145,5 +135,10 @@ class Migration(migrations.Migration):
|
||||
name='shopping_recent_days',
|
||||
field=models.PositiveIntegerField(default=7),
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='food',
|
||||
old_name='ignore_shopping',
|
||||
new_name='food_onhand',
|
||||
),
|
||||
migrations.RunPython(copy_values_to_sle),
|
||||
]
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# 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',
|
||||
# ),
|
||||
]
|
||||
@@ -482,7 +482,7 @@ class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixi
|
||||
|
||||
class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
# exclude fields not implemented yet
|
||||
inherit_fields = FoodInheritField.objects.exclude(field__in=['diet', 'substitute', 'substitute_children', 'substitute_siblings'])
|
||||
inheritable_fields = FoodInheritField.objects.exclude(field__in=['diet', 'substitute', 'substitute_children', 'substitute_siblings'])
|
||||
|
||||
# WARNING: Food inheritance relies on post_save signals, avoid using UPDATE to update Food objects unless you intend to bypass those signals
|
||||
if SORT_TREE_BY_NAME:
|
||||
@@ -492,9 +492,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
supermarket_category = models.ForeignKey(SupermarketCategory, null=True, blank=True, on_delete=models.SET_NULL)
|
||||
food_onhand = models.BooleanField(default=False) # inherited field
|
||||
description = models.TextField(default='', blank=True)
|
||||
# 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?
|
||||
inherit_fields = models.ManyToManyField(FoodInheritField, blank=True) # inherited field: is this name better as inherit instead of ignore inherit? which is more intuitive?
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space', _manager_class=TreeManager)
|
||||
@@ -512,16 +510,14 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
def reset_inheritance(space=None):
|
||||
# resets inheritted fields to the space defaults and updates all inheritted fields to root object values
|
||||
inherit = space.food_inherit.all()
|
||||
ignore_inherit = Food.inherit_fields.difference(inherit)
|
||||
|
||||
# remove all inherited fields from food
|
||||
Through = Food.objects.filter(space=space).first().inherit_fields.through
|
||||
Through.objects.all().delete()
|
||||
# food is going to inherit attributes
|
||||
if space.food_inherit.all().count() > 0:
|
||||
# using update to avoid creating a N*depth! save signals
|
||||
Food.objects.filter(space=space).update(inherit=True)
|
||||
# ManyToMany cannot be updated through an UPDATE operation
|
||||
Through = Food.objects.first().ignore_inherit.through
|
||||
Through.objects.all().delete()
|
||||
for i in ignore_inherit:
|
||||
for i in inherit:
|
||||
Through.objects.bulk_create([
|
||||
Through(food_id=x, foodinheritfield_id=i.id)
|
||||
for x in Food.objects.filter(space=space).values_list('id', flat=True)
|
||||
@@ -538,8 +534,6 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
||||
category_roots = Food.exclude_descendants(queryset=Food.objects.filter(supermarket_category__isnull=False, numchild__gt=0, space=space))
|
||||
for root in category_roots:
|
||||
root.get_descendants().update(supermarket_category=root.supermarket_category)
|
||||
else: # food is not going to inherit any attributes
|
||||
Food.objects.filter(space=space).update(inherit=False)
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
|
||||
@@ -157,15 +157,9 @@ class FoodInheritFieldSerializer(WritableNestedModelSerializer):
|
||||
|
||||
|
||||
class UserPreferenceSerializer(serializers.ModelSerializer):
|
||||
# food_inherit_default = FoodInheritFieldSerializer(source='space.food_inherit', read_only=True)
|
||||
food_ignore_default = serializers.SerializerMethodField('get_ignore_default')
|
||||
food_inherit_default = FoodInheritFieldSerializer(source='space.food_inherit', many=True, allow_null=True, required=False, read_only=True)
|
||||
plan_share = UserNameSerializer(many=True, allow_null=True, required=False, read_only=True)
|
||||
|
||||
# TODO decide: default inherit field values for foods are being handled via VUE client through user preference
|
||||
# should inherit field instead be set during the django model create?
|
||||
def get_ignore_default(self, obj):
|
||||
return FoodInheritFieldSerializer(Food.inherit_fields.difference(obj.space.food_inherit.all()), many=True).data
|
||||
|
||||
def create(self, validated_data):
|
||||
if not validated_data.get('user', None):
|
||||
raise ValidationError(_('A user is required'))
|
||||
@@ -181,7 +175,7 @@ class UserPreferenceSerializer(serializers.ModelSerializer):
|
||||
model = UserPreference
|
||||
fields = (
|
||||
'user', 'theme', 'nav_color', 'default_unit', 'default_page', 'use_kj', 'search_style', 'show_recent', 'plan_share',
|
||||
'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'food_ignore_default', 'default_delay',
|
||||
'ingredient_decimals', 'comments', 'shopping_auto_sync', 'mealplan_autoadd_shopping', 'food_inherit_default', 'default_delay',
|
||||
'mealplan_autoinclude_related', 'mealplan_autoexclude_onhand', 'shopping_share', 'shopping_recent_days', 'csv_delim', 'csv_prefix', 'filter_to_supermarket'
|
||||
)
|
||||
|
||||
@@ -376,7 +370,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
||||
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
|
||||
recipe = RecipeSimpleSerializer(allow_null=True, required=False)
|
||||
shopping = serializers.SerializerMethodField('get_shopping_status')
|
||||
ignore_inherit = FoodInheritFieldSerializer(many=True, allow_null=True, required=False)
|
||||
inherit_fields = FoodInheritFieldSerializer(many=True, allow_null=True, required=False)
|
||||
|
||||
recipe_filter = 'steps__ingredients__food'
|
||||
|
||||
@@ -403,7 +397,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
||||
model = Food
|
||||
fields = (
|
||||
'id', 'name', 'description', 'shopping', 'recipe', 'food_onhand', 'supermarket_category',
|
||||
'image', 'parent', 'numchild', 'numrecipe', 'inherit', 'ignore_inherit', 'full_name'
|
||||
'image', 'parent', 'numchild', 'numrecipe', 'inherit_fields', 'full_name'
|
||||
)
|
||||
read_only_fields = ('id', 'numchild', 'parent', 'image', 'numrecipe')
|
||||
|
||||
|
||||
@@ -66,14 +66,14 @@ def update_food_inheritance(sender, instance=None, created=False, **kwargs):
|
||||
if not instance:
|
||||
return
|
||||
|
||||
inherit = Food.inherit_fields.difference(instance.ignore_inherit.all())
|
||||
inherit = instance.inherit_fields.all()
|
||||
# nothing to apply from parent and nothing to apply to children
|
||||
if (not instance.inherit or not instance.parent or inherit.count() == 0) and instance.numchild == 0:
|
||||
if (not instance.parent or inherit.count() == 0) and instance.numchild == 0:
|
||||
return
|
||||
|
||||
inherit = inherit.values_list('field', flat=True)
|
||||
# apply changes from parent to instance for each inheritted field
|
||||
if instance.inherit and instance.parent and inherit.count() > 0:
|
||||
if instance.parent and inherit.count() > 0:
|
||||
parent = instance.get_parent()
|
||||
if 'food_onhand' in inherit:
|
||||
instance.food_onhand = parent.food_onhand
|
||||
@@ -89,13 +89,13 @@ 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='food_onhand'):
|
||||
for child in instance.get_children().filter(inherit_fields__field='food_onhand'):
|
||||
child.food_onhand = instance.food_onhand
|
||||
_save.append(child)
|
||||
# don't cascade empty supermarket category
|
||||
if instance.supermarket_category:
|
||||
# apply changes to direct children - depend on save signals for those objects to cascade inheritance down
|
||||
for child in instance.get_children().filter(inherit=True).exclude(ignore_inherit__field='supermarket_category'):
|
||||
for child in instance.get_children().filter(inherit_fields__field='supermarket_category'):
|
||||
child.supermarket_category = instance.supermarket_category
|
||||
_save.append(child)
|
||||
for child in set(_save):
|
||||
|
||||
@@ -57,7 +57,19 @@ def obj_tree_1(request, space_1):
|
||||
except AttributeError:
|
||||
params = {}
|
||||
objs = []
|
||||
inherit = params.pop('inherit', False)
|
||||
objs.extend(FoodFactory.create_batch(3, space=space_1, **params))
|
||||
|
||||
# set all foods to inherit everything
|
||||
if inherit:
|
||||
inherit = Food.inheritable_fields
|
||||
Through = Food.objects.filter(space=space_1).first().inherit_fields.through
|
||||
for i in inherit:
|
||||
Through.objects.bulk_create([
|
||||
Through(food_id=x, foodinheritfield_id=i.id)
|
||||
for x in Food.objects.filter(space=space_1).values_list('id', flat=True)
|
||||
])
|
||||
|
||||
objs[0].move(objs[1], node_location)
|
||||
objs[1].move(objs[2], node_location)
|
||||
return Food.objects.get(id=objs[1].id) # whenever you move/merge a tree it's safest to re-get the object
|
||||
@@ -496,42 +508,12 @@ def test_inherit(request, obj_tree_1, field, inherit, new_val, u1_s1):
|
||||
assert (getattr(child, field) == new_val) == inherit
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obj_tree_1, field, inherit, new_val", [
|
||||
({'has_category': True, 'inherit': True, }, 'supermarket_category', True, 'cat_1'),
|
||||
({'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):
|
||||
with scope(space=obj_tree_1.space):
|
||||
parent = obj_tree_1.get_parent()
|
||||
child = obj_tree_1.get_descendants()[0]
|
||||
obj_tree_1.ignore_inherit.add(FoodInheritField.objects.get(field=field))
|
||||
new_val = request.getfixturevalue(new_val)
|
||||
|
||||
# change parent to a new value
|
||||
setattr(parent, field, new_val)
|
||||
with scope(space=parent.space):
|
||||
parent.save() # trigger post-save signal
|
||||
# get the objects again because values are cached
|
||||
obj_tree_1 = Food.objects.get(id=obj_tree_1.id)
|
||||
# inheritance is blocked - should not get new value
|
||||
assert getattr(obj_tree_1, field) != new_val
|
||||
|
||||
setattr(obj_tree_1, field, new_val)
|
||||
with scope(space=parent.space):
|
||||
obj_tree_1.save() # trigger post-save signal
|
||||
# get the objects again because values are cached
|
||||
child = Food.objects.get(id=child.id)
|
||||
# inherit with child should still work
|
||||
assert getattr(child, field) == new_val
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obj_tree_1", [
|
||||
({'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
|
||||
space_1.food_inherit.add(*Food.inheritable_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.food_onhand = False
|
||||
|
||||
@@ -112,30 +112,29 @@ def test_preference_delete(u1_s1, u2_s1):
|
||||
|
||||
|
||||
def test_default_inherit_fields(u1_s1, u1_s2, space_1, space_2):
|
||||
food_inherit_fields = Food.inherit_fields.all()
|
||||
|
||||
r = u1_s1.get(
|
||||
reverse(DETAIL_URL, args={auth.get_user(u1_s1).id}),
|
||||
)
|
||||
food_inherit_fields = Food.inheritable_fields
|
||||
assert len([x.field for x in food_inherit_fields]) > 0
|
||||
|
||||
# by default space food will not inherit any fields, so all of them will be ignored
|
||||
assert space_1.food_inherit.all().count() == 0
|
||||
assert len([x.field for x in food_inherit_fields]) == len([x['field'] for x in json.loads(r.content)['food_ignore_default']]) > 0
|
||||
|
||||
# inherit all possible fields
|
||||
with scope(space=space_1):
|
||||
space_1.food_inherit.add(*Food.inherit_fields.values_list('id', flat=True))
|
||||
r = u1_s1.get(
|
||||
reverse(DETAIL_URL, args={auth.get_user(u1_s1).id}),
|
||||
)
|
||||
assert len([x['field'] for x in json.loads(r.content)['food_inherit_default']]) == 0
|
||||
|
||||
assert space_1.food_inherit.all().count() == Food.inherit_fields.all().count() > 0
|
||||
# now by default, food is not ignoring inheritance on any field
|
||||
assert len([x['field'] for x in json.loads(r.content)['food_ignore_default']]) == 0
|
||||
# inherit all possible fields
|
||||
with scope(space=space_1):
|
||||
space_1.food_inherit.add(*Food.inheritable_fields.values_list('id', flat=True))
|
||||
|
||||
# other spaces and users in those spaced not effected
|
||||
assert space_1.food_inherit.all().count() == Food.inheritable_fields.count() > 0
|
||||
# now by default, food is inheriting all of the possible fields
|
||||
r = u1_s1.get(
|
||||
reverse(DETAIL_URL, args={auth.get_user(u1_s1).id}),
|
||||
)
|
||||
assert len([x['field'] for x in json.loads(r.content)['food_inherit_default']]) == space_1.food_inherit.all().count()
|
||||
|
||||
# other spaces and users in those spaces not effected
|
||||
r = u1_s2.get(
|
||||
reverse(DETAIL_URL, args={auth.get_user(u1_s2).id}),
|
||||
)
|
||||
assert space_2.food_inherit.all().count() == 0
|
||||
assert len([x.field for x in food_inherit_fields]) == len([x['field'] for x in json.loads(r.content)['food_ignore_default']]) > 0
|
||||
assert space_2.food_inherit.all().count() == 0 == len([x['field'] for x in json.loads(r.content)['food_inherit_default']])
|
||||
|
||||
@@ -402,7 +402,8 @@ class FoodInheritFieldViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
|
||||
def get_queryset(self):
|
||||
# exclude fields not yet implemented
|
||||
return Food.inherit_fields
|
||||
self.queryset = Food.inheritable_fields
|
||||
return super().get_queryset()
|
||||
|
||||
|
||||
class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
||||
|
||||
@@ -561,7 +561,7 @@ def space(request):
|
||||
|
||||
space_form = SpacePreferenceForm(instance=request.space)
|
||||
|
||||
space_form.base_fields['food_inherit'].queryset = Food.inherit_fields
|
||||
space_form.base_fields['food_inherit'].queryset = Food.inheritable_fields
|
||||
if request.method == "POST" and 'space_form' in request.POST:
|
||||
form = SpacePreferenceForm(request.POST, prefix='space')
|
||||
if form.is_valid():
|
||||
|
||||
Reference in New Issue
Block a user