def test_currency(self): Money = MoneyMaker('EUR') self.assertEqual(Money.currency, 'EUR') self.assertEqual(Money.subunits, 100) Money = MoneyMaker('JPY') self.assertEqual(Money.currency, 'JPY') self.assertEqual(Money.subunits, 1)
def test_reduce(self): Money = MoneyMaker('EUR') value = Money() func, args = value.__reduce__() self.assertTrue(func is _make_money) self.assertEqual(args, ("EUR", "NaN")) Money = func(*args) self.assertTrue(Money.is_nan())
def __init__(self, *args, **kwargs): self.currency_code = kwargs.pop('currency', app_settings.DEFAULT_CURRENCY) self.Money = MoneyMaker(self.currency_code) defaults = { 'max_digits': 30, 'decimal_places': CURRENCIES[self.currency_code][1], } defaults.update(kwargs) super().__init__(*args, **defaults)
def test_reduce(self): Money = MoneyMaker('EUR') value = Money() func, args = value.__reduce__() self.assertTrue(func is _make_money) self.assertEqual(args, ("EUR", "NaN")) Money = func(*args) self.assertTrue(Money.is_nan())
def test_filter_with_strings(self): amount = MoneyMaker('EUR')('12.34') m1 = Manufacturer(name="Rosebutt") m1.save() bag = Commodity.objects.create(unit_price=amount, product_code='B', order=1, product_name="Bag", slug='bag', manufacturer=m1, caption="This is a bag") self.assertEqual(list(Commodity.objects.filter(unit_price='12.34')), [bag]) self.assertEqual(list(Commodity.objects.filter(unit_price='12.35')), []) self.assertEqual( list(Commodity.objects.filter(unit_price__gt='12.33')), [bag]) self.assertEqual( list(Commodity.objects.filter(unit_price__gt='12.34')), []) self.assertEqual( list(Commodity.objects.filter(unit_price__gte='12.34')), [bag]) self.assertEqual( list(Commodity.objects.filter(unit_price__lt='12.35')), [bag]) self.assertEqual( list(Commodity.objects.filter(unit_price__lt='12.34')), []) self.assertEqual( list(Commodity.objects.filter(unit_price__lte='12.34')), [bag])
def test_json(self): EUR = MoneyMaker('EUR') renderer = JSONRenderer() data = {'amount': EUR('1.23')} rendered_json = renderer.render(data, 'application/json') self.assertDictEqual({'amount': "€ 1.23"}, json.loads(rendered_json.decode('utf-8')))
def test_to_python(self): EUR = MoneyMaker('EUR') f = MoneyDbField(currency='EUR', null=True) self.assertEqual(f.to_python(3), EUR('3')) self.assertEqual(f.to_python('3.14'), EUR('3.14')) self.assertEqual(f.to_python(None), EUR()) with self.assertRaises(ValidationError): f.to_python('abc')
def test_truediv(): Money = MoneyMaker() assert Money(1).__truediv__(2) == Money(0.5) assert Money(1).__truediv__(2.0) == Money(0.5) with pytest.raises(ValueError): Money(1).__truediv__(Money(2)) with pytest.raises(ValueError): Money(1).__rtruediv__(2)
def test_div(): Money = MoneyMaker() assert Money(1) / 2, Money(0.5) assert Money(1) / 2.0, Money(0.5) with pytest.raises(ValueError): Money(1) / Money(2) with pytest.raises(ValueError): Money(1).__rdiv__(2)
def test_default(self): EUR = MoneyMaker('EUR') f = MoneyDbField(currency='EUR', null=False) self.assertEqual(f.get_default(), EUR()) f = MoneyDbField(currency='EUR', null=True) self.assertEqual(f.get_default(), EUR()) f = MoneyDbField(currency='EUR') self.assertEqual(f.get_default(), EUR())
def test_as_bool(self): Money = MoneyMaker() amount = Money('1.23') self.assertTrue(bool(amount)) amount = Money(0) self.assertFalse(bool(amount)) amount = Money() self.assertFalse(bool(amount))
def __init__(self, *args, **kwargs): self.currency_code = kwargs.pop('currency', app_settings.DEFAULT_CURRENCY) self.Money = MoneyMaker(self.currency_code) defaults = { 'max_digits': 30, 'decimal_places': CURRENCIES[self.currency_code][1], } defaults.update(kwargs) super(MoneyField, self).__init__(*args, **defaults)
def __init__(self, money_class=None, **kwargs): if money_class is None: money_class = MoneyMaker() if not issubclass(money_class, AbstractMoney): raise AttributeError("Given `money_class` does not declare a valid money type") self.Money = money_class if 'widget' not in kwargs: kwargs['widget'] = MoneyFieldWidget(attrs={'currency_code': money_class.currency}) super().__init__(**kwargs)
def test_default(self): EUR = MoneyMaker('EUR') OneEuro = EUR(1) f = MoneyDbField(currency='EUR', null=True) self.assertEqual(f.get_default(), None) f = MoneyDbField(currency='EUR', null=True, default=EUR()) self.assertEqual(f.get_default(), EUR()) f = MoneyDbField(currency='EUR', null=False, default=OneEuro) self.assertEqual(f.get_default(), OneEuro)
def test_wrong_currency_raises_assertion_error(self): # If we try to call a money class with a value that has a # different currency than the class, and the value is an # instance of the money class, there should be an # AssertionError. Money = MoneyMaker(currency_code='EUR') value = Money() value._currency_code = 'USD' self.assertRaises(AssertionError, lambda: Money(value))
def test_float(): Money = MoneyMaker() money = Money(Decimal('sNaN')) with pytest.raises(ValueError): float(money) money = Money(Decimal('NaN')) assert math.isnan(float(money)) is True money = Money(Decimal('-NaN')) assert math.isnan(float(money)) is True money = Money(Decimal('1.0')) assert float(money) == 1.0
def test_float_format(self): EUR = MoneyMaker('EUR') d = Decimal(1.99) e = EUR(d) self.assertEqual('{}'.format(e), "€ 1.99") self.assertEqual('{:}'.format(e), "€ 1.99") self.assertEqual('{:f}'.format(e), "€ 1.99") self.assertEqual('{:.5}'.format(e), "€ 1.9900") self.assertEqual('{:.5f}'.format(e), "€ 1.99000") self.assertEqual('{:.20}'.format(e), "€ {:.20}".format(d)) self.assertEqual('{:.20f}'.format(e), "€ {:.20f}".format(d))
def test_mul(self): Money = MoneyMaker() self.assertEqual(Money(1) * 1, Money(1)) self.assertEqual(Money(1) * 0, Money(0)) self.assertEqual(Money(1) * -1, Money(-1)) self.assertEqual(Money(1) * 1, Money(1)) self.assertEqual(Money(1) * 0, Money(0)) self.assertEqual(Money(1).__rmul__(-1), Money(-1)) self.assertEqual(Money(1).__rmul__(1.0), Money(1)) self.assertEqual(format(Money(1) * None), "€ –") self.assertRaises(ValueError, lambda: Money(1) * (Money(1)))
def test_mul(): Money = MoneyMaker() assert Money(1) * 1 == Money(1) assert Money(1) * 0 == Money(0) assert Money(1) * -1 == Money(-1) assert Money(1) * 1 == Money(1) assert Money(1) * 0 == Money(0) assert Money(1).__rmul__(-1) == Money(-1) assert Money(1).__rmul__(1.0) == Money(1) assert format(Money(1) * None) == "€ –" with pytest.raises(ValueError): Money(1) * (Money(1))
def test_add_zero(self): Money = MoneyMaker() self.assertEqual(Money('1.23') + 0, Money('1.23')) self.assertEqual(Money('1.23') + 0.0, Money('1.23')) self.assertEqual(Money('1.23') + Money('NaN'), Money('1.23')) self.assertEqual(0 + Money('1.23'), Money('1.23')) self.assertEqual(0.0 + Money('1.23'), Money('1.23')) self.assertEqual(Money('NaN') + Money('1.23'), Money('1.23')) self.assertRaises(ValueError, lambda: Money(1) + 1) self.assertRaises(ValueError, lambda: Money(1) + 1.0) self.assertRaises(ValueError, lambda: 1 + Money(1)) self.assertRaises(ValueError, lambda: 1.0 + Money(1))
def test_float(self): Money = MoneyMaker() money = Money(Decimal('sNaN')) self.assertRaises(ValueError, lambda: float(money)) money = Money(Decimal('NaN')) self.assertTrue(math.isnan(float(money))) money = Money(Decimal('-NaN')) self.assertTrue(math.isnan(float(money))) money = Money(Decimal('1.0')) self.assertEqual(float(money), 1.0)
def test_add_zero(): Money = MoneyMaker() assert Money('1.23') + 0 == Money('1.23') assert Money('1.23') + 0.0 == Money('1.23') assert Money('1.23') + Money('NaN') == Money('1.23') assert 0 + Money('1.23') == Money('1.23') assert 0.0 + Money('1.23') == Money('1.23') assert Money('NaN') + Money('1.23') == Money('1.23') with pytest.raises(ValueError): Money(1) + 1 with pytest.raises(ValueError): Money(1) + 1.0 with pytest.raises(ValueError): 1 + Money(1) with pytest.raises(ValueError): 1.0 + Money(1)
def test_str(self): EUR = MoneyMaker('EUR') value = EUR() self.assertEqual(text_type(value), "€ –") value = EUR('999999.99') self.assertEqual(text_type(value), "€ 999,999.99") value = EUR('999999.995') # check rounding self.assertEqual(text_type(value), "€ 1,000,000.00") value = EUR('-111111.114') self.assertEqual(text_type(value), "-€ 111,111.11") with self.settings(USE_THOUSAND_SEPARATOR=False): self.assertEqual(text_type(value), "-€ 111111.11") with self.settings(LANGUAGE_CODE='de'): self.assertEqual(text_type(value), "-€ 111.111,11") value.MONEY_FORMAT = '{minus}{amount} {code}' self.assertEqual(text_type(value), "-111.111,11 EUR")
def test_create_money_type_without_decimal_places(self): Money = MoneyMaker(currency_code='JPY') self.assertEqual(Money._cents, 0)
def test_format(self): EUR, JPY = MoneyMaker('EUR'), MoneyMaker('JPY') self.assertEqual(format(EUR()), "€ –") self.assertEqual(format(JPY()), "¥ –") self.assertEqual(format(EUR(1.1)), "€ 1.10") self.assertEqual(format(JPY(1)), "¥ 1")
class MoneyField(models.DecimalField): """ A MoneyField shall be used to store money related amounts in the database, keeping track of the used currency. Accessing a model field of type MoneyField, returns a MoneyIn<CURRENCY> type. """ description = _("Money in %(currency_code)s") def __init__(self, *args, **kwargs): self.currency_code = kwargs.pop('currency', app_settings.DEFAULT_CURRENCY) self.Money = MoneyMaker(self.currency_code) defaults = { 'max_digits': 30, 'decimal_places': CURRENCIES[self.currency_code][1], } defaults.update(kwargs) super(MoneyField, self).__init__(*args, **defaults) def deconstruct(self): name, path, args, kwargs = super(MoneyField, self).deconstruct() if kwargs['max_digits'] == 30: kwargs.pop('max_digits') if kwargs['decimal_places'] == CURRENCIES[self.currency_code][1]: kwargs.pop('decimal_places') return name, path, args, kwargs def to_python(self, value): if isinstance(value, AbstractMoney): return value if value is None: return self.Money('NaN') value = super(MoneyField, self).to_python(value) return self.Money(value) def get_prep_value(self, value): # force to type Decimal by using grandparent super value = super(models.DecimalField, self).get_prep_value(value) return super(MoneyField, self).to_python(value) def from_db_value(self, value, expression, connection, context): if value is None: return if isinstance(value, float): value = str(value) return self.Money(value) def get_db_prep_save(self, value, connection): if isinstance(value, Decimal) and value.is_nan(): return None return super(MoneyField, self).get_db_prep_save(value, connection) def get_prep_lookup(self, lookup_type, value): if isinstance(value, AbstractMoney): if value.get_currency() != self.Money.get_currency(): msg = "This field stores money in {}, but the lookup amount is in {}" raise ValueError(msg.format(value.get_currency(), self.Money.get_currency())) value = value.as_decimal() result = super(MoneyField, self).get_prep_lookup(lookup_type, value) return result def value_to_string(self, obj): value = self._get_val_from_obj(obj) # grandparent super value = super(models.DecimalField, self).get_prep_value(value) return self.to_python(value) def formfield(self, **kwargs): widget = MoneyFieldWidget(attrs={'currency_code': self.Money.currency}) defaults = {'form_class': MoneyFormField, 'widget': widget, 'money_class': self.Money} defaults.update(**kwargs) formfield = super(MoneyField, self).formfield(**defaults) return formfield
def test_str(self): EUR = MoneyMaker('EUR') value = EUR() self.assertEqual(text_type(value), "€ –")
def test_str_with_too_much_precision(self): EUR = MoneyMaker('EUR') value = EUR(1) prec = getcontext().prec value._cents = Decimal("0." + ("0" * prec)) self.assertRaises(ValueError, lambda: str(value))
def test_create_instance_from_decimal(self): value = Decimal('1.2') EUR = MoneyMaker('EUR') self.assertTrue(issubclass(EUR, Decimal)) self.assertIsInstance(EUR(value), Decimal)
def test_create_instance_with_invalid_value(self): Money = MoneyMaker() self.assertRaises(ValueError, lambda: Money(value="invalid"))
def test_create_instance_with_value_as_none(self): Money = MoneyMaker() money = Money(value=None) self.assertTrue(money.is_nan())
def test_create_money_type_with_unknown_currency(self): self.assertRaises(ValueError, lambda: MoneyMaker(currency_code="ABC"))
def test_create_money_type_without_arguments(self): Money = MoneyMaker() amount = Money() self.assertTrue(amount.is_nan())