food inherit tests
This commit is contained in:
parent
6c0e979909
commit
b92c027919
@ -824,7 +824,7 @@ class ShoppingListRecipe(ExportModelOperationsMixin('shopping_list_recipe'), mod
|
|||||||
|
|
||||||
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
|
class ShoppingListEntry(ExportModelOperationsMixin('shopping_list_entry'), models.Model, PermissionModelMixin):
|
||||||
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
|
list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
|
||||||
food = models.ForeignKey(Food, on_delete=models.CASCADE)
|
food = models.ForeignKey(Food, on_delete=models.CASCADE, related_name='shopping_entries')
|
||||||
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
|
unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True)
|
||||||
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, null=True, blank=True)
|
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, null=True, blank=True)
|
||||||
amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
||||||
|
@ -75,11 +75,20 @@ def update_food_inheritance(sender, instance=None, created=False, **kwargs):
|
|||||||
finally:
|
finally:
|
||||||
del instance.skip_signal
|
del instance.skip_signal
|
||||||
|
|
||||||
|
# TODO figure out how to generalize this
|
||||||
# apply changes to direct children - depend on save signals for those objects to cascade inheritance down
|
# apply changes to direct children - depend on save signals for those objects to cascade inheritance down
|
||||||
instance.get_children().filter(inherit=True).exclude(ignore_inherit__field='ignore_shopping').update(ignore_shopping=instance.ignore_shopping)
|
_save = []
|
||||||
|
for child in instance.get_children().filter(inherit=True).exclude(ignore_inherit__field='ignore_shopping'):
|
||||||
|
child.ignore_shopping = instance.ignore_shopping
|
||||||
|
_save.append(child)
|
||||||
# don't cascade empty supermarket category
|
# don't cascade empty supermarket category
|
||||||
if instance.supermarket_category:
|
if instance.supermarket_category:
|
||||||
instance.get_children().filter(inherit=True).exclude(ignore_inherit__field='supermarket_category').update(supermarket_category=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'):
|
||||||
|
child.supermarket_category = instance.supermarket_category
|
||||||
|
_save.append(child)
|
||||||
|
for child in set(_save):
|
||||||
|
child.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=MealPlan)
|
@receiver(post_save, sender=MealPlan)
|
||||||
|
@ -6,9 +6,10 @@ from django.urls import reverse
|
|||||||
from django_scopes import scope, scopes_disabled
|
from django_scopes import scope, scopes_disabled
|
||||||
from pytest_factoryboy import LazyFixture, register
|
from pytest_factoryboy import LazyFixture, register
|
||||||
|
|
||||||
from cookbook.models import Food, Ingredient, ShoppingList, ShoppingListEntry
|
from cookbook.models import Food, FoodInheritField, Ingredient, ShoppingList, ShoppingListEntry
|
||||||
from cookbook.tests.conftest import get_random_json_recipe
|
from cookbook.tests.conftest import get_random_json_recipe
|
||||||
from cookbook.tests.factories import FoodFactory, IngredientFactory, ShoppingListEntryFactory
|
from cookbook.tests.factories import (FoodFactory, IngredientFactory, ShoppingListEntryFactory,
|
||||||
|
SupermarketCategoryFactory)
|
||||||
|
|
||||||
# ------------------ IMPORTANT -------------------
|
# ------------------ IMPORTANT -------------------
|
||||||
#
|
#
|
||||||
@ -32,18 +33,38 @@ else:
|
|||||||
register(FoodFactory, 'obj_1', space=LazyFixture('space_1'))
|
register(FoodFactory, 'obj_1', space=LazyFixture('space_1'))
|
||||||
register(FoodFactory, 'obj_2', space=LazyFixture('space_1'))
|
register(FoodFactory, 'obj_2', space=LazyFixture('space_1'))
|
||||||
register(FoodFactory, 'obj_3', space=LazyFixture('space_2'))
|
register(FoodFactory, 'obj_3', space=LazyFixture('space_2'))
|
||||||
|
register(SupermarketCategoryFactory, 'cat_1', space=LazyFixture('space_1'))
|
||||||
|
|
||||||
|
|
||||||
|
# @pytest.fixture
|
||||||
|
# def true():
|
||||||
|
# return True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def false():
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def non_exist():
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def obj_tree_1(space_1):
|
def obj_tree_1(request, space_1):
|
||||||
|
try:
|
||||||
|
params = request.param
|
||||||
|
except AttributeError:
|
||||||
|
params = {}
|
||||||
objs = []
|
objs = []
|
||||||
objs.extend(FoodFactory.create_batch(3, space=space_1))
|
objs.extend(FoodFactory.create_batch(3, space=space_1, **params)) # request.param is a magic variable
|
||||||
objs[0].move(objs[1], node_location)
|
objs[0].move(objs[1], node_location)
|
||||||
objs[1].move(objs[2], 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
|
return Food.objects.get(id=objs[1].id) # whenever you move/merge a tree it's safest to re-get the object
|
||||||
|
|
||||||
|
|
||||||
@ pytest.mark.parametrize("arg", [
|
@pytest.mark.parametrize("arg", [
|
||||||
['a_u', 403],
|
['a_u', 403],
|
||||||
['g1_s1', 403],
|
['g1_s1', 403],
|
||||||
['u1_s1', 200],
|
['u1_s1', 200],
|
||||||
@ -87,11 +108,11 @@ def test_list_filter(obj_1, obj_2, u1_s1):
|
|||||||
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?query=chicken').content)
|
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?query=chicken').content)
|
||||||
assert response['count'] == 0
|
assert response['count'] == 0
|
||||||
|
|
||||||
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?query={obj_1.name[4:]}').content)
|
response = json.loads(u1_s1.get(f'{reverse(LIST_URL)}?query={obj_1.name[:-4]}').content)
|
||||||
assert response['count'] == 1
|
assert response['count'] == 1
|
||||||
|
|
||||||
|
|
||||||
@ pytest.mark.parametrize("arg", [
|
@pytest.mark.parametrize("arg", [
|
||||||
['a_u', 403],
|
['a_u', 403],
|
||||||
['g1_s1', 403],
|
['g1_s1', 403],
|
||||||
['u1_s1', 200],
|
['u1_s1', 200],
|
||||||
@ -116,7 +137,7 @@ def test_update(arg, request, obj_1):
|
|||||||
assert response['name'] == 'new'
|
assert response['name'] == 'new'
|
||||||
|
|
||||||
|
|
||||||
@ pytest.mark.parametrize("arg", [
|
@pytest.mark.parametrize("arg", [
|
||||||
['a_u', 403],
|
['a_u', 403],
|
||||||
['g1_s1', 403],
|
['g1_s1', 403],
|
||||||
['u1_s1', 201],
|
['u1_s1', 201],
|
||||||
@ -447,11 +468,63 @@ def test_tree_filter(obj_tree_1, obj_2, obj_3, u1_s1):
|
|||||||
assert response['count'] == 4
|
assert response['count'] == 4
|
||||||
|
|
||||||
|
|
||||||
def test_inherit(obj_tree_1, u1_s1):
|
# This is more about the model than the API - should this be moved to a different test?
|
||||||
pass
|
@pytest.mark.parametrize("obj_tree_1, field, inherit, new_val", [
|
||||||
# TODO test inherit creating, moving for each field type
|
({'add_categories': True, 'inherit': True}, 'supermarket_category', True, 'cat_1'),
|
||||||
# TODO test ignore inherit for each field type
|
({'add_categories': True, 'inherit': False}, 'supermarket_category', False, 'cat_1'),
|
||||||
# TODO test with grand-children
|
({'ignore_shopping': True, 'inherit': True}, 'ignore_shopping', True, 'false'),
|
||||||
# - flow from parent through child and grand-child
|
({'ignore_shopping': True, 'inherit': False}, 'ignore_shopping', False, 'false'),
|
||||||
# - flow from parent stop when child is ignore inherit
|
], 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):
|
||||||
|
parent = obj_tree_1.get_parent()
|
||||||
|
child = obj_tree_1.get_descendants()[0]
|
||||||
|
|
||||||
|
new_val = request.getfixturevalue(new_val)
|
||||||
|
# if this test passes it demonstrates that inheritance works
|
||||||
|
# when moving to a parent as each food is created with a different category
|
||||||
|
assert (getattr(parent, field) == getattr(obj_tree_1, field)) in [inherit, True]
|
||||||
|
assert (getattr(obj_tree_1, field) == getattr(child, field)) in [inherit, True]
|
||||||
|
# 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)
|
||||||
|
child = Food.objects.get(id=child.id)
|
||||||
|
# when changing parent value the obj value should be same if inherited
|
||||||
|
assert (getattr(obj_tree_1, field) == new_val) == inherit
|
||||||
|
assert (getattr(child, field) == new_val) == inherit
|
||||||
|
|
||||||
|
|
||||||
|
# This is more about the model than the API - should this be moved to a different test?
|
||||||
|
@pytest.mark.parametrize("obj_tree_1, field, inherit, new_val", [
|
||||||
|
({'add_categories': True, 'inherit': True, }, 'supermarket_category', True, 'cat_1'),
|
||||||
|
({'ignore_shopping': True, 'inherit': True, }, 'ignore_shopping', True, 'false'),
|
||||||
|
], indirect=['obj_tree_1']) # indirect=True populates magic variable request.param of obj_tree_1 with the parameter
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
# TODO test reset_inheritance
|
# TODO test reset_inheritance
|
||||||
|
@ -1,10 +1,27 @@
|
|||||||
import factory
|
import factory
|
||||||
|
import pytest
|
||||||
|
from django.contrib import auth
|
||||||
|
from django.contrib.auth.models import User
|
||||||
from django_scopes import scopes_disabled
|
from django_scopes import scopes_disabled
|
||||||
from faker import Factory as FakerFactory
|
from faker import Factory as FakerFactory
|
||||||
|
|
||||||
faker = FakerFactory.create()
|
faker = FakerFactory.create()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def enable_db_access_for_all_tests(db):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.hookimpl(hookwrapper=True)
|
||||||
|
def pytest_fixture_setup(fixturedef, request):
|
||||||
|
if inspect.isgeneratorfunction(fixturedef.func):
|
||||||
|
yield
|
||||||
|
else:
|
||||||
|
with scopes_disabled():
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
class SpaceFactory(factory.django.DjangoModelFactory):
|
class SpaceFactory(factory.django.DjangoModelFactory):
|
||||||
"""Space factory."""
|
"""Space factory."""
|
||||||
name = factory.LazyAttribute(lambda x: faker.word())
|
name = factory.LazyAttribute(lambda x: faker.word())
|
||||||
@ -18,11 +35,92 @@ class SpaceFactory(factory.django.DjangoModelFactory):
|
|||||||
model = 'cookbook.Space'
|
model = 'cookbook.Space'
|
||||||
|
|
||||||
|
|
||||||
class FoodFactory(factory.django.DjangoModelFactory):
|
class UserFactory(factory.django.DjangoModelFactory):
|
||||||
"""Food factory."""
|
"""User factory."""
|
||||||
name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=3))
|
username = factory.LazyAttribute(lambda x: faker.word())
|
||||||
|
first_name = factory.LazyAttribute(lambda x: faker.first_name())
|
||||||
|
last_name = factory.LazyAttribute(lambda x: faker.last_name())
|
||||||
|
email = factory.LazyAttribute(lambda x: faker.email())
|
||||||
|
space = factory.SubFactory(SpaceFactory)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
|
||||||
|
|
||||||
|
class SupermarketCategoryFactory(factory.django.DjangoModelFactory):
|
||||||
|
"""SupermarketCategory factory."""
|
||||||
|
name = factory.LazyAttribute(lambda x: faker.word())
|
||||||
description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10))
|
description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10))
|
||||||
space = factory.SubFactory(SpaceFactory)
|
space = factory.SubFactory(SpaceFactory)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = 'cookbook.SupermarketCategory'
|
||||||
|
|
||||||
|
|
||||||
|
# @factory.django.mute_signals(post_save)
|
||||||
|
class FoodFactory(factory.django.DjangoModelFactory):
|
||||||
|
"""Food factory."""
|
||||||
|
name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=3))
|
||||||
|
description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10))
|
||||||
|
supermarket_category = factory.Maybe(
|
||||||
|
factory.LazyAttribute(
|
||||||
|
lambda x:
|
||||||
|
x.add_categories),
|
||||||
|
yes_declaration=factory.SubFactory(SupermarketCategoryFactory),
|
||||||
|
no_declaration=None
|
||||||
|
)
|
||||||
|
space = factory.SubFactory(SpaceFactory)
|
||||||
|
|
||||||
|
# this code will run immediately prior to creating the model object useful when you want a reverse relationship
|
||||||
|
# log = factory.RelatedFactory(
|
||||||
|
# UserLogFactory,
|
||||||
|
# factory_related_name='user',
|
||||||
|
# action=models.UserLog.ACTION_CREATE,
|
||||||
|
# )
|
||||||
|
|
||||||
|
class Params:
|
||||||
|
add_categories = False
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = 'cookbook.Food'
|
model = 'cookbook.Food'
|
||||||
|
|
||||||
|
|
||||||
|
class UnitFactory(factory.django.DjangoModelFactory):
|
||||||
|
"""Unit factory."""
|
||||||
|
name = factory.LazyAttribute(lambda x: faker.word())
|
||||||
|
description = factory.LazyAttribute(lambda x: faker.sentence(nb_words=10))
|
||||||
|
space = factory.SubFactory(SpaceFactory)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = 'cookbook.Unit'
|
||||||
|
|
||||||
|
|
||||||
|
class IngredientFactory(factory.django.DjangoModelFactory):
|
||||||
|
"""Ingredient factory."""
|
||||||
|
food = factory.SubFactory(FoodFactory)
|
||||||
|
unit = factory.SubFactory(UnitFactory)
|
||||||
|
amount = 1
|
||||||
|
note = factory.LazyAttribute(lambda x: faker.sentence(nb_words=5))
|
||||||
|
space = factory.SubFactory(SpaceFactory)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = 'cookbook.Ingredient'
|
||||||
|
|
||||||
|
|
||||||
|
class ShoppingListEntryFactory(factory.django.DjangoModelFactory):
|
||||||
|
"""ShoppingListEntry factory."""
|
||||||
|
# list_recipe = models.ForeignKey(ShoppingListRecipe, on_delete=models.CASCADE, null=True, blank=True, related_name='entries')
|
||||||
|
food = factory.SubFactory(FoodFactory)
|
||||||
|
unit = factory.SubFactory(UnitFactory)
|
||||||
|
ingredient = factory.SubFactory(IngredientFactory)
|
||||||
|
amount = 1
|
||||||
|
order = 0
|
||||||
|
checked = False
|
||||||
|
created_by = factory.SubFactory(UserFactory)
|
||||||
|
# created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
# completed_at = models.DateTimeField(null=True, blank=True)
|
||||||
|
# delay_until = models.DateTimeField(null=True, blank=True)
|
||||||
|
space = factory.SubFactory(SpaceFactory)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = 'cookbook.ShoppingListEntry'
|
||||||
|
@ -225,6 +225,7 @@ class TreeMixin(MergeMixin, FuzzyFilterMixin):
|
|||||||
root = int(root)
|
root = int(root)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.queryset = self.model.objects.none()
|
self.queryset = self.model.objects.none()
|
||||||
|
|
||||||
if root == 0:
|
if root == 0:
|
||||||
self.queryset = self.model.get_root_nodes()
|
self.queryset = self.model.get_root_nodes()
|
||||||
else:
|
else:
|
||||||
|
Loading…
Reference in New Issue
Block a user