142 lines
5.9 KiB
Python
142 lines
5.9 KiB
Python
from django.core.cache import caches
|
|
from decimal import Decimal
|
|
|
|
from cookbook.helper.cache_helper import CacheHelper
|
|
from cookbook.models import Ingredient, Unit
|
|
|
|
CONVERSION_TABLE = {
|
|
'weight': {
|
|
'g': 1000,
|
|
'kg': 1,
|
|
'ounce': 35.274,
|
|
'pound': 2.20462
|
|
},
|
|
'volume': {
|
|
'ml': 1000,
|
|
'l': 1,
|
|
'fluid_ounce': 33.814,
|
|
'pint': 2.11338,
|
|
'quart': 1.05669,
|
|
'gallon': 0.264172,
|
|
'tbsp': 67.628,
|
|
'tsp': 202.884,
|
|
'imperial_fluid_ounce': 35.1951,
|
|
'imperial_pint': 1.75975,
|
|
'imperial_quart': 0.879877,
|
|
'imperial_gallon': 0.219969,
|
|
'imperial_tbsp': 56.3121,
|
|
'imperial_tsp': 168.936,
|
|
},
|
|
}
|
|
|
|
BASE_UNITS_WEIGHT = list(CONVERSION_TABLE['weight'].keys())
|
|
BASE_UNITS_VOLUME = list(CONVERSION_TABLE['volume'].keys())
|
|
|
|
|
|
class ConversionException(Exception):
|
|
pass
|
|
|
|
|
|
class UnitConversionHelper:
|
|
space = None
|
|
|
|
def __init__(self, space):
|
|
"""
|
|
Initializes unit conversion helper
|
|
:param space: space to perform conversions on
|
|
"""
|
|
self.space = space
|
|
|
|
@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'
|
|
if from_unit in BASE_UNITS_VOLUME and to_unit in BASE_UNITS_VOLUME:
|
|
system = 'volume'
|
|
|
|
if not system:
|
|
raise ConversionException('Trying to convert units not existing or not in one unit system (weight/volume)')
|
|
|
|
return Decimal(amount / Decimal(CONVERSION_TABLE[system][from_unit] / CONVERSION_TABLE[system][to_unit]))
|
|
|
|
def base_conversions(self, ingredient_list):
|
|
"""
|
|
Calculates all possible base unit conversions for each ingredient give.
|
|
Converts to all common base units IF they exist in the unit database of the space.
|
|
For useful results all ingredients passed should be of the same food, otherwise filtering afterwards might be required.
|
|
:param ingredient_list: list of ingredients to convert
|
|
:return: ingredient list with appended conversions
|
|
"""
|
|
base_conversion_ingredient_list = ingredient_list.copy()
|
|
for i in ingredient_list:
|
|
try:
|
|
conversion_unit = i.unit.name
|
|
if i.unit.base_unit:
|
|
conversion_unit = i.unit.base_unit
|
|
|
|
# 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=(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:
|
|
try:
|
|
ingredient = Ingredient(amount=self.convert_from_to(conversion_unit, u.base_unit, i.amount), unit=u, food=ingredient_list[0].food, )
|
|
if not any((x.unit.name == ingredient.unit.name or x.unit.base_unit == ingredient.unit.name) for x in base_conversion_ingredient_list):
|
|
base_conversion_ingredient_list.append(ingredient)
|
|
except ConversionException:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
return base_conversion_ingredient_list
|
|
|
|
def get_conversions(self, ingredient):
|
|
"""
|
|
Converts an ingredient to all possible conversions based on the custom unit conversion database.
|
|
After that passes conversion to UnitConversionHelper.base_conversions() to get all base conversions possible.
|
|
:param ingredient: Ingredient object
|
|
:return: list of ingredients with all possible custom and base conversions
|
|
"""
|
|
conversions = [ingredient]
|
|
if ingredient.unit:
|
|
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)
|
|
if r and r not in conversions:
|
|
conversions.append(r)
|
|
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)
|
|
if r and r not in conversions:
|
|
conversions.append(r)
|
|
|
|
conversions = self.base_conversions(conversions)
|
|
|
|
return conversions
|
|
|
|
def _uc_convert(self, uc, amount, unit, food):
|
|
"""
|
|
Helper to calculate values for custom unit conversions.
|
|
Converts given base values using the passed UnitConversion object into a converted Ingredient
|
|
:param uc: UnitConversion object
|
|
:param amount: base amount
|
|
:param unit: base unit
|
|
:param food: base food
|
|
:return: converted ingredient object from base amount/unit/food
|
|
"""
|
|
if uc.food is None or uc.food == food:
|
|
if unit == uc.base_unit:
|
|
return Ingredient(amount=amount * (uc.converted_amount / uc.base_amount), unit=uc.converted_unit, food=food, space=self.space)
|
|
else:
|
|
return Ingredient(amount=amount * (uc.base_amount / uc.converted_amount), unit=uc.base_unit, food=food, space=self.space)
|