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):
|
||||
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)
|
||||
ingredient = models.ForeignKey(Ingredient, on_delete=models.CASCADE, null=True, blank=True)
|
||||
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:
|
||||
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
|
||||
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
|
||||
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)
|
||||
|
@ -6,9 +6,10 @@ from django.urls import reverse
|
||||
from django_scopes import scope, scopes_disabled
|
||||
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.factories import FoodFactory, IngredientFactory, ShoppingListEntryFactory
|
||||
from cookbook.tests.factories import (FoodFactory, IngredientFactory, ShoppingListEntryFactory,
|
||||
SupermarketCategoryFactory)
|
||||
|
||||
# ------------------ IMPORTANT -------------------
|
||||
#
|
||||
@ -32,18 +33,38 @@ else:
|
||||
register(FoodFactory, 'obj_1', space=LazyFixture('space_1'))
|
||||
register(FoodFactory, 'obj_2', space=LazyFixture('space_1'))
|
||||
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()
|
||||
def obj_tree_1(space_1):
|
||||
def obj_tree_1(request, space_1):
|
||||
try:
|
||||
params = request.param
|
||||
except AttributeError:
|
||||
params = {}
|
||||
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[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
|
||||
|
||||
|
||||
@ pytest.mark.parametrize("arg", [
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
['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)
|
||||
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
|
||||
|
||||
|
||||
@ pytest.mark.parametrize("arg", [
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
['u1_s1', 200],
|
||||
@ -116,7 +137,7 @@ def test_update(arg, request, obj_1):
|
||||
assert response['name'] == 'new'
|
||||
|
||||
|
||||
@ pytest.mark.parametrize("arg", [
|
||||
@pytest.mark.parametrize("arg", [
|
||||
['a_u', 403],
|
||||
['g1_s1', 403],
|
||||
['u1_s1', 201],
|
||||
@ -447,11 +468,63 @@ def test_tree_filter(obj_tree_1, obj_2, obj_3, u1_s1):
|
||||
assert response['count'] == 4
|
||||
|
||||
|
||||
def test_inherit(obj_tree_1, u1_s1):
|
||||
pass
|
||||
# TODO test inherit creating, moving for each field type
|
||||
# TODO test ignore inherit for each field type
|
||||
# TODO test with grand-children
|
||||
# - flow from parent through child and grand-child
|
||||
# - flow from parent stop when child is ignore 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'),
|
||||
({'add_categories': 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'),
|
||||
], 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
|
||||
|
@ -1,10 +1,27 @@
|
||||
import factory
|
||||
import pytest
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import User
|
||||
from django_scopes import scopes_disabled
|
||||
from faker import Factory as FakerFactory
|
||||
|
||||
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):
|
||||
"""Space factory."""
|
||||
name = factory.LazyAttribute(lambda x: faker.word())
|
||||
@ -18,11 +35,92 @@ class SpaceFactory(factory.django.DjangoModelFactory):
|
||||
model = 'cookbook.Space'
|
||||
|
||||
|
||||
class FoodFactory(factory.django.DjangoModelFactory):
|
||||
"""Food factory."""
|
||||
name = factory.LazyAttribute(lambda x: faker.sentence(nb_words=3))
|
||||
class UserFactory(factory.django.DjangoModelFactory):
|
||||
"""User factory."""
|
||||
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))
|
||||
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:
|
||||
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)
|
||||
except ValueError:
|
||||
self.queryset = self.model.objects.none()
|
||||
|
||||
if root == 0:
|
||||
self.queryset = self.model.get_root_nodes()
|
||||
else:
|
||||
|
Loading…
Reference in New Issue
Block a user