mirror of
https://github.com/TandoorRecipes/recipes.git
synced 2026-01-06 22:58:19 -05:00
improved unit conversion and tests
This commit is contained in:
@@ -1,18 +1,9 @@
|
|||||||
from django.core.cache import caches
|
from django.core.cache import caches
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
|
||||||
from cookbook.helper.cache_helper import CacheHelper
|
from cookbook.helper.cache_helper import CacheHelper
|
||||||
from cookbook.models import Ingredient, Unit
|
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 = {
|
CONVERSION_TABLE = {
|
||||||
'weight': {
|
'weight': {
|
||||||
'g': 1000,
|
'g': 1000,
|
||||||
@@ -38,8 +29,8 @@ CONVERSION_TABLE = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
BASE_UNITS_WEIGHT = CONVERSION_TABLE['weight'].keys()
|
BASE_UNITS_WEIGHT = list(CONVERSION_TABLE['weight'].keys())
|
||||||
BASE_UNITS_VOLUME = CONVERSION_TABLE['volume'].keys()
|
BASE_UNITS_VOLUME = list(CONVERSION_TABLE['volume'].keys())
|
||||||
|
|
||||||
|
|
||||||
class ConversionException(Exception):
|
class ConversionException(Exception):
|
||||||
@@ -58,6 +49,13 @@ class UnitConversionHelper:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def convert_from_to(from_unit, to_unit, amount):
|
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
|
system = None
|
||||||
if from_unit in BASE_UNITS_WEIGHT and to_unit in BASE_UNITS_WEIGHT:
|
if from_unit in BASE_UNITS_WEIGHT and to_unit in BASE_UNITS_WEIGHT:
|
||||||
system = 'weight'
|
system = 'weight'
|
||||||
@@ -87,7 +85,7 @@ class UnitConversionHelper:
|
|||||||
# TODO allow setting which units to convert to? possibly only once conversions become visible
|
# 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)
|
units = caches['default'].get(CacheHelper(self.space).BASE_UNITS_CACHE_KEY, None)
|
||||||
if not units:
|
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
|
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:
|
for u in units:
|
||||||
@@ -112,10 +110,12 @@ class UnitConversionHelper:
|
|||||||
conversions = [ingredient]
|
conversions = [ingredient]
|
||||||
if ingredient.unit:
|
if ingredient.unit:
|
||||||
for c in ingredient.unit.unit_conversion_base_relation.all():
|
for c in ingredient.unit.unit_conversion_base_relation.all():
|
||||||
|
if c.space == self.space:
|
||||||
r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food)
|
r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food)
|
||||||
if r and r not in conversions:
|
if r and r not in conversions:
|
||||||
conversions.append(r)
|
conversions.append(r)
|
||||||
for c in ingredient.unit.unit_conversion_converted_relation.all():
|
for c in ingredient.unit.unit_conversion_converted_relation.all():
|
||||||
|
if c.space == self.space:
|
||||||
r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food)
|
r = self._uc_convert(c, ingredient.amount, ingredient.unit, ingredient.food)
|
||||||
if r and r not in conversions:
|
if r and r not in conversions:
|
||||||
conversions.append(r)
|
conversions.append(r)
|
||||||
|
|||||||
@@ -3,15 +3,29 @@ from _decimal import Decimal
|
|||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django_scopes import scopes_disabled
|
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
|
from cookbook.models import Unit, Food, Ingredient, UnitConversion
|
||||||
|
|
||||||
|
|
||||||
def test_base_converter(space_1):
|
def test_base_converter(space_1):
|
||||||
uch = UnitConversionHelper(space_1)
|
uch = UnitConversionHelper(space_1)
|
||||||
assert uch.convert_from_to('g', 'kg', 1234) == 1.234
|
assert abs(uch.convert_from_to('g', 'kg', 1234) - Decimal(1.234)) < 0.0001
|
||||||
assert uch.convert_from_to('kg', 'pound', 2) == 4.40924
|
assert abs(uch.convert_from_to('kg', 'pound', 2) - Decimal(4.40924)) < 0.00001
|
||||||
# TODO add some more tests and test exception
|
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):
|
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)
|
print(conversions)
|
||||||
assert len(conversions) == 2
|
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) 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 ---------------')
|
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)
|
conversions = uch.get_conversions(ingredient_food_1_floz1)
|
||||||
assert len(conversions) == 2
|
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) 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)
|
print(conversions)
|
||||||
|
|
||||||
@@ -63,7 +77,7 @@ def test_unit_conversions(space_1, space_2, u1_s1):
|
|||||||
conversions = uch.get_conversions(ingredient_food_1_floz1)
|
conversions = uch.get_conversions(ingredient_food_1_floz1)
|
||||||
assert len(conversions) == 3
|
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) 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)
|
print(conversions)
|
||||||
|
|
||||||
@@ -80,7 +94,7 @@ def test_unit_conversions(space_1, space_2, u1_s1):
|
|||||||
|
|
||||||
assert len(conversions) == 3
|
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) 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)
|
print(conversions)
|
||||||
|
|
||||||
@@ -117,8 +131,8 @@ def test_unit_conversions(space_1, space_2, u1_s1):
|
|||||||
|
|
||||||
conversions = uch.get_conversions(ingredient_food_1_pcs)
|
conversions = uch.get_conversions(ingredient_food_1_pcs)
|
||||||
assert len(conversions) == 3
|
assert len(conversions) == 3
|
||||||
assert next(x for x in conversions if x.unit == unit_gram).amount == 1000
|
assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001
|
||||||
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_kg).amount - Decimal(1)) < 0.0001
|
||||||
print(conversions)
|
print(conversions)
|
||||||
|
|
||||||
assert len(uch.get_conversions(ingredient_food_2_pcs)) == 1
|
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)
|
conversions = uch.get_conversions(ingredient_food_1_pcs)
|
||||||
assert len(conversions) == 3
|
assert len(conversions) == 3
|
||||||
assert next(x for x in conversions if x.unit == unit_gram).amount == 1000
|
assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001
|
||||||
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_kg).amount - Decimal(1)) < 0.0001
|
||||||
print(conversions)
|
print(conversions)
|
||||||
|
|
||||||
conversions = uch.get_conversions(ingredient_food_2_pcs)
|
conversions = uch.get_conversions(ingredient_food_2_pcs)
|
||||||
assert len(conversions) == 3
|
assert len(conversions) == 3
|
||||||
assert next(x for x in conversions if x.unit == unit_gram).amount == 1000
|
assert abs(next(x for x in conversions if x.unit == unit_gram).amount - Decimal(1000)) < 0.0001
|
||||||
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_kg).amount - Decimal(1)) < 0.0001
|
||||||
print(conversions)
|
print(conversions)
|
||||||
|
|
||||||
print('\n----------- TEST SPACE SEPARATION ---------------')
|
print('\n----------- TEST SPACE SEPARATION ---------------')
|
||||||
uc2.space = space_2
|
uc2.space = space_2
|
||||||
uc2.save()
|
uc2.save()
|
||||||
|
|
||||||
conversions = uch.get_conversions(ingredient_food_2_pcs)
|
conversions = uch.get_conversions(ingredient_food_2_pcs)
|
||||||
assert len(conversions) == 1
|
assert len(conversions) == 1
|
||||||
print(conversions)
|
print(conversions)
|
||||||
@@ -164,5 +179,5 @@ def test_unit_conversions(space_1, space_2, u1_s1):
|
|||||||
assert len(conversions) == 2
|
assert len(conversions) == 2
|
||||||
assert not any(x for x in conversions if x.unit == unit_kg)
|
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) 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)
|
print(conversions)
|
||||||
|
|||||||
Reference in New Issue
Block a user