From 9a77089c6dd4c56555cff9729265383e5dd62b81 Mon Sep 17 00:00:00 2001 From: vabene1111 Date: Sat, 6 May 2023 23:57:45 +0200 Subject: [PATCH] improved unit conversion and tests --- cookbook/helper/unit_conversion_helper.py | 36 ++++++++-------- cookbook/tests/other/test_unit_conversion.py | 45 +++++++++++++------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/cookbook/helper/unit_conversion_helper.py b/cookbook/helper/unit_conversion_helper.py index 033c48250..4b9f6bf0b 100644 --- a/cookbook/helper/unit_conversion_helper.py +++ b/cookbook/helper/unit_conversion_helper.py @@ -1,18 +1,9 @@ from django.core.cache import caches from decimal import Decimal - from cookbook.helper.cache_helper import CacheHelper from cookbook.models import Ingredient, Unit -# basic units that should be considered for "to" conversions -# TODO possible remove this hardcoded units and just add a flag to the unit -CONVERT_TO_UNITS = { - 'metric': ['g', 'kg', 'ml', 'l'], - 'us': ['ounce', 'pound', 'fluid_ounce', 'pint', 'quart', 'gallon'], - 'uk': ['ounce', 'pound', 'imperial_fluid_ounce', 'imperial_pint', 'imperial_quart', 'imperial_gallon'], -} - CONVERSION_TABLE = { 'weight': { 'g': 1000, @@ -38,8 +29,8 @@ CONVERSION_TABLE = { }, } -BASE_UNITS_WEIGHT = CONVERSION_TABLE['weight'].keys() -BASE_UNITS_VOLUME = CONVERSION_TABLE['volume'].keys() +BASE_UNITS_WEIGHT = list(CONVERSION_TABLE['weight'].keys()) +BASE_UNITS_VOLUME = list(CONVERSION_TABLE['volume'].keys()) class ConversionException(Exception): @@ -58,6 +49,13 @@ class UnitConversionHelper: @staticmethod def convert_from_to(from_unit, to_unit, amount): + """ + Convert from one base unit to another. Throws ConversionException if trying to convert between different systems (weight/volume) or if units are not supported. + :param from_unit: str unit to convert from + :param to_unit: str unit to convert to + :param amount: amount to convert + :return: Decimal converted amount + """ system = None if from_unit in BASE_UNITS_WEIGHT and to_unit in BASE_UNITS_WEIGHT: system = 'weight' @@ -87,7 +85,7 @@ class UnitConversionHelper: # TODO allow setting which units to convert to? possibly only once conversions become visible units = caches['default'].get(CacheHelper(self.space).BASE_UNITS_CACHE_KEY, None) if not units: - units = Unit.objects.filter(space=self.space, base_unit__in=(CONVERT_TO_UNITS['metric'] + CONVERT_TO_UNITS['us'] + CONVERT_TO_UNITS['uk'])).all() + units = Unit.objects.filter(space=self.space, base_unit__in=(BASE_UNITS_VOLUME + BASE_UNITS_WEIGHT)).all() caches['default'].set(CacheHelper(self.space).BASE_UNITS_CACHE_KEY, units, 60 * 60) # cache is cleared on unit save signal so long duration is fine for u in units: @@ -112,13 +110,15 @@ class UnitConversionHelper: conversions = [ingredient] if ingredient.unit: for c in ingredient.unit.unit_conversion_base_relation.all(): - r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food) - if r and r not in conversions: - conversions.append(r) + if c.space == self.space: + r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food) + if r and r not in conversions: + conversions.append(r) for c in ingredient.unit.unit_conversion_converted_relation.all(): - r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food) - if r and r not in conversions: - conversions.append(r) + if c.space == self.space: + r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food) + if r and r not in conversions: + conversions.append(r) conversions = self.base_conversions(conversions) diff --git a/cookbook/tests/other/test_unit_conversion.py b/cookbook/tests/other/test_unit_conversion.py index a2e163d86..917f5991c 100644 --- a/cookbook/tests/other/test_unit_conversion.py +++ b/cookbook/tests/other/test_unit_conversion.py @@ -3,15 +3,29 @@ from _decimal import Decimal from django.contrib import auth from django_scopes import scopes_disabled -from cookbook.helper.unit_conversion_helper import UnitConversionHelper +from cookbook.helper.unit_conversion_helper import UnitConversionHelper, ConversionException from cookbook.models import Unit, Food, Ingredient, UnitConversion def test_base_converter(space_1): uch = UnitConversionHelper(space_1) - assert uch.convert_from_to('g', 'kg', 1234) == 1.234 - assert uch.convert_from_to('kg', 'pound', 2) == 4.40924 - # TODO add some more tests and test exception + assert abs(uch.convert_from_to('g', 'kg', 1234) - Decimal(1.234)) < 0.0001 + assert abs(uch.convert_from_to('kg', 'pound', 2) - Decimal(4.40924)) < 0.00001 + assert abs(uch.convert_from_to('kg', 'g', 1) - Decimal(1000)) < 0.00001 + assert abs(uch.convert_from_to('imperial_gallon', 'gallon', 1000) - Decimal(1200.95104)) < 0.00001 + assert abs(uch.convert_from_to('tbsp', 'ml', 20) - Decimal(295.73549)) < 0.00001 + + try: + assert uch.convert_from_to('kg', 'tbsp', 2) == 1234 + assert False + except ConversionException: + assert True + + try: + assert uch.convert_from_to('kg', 'g2', 2) == 1234 + assert False + except ConversionException: + assert True def test_unit_conversions(space_1, space_2, u1_s1): @@ -41,7 +55,7 @@ def test_unit_conversions(space_1, space_2, u1_s1): print(conversions) assert len(conversions) == 2 assert next(x for x in conversions if x.unit == unit_kg) is not None - assert next(x for x in conversions if x.unit == unit_kg).amount == 0.1 + assert abs(next(x for x in conversions if x.unit == unit_kg).amount - Decimal(0.1)) < 0.0001 print('\n----------- TEST BASE CONVERSIONS - VOLUMES ---------------') @@ -55,7 +69,7 @@ def test_unit_conversions(space_1, space_2, u1_s1): conversions = uch.get_conversions(ingredient_food_1_floz1) assert len(conversions) == 2 assert next(x for x in conversions if x.unit == unit_floz2) is not None - assert next(x for x in conversions if x.unit == unit_floz2).amount == 96.07599404038842 # TODO validate value + assert abs(next(x for x in conversions if x.unit == unit_floz2).amount - Decimal(96.07599404038842)) < 0.001 # TODO validate value print(conversions) @@ -63,7 +77,7 @@ def test_unit_conversions(space_1, space_2, u1_s1): conversions = uch.get_conversions(ingredient_food_1_floz1) assert len(conversions) == 3 assert next(x for x in conversions if x.unit == unit_pint) is not None - assert next(x for x in conversions if x.unit == unit_pint).amount == 6.004749627524276 # TODO validate value + assert abs(next(x for x in conversions if x.unit == unit_pint).amount - Decimal(6.004749627524276)) < 0.001 # TODO validate value print(conversions) @@ -80,7 +94,7 @@ def test_unit_conversions(space_1, space_2, u1_s1): assert len(conversions) == 3 assert next(x for x in conversions if x.unit == unit_fantasy) is not None - assert next(x for x in conversions if x.unit == unit_fantasy).amount == Decimal('133.700') # TODO validate value + assert abs(next(x for x in conversions if x.unit == unit_fantasy).amount - Decimal('133.700')) < 0.001 # TODO validate value print(conversions) @@ -117,8 +131,8 @@ def test_unit_conversions(space_1, space_2, u1_s1): conversions = uch.get_conversions(ingredient_food_1_pcs) assert len(conversions) == 3 - assert next(x for x in conversions if x.unit == unit_gram).amount == 1000 - assert next(x for x in conversions if x.unit == unit_kg).amount == 1 + assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001 + assert abs(next(x for x in conversions if x.unit == unit_kg).amount - Decimal(1)) < 0.0001 print(conversions) assert len(uch.get_conversions(ingredient_food_2_pcs)) == 1 @@ -137,19 +151,20 @@ def test_unit_conversions(space_1, space_2, u1_s1): conversions = uch.get_conversions(ingredient_food_1_pcs) assert len(conversions) == 3 - assert next(x for x in conversions if x.unit == unit_gram).amount == 1000 - assert next(x for x in conversions if x.unit == unit_kg).amount == 1 + assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001 + assert abs(next(x for x in conversions if x.unit == unit_kg).amount - Decimal(1)) < 0.0001 print(conversions) conversions = uch.get_conversions(ingredient_food_2_pcs) assert len(conversions) == 3 - assert next(x for x in conversions if x.unit == unit_gram).amount == 1000 - assert next(x for x in conversions if x.unit == unit_kg).amount == 1 + assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001 + assert abs(next(x for x in conversions if x.unit == unit_kg).amount - Decimal(1)) < 0.0001 print(conversions) print('\n----------- TEST SPACE SEPARATION ---------------') uc2.space = space_2 uc2.save() + conversions = uch.get_conversions(ingredient_food_2_pcs) assert len(conversions) == 1 print(conversions) @@ -164,5 +179,5 @@ def test_unit_conversions(space_1, space_2, u1_s1): assert len(conversions) == 2 assert not any(x for x in conversions if x.unit == unit_kg) assert next(x for x in conversions if x.unit == unit_kg_space_2) is not None - assert next(x for x in conversions if x.unit == unit_kg_space_2).amount == 0.1 + assert abs(next(x for x in conversions if x.unit == unit_kg_space_2).amount - Decimal(0.1)) < 0.0001 print(conversions)