class TestExpressions(object): def test_conditional_update(self): SimpleMoneyModel.objects.bulk_create(( SimpleMoneyModel(amount=Money(1)), SimpleMoneyModel(amount=Money(2)), )) SimpleMoneyModel.objects.update( amount=Case(When(amount=1, then=Value(10)), default=Value(0))) assert SimpleMoneyModel.objects.all()[:1].get().amount == Money( 10, 'EUR') assert SimpleMoneyModel.objects.all()[1:2].get().amount == Money( 0, 'EUR') @pytest.mark.skipif(VERSION < (1, 9), reason='Only Django 1.9+ supports this') def test_create_func(self): instance = SimpleMoneyModel.objects.create( amount=Func(Value(-10), function='ABS')) instance.refresh_from_db() assert instance.amount.amount == 10 @pytest.mark.parametrize('value, expected', ( (None, None), (10, Money(10, 'EUR')), (Money(10, 'USD'), Money(10, 'USD')), )) def test_value_create(self, value, expected): instance = NullMoneyFieldModel.objects.create(amount=Value(value)) instance.refresh_from_db() assert instance.amount == expected def test_value_create_invalid(self): with pytest.raises(ValidationError): SimpleMoneyModel.objects.create(amount=Value('string'))
def test_currency_querying(self, model_class): model_class.objects.create(amount=Money('100.0', 'GBP')) assert model_class.objects.filter( amount__lt=Money('1000', 'USD')).count() == 0 assert model_class.objects.filter( amount__lt=Money('1000', 'GBP')).count() == 1
def test_fields(self, model_class): first_value = Money('100.0', 'GBP') second_value = Money('200.0', 'USD') instance = model_class.objects.create(amount=first_value, amount2=second_value) assert instance.amount == first_value assert instance.amount2 == second_value
def test_find_models_related_to_money_models(): money_model = SimpleMoneyModel.objects.create(amount=Money('100.0', 'USD')) ModelRelatedToModelWithMoney.objects.create(money_model=money_model) ModelRelatedToModelWithMoney.objects.get( money_model__amount=Money('100.0', 'USD')) ModelRelatedToModelWithMoney.objects.get( money_model__amount__lt=Money('1000.0', 'USD'))
def test_range_search(self): money = Money('3') instance = SimpleMoneyModel.objects.create(amount=Money('100.0')) retrieved = SimpleMoneyModel.objects.get(amount__gt=money) assert instance.pk == retrieved.pk assert SimpleMoneyModel.objects.filter(amount__lt=money).count() == 0
def test_default_currency(self): klass = self.create_class(default_currency=None, default=Money(10, 'EUR'), max_digits=10, decimal_places=2) assert str(klass._meta.fields[2].default_currency) == 'EUR' instance = klass() assert instance.field == Money(10, 'EUR')
def test_save_new_value(self): instance = SimpleMoneyModel.objects.create(amount=Money('100.0')) # Try setting the value directly setattr(instance, 'amount', Money(1, 'GBP')) instance.save() instance.refresh_from_db() assert getattr(instance, 'amount') == Money(1, 'GBP')
def test_conditional_update(self): SimpleMoneyModel.objects.bulk_create(( SimpleMoneyModel(amount=Money(1)), SimpleMoneyModel(amount=Money(2)), )) SimpleMoneyModel.objects.update( amount=Case(When(amount=1, then=Value(10)), default=Value(0))) assert SimpleMoneyModel.objects.all()[:1].get().amount == Money( 10, 'EUR') assert SimpleMoneyModel.objects.all()[1:2].get().amount == Money( 0, 'EUR')
def test_exact_match(self): money = Money('100.0') instance = SimpleMoneyModel.objects.create(amount=money) retrieved = SimpleMoneyModel.objects.get(amount=money) assert instance.pk == retrieved.pk
def test_isnull_lookup(self): NullMoneyFieldModel.objects.create(amount=None) NullMoneyFieldModel.objects.create(amount=Money(100, 'USD')) queryset = NullMoneyFieldModel.objects.filter(amount=None) assert queryset.count() == 1
def test_in_lookup(self): assert ModelWithTwoMoneyFields.objects.filter( amount1__in=(Money(1, 'EUR'), Money(5, 'USD'))).count() == 2 assert ModelWithTwoMoneyFields.objects.filter( Q(amount1__lte=Money(2, 'EUR')), amount1__in=(Money(1, 'EUR'), Money(3, 'EUR'))).count() == 1 assert ModelWithTwoMoneyFields.objects.exclude( amount1__in=(Money(1, 'EUR'), Money(5, 'USD'))).count() == 4
def test_money_field_serializer(self): model = SimpleMoneyModel.objects.create( amount=Money(amount='100.00', currency='EUR')) serializer = SimpleMoneyModelSerializer(model) expected = { 'id': model.pk, 'amount': model.amount.amount_rounded, 'amount_currency': model.amount.currency, } assert serializer.data == expected
class TestGetOrCreate(object): @pytest.mark.parametrize('kwargs, currency', (({ 'amount_currency': 'PLN' }, 'PLN'), ({ 'amount': Money(0, 'EUR') }, 'EUR'))) def test_get_or_create_respects_currency(self, kwargs, currency): instance, created = SimpleMoneyModel.objects.get_or_create(**kwargs) assert str( instance.amount.currency ) == currency, 'currency should be taken into account in get_or_create' def test_defaults(self): money = Money(10, 'GBP') instance, _ = SimpleMoneyModel.objects.get_or_create( defaults={'amount': money}) assert instance.amount == money
def test_patching(self): ProxyMoneyModel.objects.create(amount=Money('100.0', 'USD')) # This will fail if ProxyMoneyModel.objects doesn't have the patched manager assert ProxyMoneyModel.objects.filter( amount__gt=Money('50.00', 'GBP')).count() == 0
def test_defaults(self): money = Money(10, 'GBP') instance, _ = SimpleMoneyModel.objects.get_or_create( defaults={'amount': money}) assert instance.amount == money
def test_objects_creation(self): SimpleMoneyModel.objects.create(amount=Money('100.0', 'USD')) assert SimpleMoneyModel.objects.count() == 1
def test_instances(self): ProxyMoneyModel.objects.create(amount=Money('100.0', 'USD')) assert isinstance(ProxyMoneyModel.objects.all()[:1].get(), ProxyMoneyModel)
def test_string_parse(self): value = Money.from_string('100.0 GBP') assert value.amount == Decimal('100.0') assert value.currency == 'GBP' assert value.currency == CURRENCIES['GBP']
def test_mutation_of_currency_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.currency = 'USD'
class TestMoney(object): """ Tests of the Money class """ MONEY_CREATION = [ # Int values (Money(10, 'EUR'), Decimal(10), 'EUR'), (Money(-10, 'EUR'), Decimal(-10), 'EUR'), # Str values (Money('10', 'EUR'), Decimal(10), 'EUR'), (Money('-10', 'EUR'), Decimal(-10), 'EUR'), (Money(str('10.50'), str('EUR')), Decimal('10.50'), 'EUR'), (Money(str('-10.50'), str('EUR')), Decimal('-10.50'), 'EUR'), (Money('10.50', 'EUR'), Decimal('10.50'), 'EUR'), (Money('-10.50', 'EUR'), Decimal('-10.50'), 'EUR'), # Float values (Money(10.50, 'EUR'), Decimal('10.50'), 'EUR'), (Money(-10.50, 'EUR'), Decimal('-10.50'), 'EUR'), # Decimal values (Money(Decimal('10.50'), 'EUR'), Decimal('10.50'), 'EUR'), (Money(Decimal('-10.50'), 'EUR'), Decimal('-10.50'), 'EUR'), # Unspecified currency (Money(10), Decimal(10), settings.DEFAULT_CURRENCY), # Unspecified amount (Money(currency='EUR'), 0, 'EUR'), # Internal type (Money(1, Currency(code='AAA', name='My Currency')), 1, 'AAA'), # Parsed value (Money('10 EUR'), 10, 'EUR'), ] @pytest.mark.parametrize('value,expected_amount,expected_currency', MONEY_CREATION) def test_creation(self, value, expected_amount, expected_currency): assert isinstance(value, Money) assert value.amount == expected_amount assert value.currency.code == expected_currency MONEY_CREATION_UNSUPPORTED = [ (lambda: Money('10 EUR', 'USD')), (lambda: Money('EUR 10 EUR')), ] @pytest.mark.parametrize('value', MONEY_CREATION_UNSUPPORTED) def test_invalid_creation(self, value): with pytest.raises(IncorrectMoneyInputError): value() def test_amount_attribute(self): value = Money(101, 'USD') assert value.amount == 101 def test_mutation_of_amount_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.amount = 1 MONEY_PRECISION = [(Money('10 EUR'), Decimal(10)), (Money('10.2 EUR'), Decimal('10.2')), (Money('10.225 EUR'), Decimal('10.23')), (Money('10.226 EUR'), Decimal('10.23')), (Money('10.2288 EUR'), Decimal('10.23'))] @pytest.mark.parametrize('value,expected', MONEY_PRECISION) def test_amount_rounded_attribute(self, value, expected): assert value.amount_rounded == expected def test_mutation_of_amount_rounded_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.amount_rounded = 1 def test_currency_attribute(self): value = Money(101, 'USD') assert value.currency == 'USD' def test_mutation_of_currency_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.currency = 'USD' MONEY_STRINGS = [ # Default currency: ( Money(' 123'), f'123 {settings.DEFAULT_CURRENCY}', ), ( Money('-123'), f'-123 {settings.DEFAULT_CURRENCY}', ), # Test a currency with decimals: ( Money('123', 'EUR'), '123 EUR', ), ( Money('-123', 'EUR'), '-123 EUR', ), ( Money('123.0000', 'EUR'), '123.0000 EUR', ), ( Money('-123.0000', 'EUR'), '-123.0000 EUR', ), ( Money('123.25', 'EUR'), '123.25 EUR', ), ( Money('-123.25', 'EUR'), '-123.25 EUR', ), # Test a currency that is normally written without decimals: ( Money('123', 'JPY'), '123 JPY', ), ( Money('-123', 'JPY'), '-123 JPY', ), ( Money('123.0000', 'JPY'), '123.0000 JPY', ), ( Money('-123.0000', 'JPY'), '-123.0000 JPY', ), ( Money('123.25', 'JPY'), '123.25 JPY', ), ( Money('-123.25', 'JPY'), '-123.25 JPY', ), ] @pytest.mark.parametrize('value,expected', MONEY_STRINGS) def test_str(self, value, expected): assert str(value) == expected @pytest.mark.parametrize('value,expected', MONEY_STRINGS) def test_repr(self, value, expected): assert repr(value) == expected MONEY_ARITHMETIC = [ # Casting (lambda: Money('100') + 0.5, Money('100.5')), (lambda: float(Money('100')), float(100)), (lambda: int(Money('100')), 100), # Addition (lambda: Money('100') + Money('100'), Money('200')), (lambda: Money('100') + Money('-100'), Money('0')), (lambda: Money('100') + 100, Money('200')), (lambda: Money('100') + -100, Money('0')), (lambda: Money('100') + Decimal('100'), Money('200')), (lambda: Money('100') + Decimal('-100'), Money('0')), # Subtraction (lambda: Money('100') - Money('100'), Money('0')), (lambda: Money('100') - Money('-100'), Money('200')), (lambda: Money('100') - 100, Money('0')), (lambda: Money('100') - -100, Money('200')), (lambda: Money('100') - Decimal('100'), Money('0')), (lambda: Money('100') - Decimal('-100'), Money('200')), # Multiplication (lambda: Money('100') * 4, Money('400')), (lambda: Money('100') * Decimal('4'), Money('400')), # Division (lambda: Money('100') / 4, Money('25')), (lambda: Money('100') / Decimal('4'), Money('25')), # Negation (lambda: -Money('100'), Money('-100')), (lambda: -Money('100.12', 'EUR'), Money('-100.12', 'EUR')), (lambda: +Money('100'), Money('100')), # Absolute value (lambda: abs(Money('-100')), Money('100')), (lambda: abs(Money('-100.12', 'EUR')), Money('100.12', 'EUR')), (lambda: abs(Money('0')), Money('0')), (lambda: abs(Money('100')), Money('100')), (lambda: abs(Money('100.12', 'EUR')), Money('100.12', 'EUR')), ] @pytest.mark.parametrize('value,expected', MONEY_ARITHMETIC) def test_arithmetic(self, value, expected): result = value() assert result == expected MONEY_ARITHMETIC_UNSUPPORTED = [ # Modulus (lambda: 4 % Money('100')), (lambda: Decimal('4') % Money('100')), (lambda: Money('100') % 4), (lambda: Money('100') % Decimal('4')), # Division: floor division (see future import above) (lambda: Money('100') // 4), (lambda: Money('100') // Decimal('4')), # Dividing a value by Money (lambda: 4 / Money('100')), (lambda: Decimal('4') / Money('100')), (lambda: Money('100') / Money('100')), # Multiplication of 2 Money objects (lambda: Money('100') * Money('100')), # Subtracting money from a digit (lambda: 100 - Money('100')), (lambda: -100 - Money('100')), (lambda: Decimal('100') - Money('100')), (lambda: Decimal('-100') - Money('100')), ] @pytest.mark.parametrize('value', MONEY_ARITHMETIC_UNSUPPORTED) def test_invalid_arithmetic(self, value): with pytest.raises(TypeError): value() MONEY_ARITHMETIC_MISMATCHED = [ # Mismatched currencies (lambda: Money('100', 'JPY') + Money('100', 'EUR')), (lambda: Money('100', 'JPY') - Money('100', 'EUR')), ] @pytest.mark.parametrize('value', MONEY_ARITHMETIC_MISMATCHED) def test_invalid_currency(self, value): with pytest.raises(CurrencyMismatch): value() MONEY_EQUALITY = [ # Bool (bool(Money('0')), False), (bool(Money('1')), True), (bool(Money('0', 'EUR')), False), (bool(Money('1', 'EUR')), True), (bool(Money('-1', 'EUR')), True), # Equality (Money('0') == Money('0'), True), (Money('100') == Money('100'), True), (Money('-100') == Money('-100'), True), (Money('100', 'EUR') == Money('100', 'EUR'), True), (Money('100.0', 'EUR') == Money('100', 'EUR'), True), # Mismatched currencies (Money('0', 'EUR') == Money('0', 'JPY'), False), (Money('100', 'JPY') == Money('100'), False), (Money('100', 'EUR') == Money('100', 'JPY'), False), (Money('100.0', 'EUR') == Money('100', 'JPY'), False), # Other types (Money('100.0', 'EUR') == Decimal('100'), False), (Money('100.0', 'EUR') == 100, False), (Decimal('100') == Money('100.0', 'EUR'), False), (100 == Money('100.0', 'EUR'), False), # Inequality (Money('0') != Money('0'), False), (Money('100') != Money('100'), False), (Money('-100') != Money('-100'), False), (Money('100', 'EUR') != Money('100', 'EUR'), False), (Money('100.0', 'EUR') != Money('100', 'EUR'), False), # Mismatched currencies (Money('0', 'EUR') != Money('0', 'JPY'), True), (Money('100', 'JPY') != Money('100'), True), (Money('100', 'EUR') != Money('100', 'JPY'), True), (Money('100.0', 'EUR') != Money('100', 'JPY'), True), # Other types (Money('100.0', 'EUR') != Decimal('100'), True), (Money('100.0', 'EUR') != 100, True), (Decimal('100') != Money('100.0', 'EUR'), True), (100 != Money('100.0', 'EUR'), True), # LT/GT (0 < Money('0'), False), (100 < Money('100'), False), (-100 < Money('-100'), False), (100 < Money('100', 'EUR'), False), (100.0 < Money('100', 'EUR'), False), (0 > Money('1'), False), (1 > Money('0'), True), (-101 > Money('-100'), False), (-100 > Money('-101'), True), (100 > Money('100.01', 'EUR'), False), (100.01 > Money('100', 'EUR'), True), (Money('0') < Money('0'), False), (Money('100') < Money('100'), False), (Money('-100') < Money('-100'), False), (Money('100', 'EUR') < Money('100', 'EUR'), False), (Money('100.0', 'EUR') < Money('100', 'EUR'), False), (Money('0') > Money('0'), False), (Money('100') > Money('100'), False), (Money('-100') > Money('-100'), False), (Money('100', 'EUR') > Money('100', 'EUR'), False), (Money('0') < Money('1'), True), (Money('1') < Money('0'), False), (Money('-101') < Money('-100'), True), (Money('-100') < Money('-101'), False), (Money('100', 'EUR') < Money('100.01', 'EUR'), True), (Money('100.01', 'EUR') < Money('100', 'EUR'), False), (Money('0') > Money('1'), False), (Money('1') > Money('0'), True), (Money('-101') > Money('-100'), False), (Money('-100') > Money('-101'), True), (Money('100', 'EUR') > Money('100.01', 'EUR'), False), (Money('100.01', 'EUR') > Money('100', 'EUR'), True), (Money('100.0', 'EUR') < Money('100', 'EUR'), False), (Money('100.0', 'EUR') > Money('100', 'EUR'), False), # GTE/LTE (Money('0') <= Money('0'), True), (Money('100') <= Money('100'), True), (Money('-100') <= Money('-100'), True), (Money('100', 'EUR') <= Money('100', 'EUR'), True), (Money('100.0', 'EUR') <= Money('100', 'EUR'), True), (Money('0') >= Money('0'), True), (Money('100') >= Money('100'), True), (Money('-100') >= Money('-100'), True), (Money('100', 'EUR') >= Money('100', 'EUR'), True), (Money('100.0', 'EUR') >= Money('100', 'EUR'), True), (Money('0') <= Money('1'), True), (Money('1') <= Money('0'), False), (Money('-101') <= Money('-100'), True), (Money('-100') <= Money('-101'), False), (Money('100', 'EUR') <= Money('100.01', 'EUR'), True), (Money('100.01', 'EUR') <= Money('100', 'EUR'), False), (Money('0') >= Money('1'), False), (Money('1') >= Money('0'), True), (Money('-101') >= Money('-100'), False), (Money('-100') >= Money('-101'), True), (Money('100', 'EUR') >= Money('100.01', 'EUR'), False), (Money('100.01', 'EUR') >= Money('100', 'EUR'), True), (Money('100.0', 'EUR') <= Money('100', 'EUR'), True), (Money('100.0', 'EUR') >= Money('100', 'EUR'), True), ] @pytest.mark.parametrize('value,expected', MONEY_EQUALITY) def test_equality(self, value, expected): assert value == expected def test_string_parse(self): value = Money.from_string('100.0 GBP') assert value.amount == Decimal('100.0') assert value.currency == 'GBP' assert value.currency == CURRENCIES['GBP'] def test_string_parse_default_currency(self): value = Money.from_string('100.35') assert value.amount == Decimal('100.35') assert value.currency == settings.DEFAULT_CURRENCY assert value.currency == CURRENCIES[settings.DEFAULT_CURRENCY] MONEY_ROUND = [ (lambda: Money('123.001', 'EUR').round(), Money('123', 'EUR')), (lambda: Money('123.005', 'EUR').round(), Money('123.01', 'EUR')), (lambda: Money('123.006', 'EUR').round(), Money('123.01', 'EUR')), (lambda: Money('123.001', 'EUR').round(3), Money('123.001', 'EUR')), (lambda: Money('123.005', 'EUR').round(3), Money('123.005', 'EUR')), (lambda: Money('123.006', 'EUR').round(3), Money('123.006', 'EUR')), ] @pytest.mark.parametrize('value,expected', MONEY_ROUND) def test_round(self, value, expected): assert value() == expected
def test_string_parse_default_currency(self): value = Money.from_string('100.35') assert value.amount == Decimal('100.35') assert value.currency == settings.DEFAULT_CURRENCY assert value.currency == CURRENCIES[settings.DEFAULT_CURRENCY]
def test_f_update(self, f_obj, expected): instance = SimpleMoneyModel.objects.create(amount=Money(100, 'USD')) SimpleMoneyModel.objects.update(amount=f_obj) instance.refresh_from_db() assert instance.amount == expected
def test_update_fields_save(self): instance = SimpleMoneyModel.objects.create(amount=Money(100, 'USD')) instance.amount = F('amount') + Money(100, 'USD') instance.save(update_fields=['amount']) instance = SimpleMoneyModel.objects.get(pk=instance.pk) assert instance.amount == Money(200, 'USD')
class TestMoneyField(object): @pytest.mark.parametrize('value', ( (1, 'USD', 'extra_string'), (1, None), (1, ), )) def test_create_with_invalid_values(self, value): with pytest.raises(ValidationError): SimpleMoneyModel.objects.create(amount=value) @pytest.mark.parametrize('model_class, kwargs, expected', ( (NullMoneyFieldModel, {}, None), (SimpleMoneyModel, { 'amount': Money('100.0') }, Money('100.0')), (SimpleMoneyModel, {}, Money(0, 'EUR')), (SimpleMoneyModel, { 'amount': '111.2' }, Money('111.2', 'EUR')), (SimpleMoneyModel, { 'amount': Money('123', 'PLN') }, Money('123', 'PLN')), (SimpleMoneyModel, { 'amount': ('123', 'PLN') }, Money('123', 'PLN')), (SimpleMoneyModel, { 'amount': (123.0, 'PLN') }, Money('123', 'PLN')), (ModelWithDefaultAsMoney, {}, Money('0.01', 'RUB')), (ModelWithDefaultAsFloat, {}, Money('12.05', 'PLN')), (ModelWithDefaultAsStringWithCurrency, {}, Money('123', 'EUR')), (ModelWithDefaultAsString, {}, Money('123', 'PLN')), (ModelWithDefaultAsInt, {}, Money('123', 'GHS')), (ModelWithDefaultAsDecimal, {}, Money('0.01', 'CHF')), )) def test_create_defaults(self, model_class, kwargs, expected): instance = model_class.objects.create(**kwargs) assert instance.amount == expected retrieved = model_class.objects.get(pk=instance.pk) assert retrieved.amount == expected def test_save_new_value(self): instance = SimpleMoneyModel.objects.create(amount=Money('100.0')) # Try setting the value directly setattr(instance, 'amount', Money(1, 'GBP')) instance.save() instance.refresh_from_db() assert getattr(instance, 'amount') == Money(1, 'GBP') def test_not_supported_lookup(self): with pytest.raises(NotSupportedLookup) as exc: SimpleMoneyModel.objects.filter(amount__regex=r'\d+').count() assert str( exc.value) == 'Lookup "regex" is not supported for MoneyField' @pytest.fixture def objects_setup(self): ModelWithTwoMoneyFields.objects.bulk_create(( ModelWithTwoMoneyFields(amount1=Money(1, 'EUR'), amount2=Money(2, 'EUR')), ModelWithTwoMoneyFields(amount1=Money(2, 'EUR'), amount2=Money(0, 'EUR')), ModelWithTwoMoneyFields(amount1=Money(3, 'EUR'), amount2=Money(0, 'EUR')), ModelWithTwoMoneyFields(amount1=Money(4, 'EUR'), amount2=Money(0, 'GHS')), ModelWithTwoMoneyFields(amount1=Money(5, 'EUR'), amount2=Money(5, 'EUR')), ModelWithTwoMoneyFields(amount1=Money(5, 'USD'), amount2=Money(5, 'EUR')), )) @pytest.mark.parametrize('filters, expected_count', ( (Q(amount1=F('amount2')), 1), (Q(amount1__gt=F('amount2')), 2), (Q(amount1__in=(Money(1, 'EUR'), Money(5, 'USD'))), 2), (Q(id__in=(-1, -2)), 0), (Q(amount1=Money(1, 'EUR')) | Q(amount2=Money(0, 'EUR')), 3), (Q(amount1=Money(1, 'EUR')) | Q(amount1=Money(4, 'EUR')) | Q(amount2=Money(0, 'GHS')), 2), (Q(amount1=Money(1, 'EUR')) | Q(amount1=Money(5, 'EUR')) | Q(amount2=Money(0, 'GHS')), 3), (Q(amount1=Money(1, 'EUR')) | Q(amount1=Money(4, 'EUR'), amount2=Money(0, 'GHS')), 2), (Q(amount1=Money(1, 'EUR')) | Q(amount1__gt=Money(4, 'EUR'), amount2=Money(0, 'GHS')), 1), (Q(amount1=Money(1, 'EUR')) | Q(amount1__gte=Money(4, 'EUR'), amount2=Money(0, 'GHS')), 2), )) @pytest.mark.usefixtures('objects_setup') def test_comparison_lookup(self, filters, expected_count): assert ModelWithTwoMoneyFields.objects.filter( filters).count() == expected_count @pytest.mark.usefixtures('objects_setup') def test_in_lookup(self): assert ModelWithTwoMoneyFields.objects.filter( amount1__in=(Money(1, 'EUR'), Money(5, 'USD'))).count() == 2 assert ModelWithTwoMoneyFields.objects.filter( Q(amount1__lte=Money(2, 'EUR')), amount1__in=(Money(1, 'EUR'), Money(3, 'EUR'))).count() == 1 assert ModelWithTwoMoneyFields.objects.exclude( amount1__in=(Money(1, 'EUR'), Money(5, 'USD'))).count() == 4 def test_isnull_lookup(self): NullMoneyFieldModel.objects.create(amount=None) NullMoneyFieldModel.objects.create(amount=Money(100, 'USD')) queryset = NullMoneyFieldModel.objects.filter(amount=None) assert queryset.count() == 1 def test_rounding(self): instance = SimpleMoneyModel.objects.create( amount=Money('100.0623456781123219')) instance.refresh_from_db() assert instance.amount == Money('100.06') def test_exact_match(self): money = Money('100.0') instance = SimpleMoneyModel.objects.create(amount=money) retrieved = SimpleMoneyModel.objects.get(amount=money) assert instance.pk == retrieved.pk def test_range_search(self): money = Money('3') instance = SimpleMoneyModel.objects.create(amount=Money('100.0')) retrieved = SimpleMoneyModel.objects.get(amount__gt=money) assert instance.pk == retrieved.pk assert SimpleMoneyModel.objects.filter(amount__lt=money).count() == 0 # @pytest.mark.parametrize('model_class', (SimpleMoneyModel, ModelWithChoicesMoneyField)) @pytest.mark.parametrize('model_class', (SimpleMoneyModel, )) def test_currency_querying(self, model_class): model_class.objects.create(amount=Money('100.0', 'GBP')) assert model_class.objects.filter( amount__lt=Money('1000', 'USD')).count() == 0 assert model_class.objects.filter( amount__lt=Money('1000', 'GBP')).count() == 1
class TestFExpressions(object): parametrize_f_objects = pytest.mark.parametrize('f_obj, expected', ( (F('amount') + Money(100, 'USD'), Money(200, 'USD')), (F('amount') - Money(100, 'USD'), Money(0, 'USD')), (F('amount') * 2, Money(200, 'USD')), (F('amount') * 2, Money(200, 'USD')), (F('amount') / 2, Money(50, 'USD')), (F('amount') % 98, Money(2, 'USD')), (F('amount') / 2, Money(50, 'USD')), (F('amount') + F('amount'), Money(200, 'USD')), (F('amount') - F('amount'), Money(0, 'USD')), )) @parametrize_f_objects def test_save(self, f_obj, expected): instance = SimpleMoneyModel.objects.create(amount=Money(100, 'USD')) instance.amount = f_obj instance.save() instance.refresh_from_db() assert instance.amount == expected @parametrize_f_objects def test_f_update(self, f_obj, expected): instance = SimpleMoneyModel.objects.create(amount=Money(100, 'USD')) SimpleMoneyModel.objects.update(amount=f_obj) instance.refresh_from_db() assert instance.amount == expected @pytest.mark.parametrize('create_kwargs, filter_value, in_result', ( ({ 'amount1': Money(100, 'USD'), 'amount2': Money(100, 'USD') }, { 'amount1': F('amount1') }, True), ({ 'amount1': Money(100, 'USD'), 'amount2': Money(100, 'USD') }, { 'amount1': F('amount2') }, True), ({ 'amount1': Money(100, 'USD'), 'amount2': Money(100, 'EUR') }, { 'amount1': F('amount2') }, False), ({ 'amount1': Money(50, 'USD'), 'amount2': Money(100, 'USD') }, { 'amount2': F('amount1') * 2 }, True), ({ 'amount1': Money(50, 'USD'), 'amount2': Money(100, 'USD') }, { 'amount2': F('amount1') + Money(50, 'USD') }, True), ({ 'amount1': Money(50, 'USD'), 'amount2': Money(100, 'EUR') }, { 'amount2': F('amount1') * 2 }, False), ({ 'amount1': Money(50, 'USD'), 'amount2': Money(100, 'EUR') }, { 'amount2': F('amount1') + Money(50, 'USD') }, False), )) def test_filtration(self, create_kwargs, filter_value, in_result): instance = ModelWithTwoMoneyFields.objects.create(**create_kwargs) assert (instance in ModelWithTwoMoneyFields.objects.filter( **filter_value)) is in_result def test_update_fields_save(self): instance = SimpleMoneyModel.objects.create(amount=Money(100, 'USD')) instance.amount = F('amount') + Money(100, 'USD') instance.save(update_fields=['amount']) instance = SimpleMoneyModel.objects.get(pk=instance.pk) assert instance.amount == Money(200, 'USD')
def test_currency_attribute(self): value = Money(101, 'USD') assert value.currency == 'USD'
class ModelWithDefaultAsMoney(models.Model): amount = MoneyField(default=Money('0.01', 'RUB'), max_digits=10, decimal_places=2)
def test_rounding(self): instance = SimpleMoneyModel.objects.create( amount=Money('100.0623456781123219')) instance.refresh_from_db() assert instance.amount == Money('100.06')
def objects_setup(self): ModelWithTwoMoneyFields.objects.bulk_create(( ModelWithTwoMoneyFields(amount1=Money(1, 'EUR'), amount2=Money(2, 'EUR')), ModelWithTwoMoneyFields(amount1=Money(2, 'EUR'), amount2=Money(0, 'EUR')), ModelWithTwoMoneyFields(amount1=Money(3, 'EUR'), amount2=Money(0, 'EUR')), ModelWithTwoMoneyFields(amount1=Money(4, 'EUR'), amount2=Money(0, 'GHS')), ModelWithTwoMoneyFields(amount1=Money(5, 'EUR'), amount2=Money(5, 'EUR')), ModelWithTwoMoneyFields(amount1=Money(5, 'USD'), amount2=Money(5, 'EUR')), ))
def test_mutation_of_amount_rounded_attribute(self): money = Money('2', 'EUR') with pytest.raises(AttributeError): money.amount_rounded = 1