improvements to property calculation

This commit is contained in:
vabene1111
2023-05-07 00:30:32 +02:00
parent 9a77089c6d
commit 2a5cba0178
5 changed files with 94 additions and 14 deletions

View File

@ -2,8 +2,10 @@ class CacheHelper:
space = None space = None
BASE_UNITS_CACHE_KEY = None BASE_UNITS_CACHE_KEY = None
PROPERTY_TYPE_CACHE_KEY = None
def __init__(self, space): def __init__(self, space):
self.space = space self.space = space
self.BASE_UNITS_CACHE_KEY = f'SPACE_{space.id}_BASE_UNITS' self.BASE_UNITS_CACHE_KEY = f'SPACE_{space.id}_BASE_UNITS'
self.PROPERTY_TYPE_CACHE_KEY = f'SPACE_{space.id}_PROPERTY_TYPES'

View File

@ -1,3 +1,6 @@
from django.core.cache import caches
from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.unit_conversion_helper import UnitConversionHelper from cookbook.helper.unit_conversion_helper import UnitConversionHelper
from cookbook.models import PropertyType, Unit, Food, FoodProperty, Recipe, Step from cookbook.models import PropertyType, Unit, Food, FoodProperty, Recipe, Step
@ -20,15 +23,18 @@ class FoodPropertyHelper:
""" """
ingredients = [] ingredients = []
computed_properties = {} computed_properties = {}
property_types = PropertyType.objects.filter(space=self.space).all()
for s in recipe.steps.all(): for s in recipe.steps.all():
ingredients += s.ingredients.all() ingredients += s.ingredients.all()
for fpt in property_types: # TODO is this safe or should I require the request context? property_types = caches['default'].get(CacheHelper(self.space).PROPERTY_TYPE_CACHE_KEY, None)
computed_properties[fpt.id] = {'id': fpt.id, 'name': fpt.name, 'icon': fpt.icon, 'description': fpt.description, 'unit': fpt.unit, 'food_values': {}, 'total_value': 0, 'missing_value': False}
# TODO unit conversion support if not property_types:
property_types = PropertyType.objects.filter(space=self.space).all()
caches['default'].set(CacheHelper(self.space).PROPERTY_TYPE_CACHE_KEY, property_types, 60 * 60) # cache is cleared on property type save signal so long duration is fine
for fpt in property_types:
computed_properties[fpt.id] = {'id': fpt.id, 'name': fpt.name, 'icon': fpt.icon, 'description': fpt.description, 'unit': fpt.unit, 'food_values': {}, 'total_value': 0, 'missing_value': False}
uch = UnitConversionHelper(self.space) uch = UnitConversionHelper(self.space)

View File

@ -14,7 +14,7 @@ from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.shopping_helper import RecipeShoppingEditor from cookbook.helper.shopping_helper import RecipeShoppingEditor
from cookbook.managers import DICTIONARY from cookbook.managers import DICTIONARY
from cookbook.models import (Food, FoodInheritField, Ingredient, MealPlan, Recipe, from cookbook.models import (Food, FoodInheritField, Ingredient, MealPlan, Recipe,
ShoppingListEntry, Step, UserPreference, SearchPreference, SearchFields, Unit) ShoppingListEntry, Step, UserPreference, SearchPreference, SearchFields, Unit, PropertyType)
SQLITE = True SQLITE = True
if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2', if settings.DATABASES['default']['ENGINE'] in ['django.db.backends.postgresql_psycopg2',
@ -154,6 +154,12 @@ def auto_add_shopping(sender, instance=None, created=False, weak=False, **kwargs
@receiver(post_save, sender=Unit) @receiver(post_save, sender=Unit)
def create_search_preference(sender, instance=None, created=False, **kwargs): def clear_unit_cache(sender, instance=None, created=False, **kwargs):
if instance: if instance:
caches['default'].delete(CacheHelper(instance.space).BASE_UNITS_CACHE_KEY) caches['default'].delete(CacheHelper(instance.space).BASE_UNITS_CACHE_KEY)
@receiver(post_save, sender=PropertyType)
def clear_property_type_cache(sender, instance=None, created=False, **kwargs):
if instance:
caches['default'].delete(CacheHelper(instance.space).PROPERTY_TYPE_CACHE_KEY)

View File

@ -1,13 +1,17 @@
from django.contrib import auth from django.contrib import auth
from django.core.cache import caches
from django_scopes import scopes_disabled from django_scopes import scopes_disabled
from decimal import Decimal
from cookbook.helper.cache_helper import CacheHelper
from cookbook.helper.property_helper import FoodPropertyHelper from cookbook.helper.property_helper import FoodPropertyHelper
from cookbook.models import Unit, Food, PropertyType, FoodProperty, Recipe, Step from cookbook.models import Unit, Food, PropertyType, FoodProperty, Recipe, Step, UnitConversion
def test_food_property(space_1, u1_s1): def test_food_property(space_1, space_2, u1_s1):
with scopes_disabled(): with scopes_disabled():
unit_gram = Unit.objects.create(name='gram', base_unit='g', space=space_1) unit_gram = Unit.objects.create(name='gram', base_unit='g', space=space_1)
unit_kg = Unit.objects.create(name='kg', base_unit='kg', space=space_1)
unit_pcs = Unit.objects.create(name='pcs', base_unit='', space=space_1) unit_pcs = Unit.objects.create(name='pcs', base_unit='', space=space_1)
unit_floz1 = Unit.objects.create(name='fl. oz 1', base_unit='imperial_fluid_ounce', space=space_1) # US and UK use different volume systems (US vs imperial) unit_floz1 = Unit.objects.create(name='fl. oz 1', base_unit='imperial_fluid_ounce', space=space_1) # US and UK use different volume systems (US vs imperial)
unit_floz2 = Unit.objects.create(name='fl. oz 2', base_unit='fluid_ounce', space=space_1) unit_floz2 = Unit.objects.create(name='fl. oz 2', base_unit='fluid_ounce', space=space_1)
@ -29,6 +33,7 @@ def test_food_property(space_1, u1_s1):
food_2_property_nuts = FoodProperty.objects.create(food_amount=100, food_unit=unit_gram, food=food_2, property_amount=0, property_type=property_nuts, space=space_1) food_2_property_nuts = FoodProperty.objects.create(food_amount=100, food_unit=unit_gram, food=food_2, property_amount=0, property_type=property_nuts, space=space_1)
food_2_property_price = FoodProperty.objects.create(food_amount=100, food_unit=unit_gram, food=food_2, property_amount=2.50, property_type=property_price, space=space_1) food_2_property_price = FoodProperty.objects.create(food_amount=100, food_unit=unit_gram, food=food_2, property_amount=2.50, property_type=property_price, space=space_1)
print('\n----------- TEST PROPERTY - PROPERTY CALCULATION MULTI STEP IDENTICAL UNIT ---------------')
recipe_1 = Recipe.objects.create(name='recipe_1', waiting_time=0, working_time=0, space=space_1, created_by=auth.get_user(u1_s1)) recipe_1 = Recipe.objects.create(name='recipe_1', waiting_time=0, working_time=0, space=space_1, created_by=auth.get_user(u1_s1))
step_1 = Step.objects.create(instruction='instruction_step_1', space=space_1) step_1 = Step.objects.create(instruction='instruction_step_1', space=space_1)
@ -43,10 +48,67 @@ def test_food_property(space_1, u1_s1):
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_1) property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_1)
assert property_values[property_fat.id]['name'] == property_fat.name assert property_values[property_fat.id]['name'] == property_fat.name
assert property_values[property_fat.id]['total_value'] == 525 # TODO manually validate those numbers assert abs(property_values[property_fat.id]['total_value'] - Decimal(525)) < 0.0001
assert property_values[property_fat.id]['food_values'][food_1.id] == 275 # TODO manually validate those numbers assert abs(property_values[property_fat.id]['food_values'][food_1.id]['value'] - Decimal(275)) < 0.0001
assert property_values[property_fat.id]['food_values'][food_2.id] == 250 # TODO manually validate those numbers assert abs(property_values[property_fat.id]['food_values'][food_2.id]['value'] - Decimal(250)) < 0.0001
print(property_values)
# TODO more property tests
# TODO test space separation print('\n----------- TEST PROPERTY - PROPERTY CALCULATION NO POSSIBLE CONVERSION ---------------')
recipe_2 = Recipe.objects.create(name='recipe_2', waiting_time=0, working_time=0, space=space_1, created_by=auth.get_user(u1_s1))
step_1 = Step.objects.create(instruction='instruction_step_1', space=space_1)
step_1.ingredients.create(amount=5, unit=unit_pcs, food=food_1, space=space_1)
step_1.ingredients.create(amount=10, unit=unit_pcs, food=food_2, space=space_1)
recipe_2.steps.add(step_1)
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_values[property_fat.id]['name'] == property_fat.name
assert abs(property_values[property_fat.id]['total_value']) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_1.id]['value']) < 0.0001
print('\n----------- TEST PROPERTY - PROPERTY CALCULATION UNIT CONVERSION ---------------')
uc1 = UnitConversion.objects.create(
base_amount=100,
base_unit=unit_gram,
converted_amount=1,
converted_unit=unit_pcs,
space=space_1,
created_by=auth.get_user(u1_s1),
)
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_values[property_fat.id]['name'] == property_fat.name
assert abs(property_values[property_fat.id]['total_value'] - Decimal(500)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_1.id]['value'] - Decimal(250)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_2.id]['value'] - Decimal(250)) < 0.0001
print('\n----------- TEST PROPERTY - PROPERTY CALCULATION UNIT CONVERSION MULTIPLE ---------------')
uc1.delete()
uc1 = UnitConversion.objects.create(
base_amount=0.1,
base_unit=unit_kg,
converted_amount=1,
converted_unit=unit_pcs,
space=space_1,
created_by=auth.get_user(u1_s1),
)
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_values[property_fat.id]['name'] == property_fat.name
assert abs(property_values[property_fat.id]['total_value'] - Decimal(500)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_1.id]['value'] - Decimal(250)) < 0.0001
assert abs(property_values[property_fat.id]['food_values'][food_2.id]['value'] - Decimal(250)) < 0.0001
print('\n----------- TEST PROPERTY - SPACE SEPARATION ---------------')
property_fat.space = space_2
property_fat.save()
caches['default'].delete(CacheHelper(space_1).PROPERTY_TYPE_CACHE_KEY) # clear cache as objects won't change space in reality
property_values = FoodPropertyHelper(space_1).calculate_recipe_properties(recipe_2)
assert property_fat.id not in property_values

View File

@ -138,6 +138,10 @@ def test_unit_conversions(space_1, space_2, u1_s1):
assert len(uch.get_conversions(ingredient_food_2_pcs)) == 1 assert len(uch.get_conversions(ingredient_food_2_pcs)) == 1
print(uch.get_conversions(ingredient_food_2_pcs)) print(uch.get_conversions(ingredient_food_2_pcs))
print('\n----------- TEST CUSTOM CONVERSION - CONVERT MULTI STEP ---------------')
# TODO add test for multi step conversion ... do I even do or want to support this ?
print('\n----------- TEST CUSTOM CONVERSION - REVERSE CONVERSION ---------------') print('\n----------- TEST CUSTOM CONVERSION - REVERSE CONVERSION ---------------')
uc2 = UnitConversion.objects.create( uc2 = UnitConversion.objects.create(
base_amount=200, base_amount=200,