def test_price(self): with patch('exchange.conversion.ExchangeRates') as exchange_rates: exchange_rates.get_instance.return_value = \ {'USD': {'GBP': 0.5}} price = Money(3, 'USD') converted_price = price.convert('GBP') self.assertEqual(converted_price.amount, 1.50) self.assertEqual(converted_price.currency, 'GBP')
def __set__(self, obj, value): if isinstance(value, tuple): value = Money(amount=value[0], currency=value[1]) if isinstance(value, Money): obj.__dict__[self.field.name] = value.amount setattr(obj, self.currency_field_name, smart_unicode(value.currency)) elif isinstance(value, BaseExpression): if isinstance(value.children[1], Money): value.children[1] = value.children[1].amount obj.__dict__[self.field.name] = value else: if value: value = str(value) obj.__dict__[self.field.name] = self.field.to_python(value)
def __set__(self, obj, value): if isinstance(value, tuple): if len(value) != 2: raise ValueError('Invalid value for MoneyField: %s' % str(value)) value = Money(amount=value[0], currency=value[1]) if isinstance(value, Money): obj.__dict__[self.field.name] = value.amount # we have to determine whether to replace the currency. # i.e. if we do the following: # .objects.get_or_create(money_currency='EUR') # then the currency is already set up, before this code hits # __set__ of MoneyField. This is because the currency field # has less creation counter than money field. obj_curr = obj.__dict__[self.currency_field_name] val_curr = str(value.currency) def_curr = str(self.field.default_currency) if obj_curr != val_curr: # in other words, update the currency only if it wasn't # changed before. if obj_curr == def_curr: setattr( obj, self.currency_field_name, smart_unicode(value.currency)) elif isinstance(value, BaseExpression): # we can only allow this operation if the currency matches. # otherwise, one could use F() with different currencies # which should not be possible. def _check_currency(obj, field, amt): currency_field_name = get_currency_field_name(field.name) if obj.__dict__[currency_field_name] != str(amt.currency): raise ValueError( 'You cannot use F() with different currencies.') if VERSION < (1, 8): _check_currency(obj, self.field, value.children[1]) # Django 1.8 removed `children` attribute. if isinstance(value.children[1], Money): value.children[1] = value.children[1].amount elif VERSION >= (1, 8, 0): _check_currency(obj, self.field, value.rhs.value) # value.lhs contains F expression, i.e. # F(field) # rhs contains our value, however we need to extract the amount # it is an analogy to the above code (pre Django-1.8) value.rhs.value = value.rhs.value.amount obj.__dict__[self.field.name] = value else: if value: value = str(value) obj.__dict__[self.field.name] = self.field.to_python(value)
def test_object_to_money(self): data = {'amount': 10, 'currency': 'USD'} self.assertEqual(self.serializer.to_internal_value(data), Money(10, 'USD'))
def to_internal_value(self, data): if isinstance(data, Money): amount = super(MoneyField, self).to_internal_value(data.amount) return Money(amount, data.currency) return super(MoneyField, self).to_internal_value(data)
class TestVanillaMoneyField: @pytest.mark.parametrize('model_class, kwargs, expected', ( (ModelWithVanillaMoneyField, { 'money': Money('100.0') }, Money('100.0')), (BaseModel, {}, Money(0, 'USD')), (BaseModel, { 'money': '111.2' }, Money('111.2', 'USD')), (BaseModel, { 'money': Money('123', 'PLN') }, Money('123', 'PLN')), (BaseModel, { 'money': ('123', 'PLN') }, Money('123', 'PLN')), (BaseModel, { 'money': (123.0, 'PLN') }, Money('123', 'PLN')), (ModelWithDefaultAsMoney, {}, Money('0.01', 'RUB')), (ModelWithDefaultAsFloat, {}, Money('12.05', 'PLN')), (ModelWithDefaultAsStringWithCurrency, {}, Money('123', 'USD')), (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.money == expected retrieved = model_class.objects.get(pk=instance.pk) assert retrieved.money == expected @pytest.mark.parametrize('model_class, other_value', ( (ModelWithVanillaMoneyField, Money('100.0')), (BaseModel, Money(0, 'USD')), (ModelWithDefaultAsMoney, Money('0.01', 'RUB')), (ModelWithDefaultAsFloat, Money('12.05', 'PLN')), )) def test_revert_to_default(self, model_class, other_value): if hasattr(model_class._meta, 'get_field'): default_instance = model_class._meta.get_field( 'money').get_default() else: default_instance = model_class._meta.get_field_by_name( 'money').default instance1 = model_class.objects.create() pk = instance1.pk # Grab a fresh instance, change the currency to something non-default # and unexpected instance2 = model_class.objects.get(id=pk) instance2.money = Money(other_value.amount, "DKK") instance2.save() instance3 = model_class.objects.get(id=pk) assert instance3.money == Money(other_value.amount, "DKK") # Now change the field back to the default currency instance3.money = copy(default_instance) instance3.save() instance4 = model_class.objects.get(id=pk) assert instance4.money == default_instance @pytest.mark.parametrize('value', ( (1, 'USD', 'extra_string'), (1, None), (1, ), )) def test_invalid_values(self, value): with pytest.raises(ValidationError): BaseModel.objects.create(money=value) @pytest.mark.parametrize('field_name', ('money', 'second_money')) def test_save_new_value(self, field_name): ModelWithVanillaMoneyField.objects.create( **{field_name: Money('100.0')}) # Try setting the value directly retrieved = ModelWithVanillaMoneyField.objects.get() setattr(retrieved, field_name, Money(1, moneyed.DKK)) retrieved.save() retrieved = ModelWithVanillaMoneyField.objects.get() assert getattr(retrieved, field_name) == Money(1, moneyed.DKK) def test_rounding(self): money = Money('100.0623456781123219') instance = ModelWithVanillaMoneyField.objects.create(money=money) # TODO. Should instance.money be rounded too? retrieved = ModelWithVanillaMoneyField.objects.get(pk=instance.pk) assert retrieved.money == Money('100.06') @pytest.fixture def objects_setup(self): ModelWithTwoMoneyFields.objects.bulk_create(( ModelWithTwoMoneyFields(amount1=Money(1, 'USD'), amount2=Money(2, 'USD')), ModelWithTwoMoneyFields(amount1=Money(2, 'USD'), amount2=Money(0, 'USD')), ModelWithTwoMoneyFields(amount1=Money(3, 'USD'), amount2=Money(0, 'USD')), ModelWithTwoMoneyFields(amount1=Money(4, 'USD'), amount2=Money(0, 'GHS')), ModelWithTwoMoneyFields(amount1=Money(5, 'USD'), amount2=Money(5, 'USD')), ModelWithTwoMoneyFields(amount1=Money(5, 'EUR'), amount2=Money(5, 'USD')), )) @pytest.mark.parametrize('filters, expected_count', ( (Q(amount1=F('amount2')), 1), (Q(amount1__gt=F('amount2')), 2), (Q(amount1__in=(Money(1, 'USD'), Money(5, 'EUR'))), 2), (Q(id__in=(-1, -2)), 0), (Q(amount1=Money(1, 'USD')) | Q(amount2=Money(0, 'USD')), 3), (Q(amount1=Money(1, 'USD')) | Q(amount1=Money(4, 'USD')) | Q(amount2=Money(0, 'GHS')), 2), (Q(amount1=Money(1, 'USD')) | Q(amount1=Money(5, 'USD')) | Q(amount2=Money(0, 'GHS')), 3), (Q(amount1=Money(1, 'USD')) | Q(amount1=Money(4, 'USD'), amount2=Money(0, 'GHS')), 2), (Q(amount1=Money(1, 'USD')) | Q(amount1__gt=Money(4, 'USD'), amount2=Money(0, 'GHS')), 1), (Q(amount1=Money(1, 'USD')) | Q(amount1__gte=Money(4, 'USD'), 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.skipif(VERSION < (1, 9), reason='Only Django 1.9+ supports __date lookup') def test_date_lookup(self): DateTimeModel.objects.create(field=Money(1, 'USD'), created='2016-12-05') assert DateTimeModel.objects.filter( created__date='2016-12-01').count() == 0 assert DateTimeModel.objects.filter( created__date='2016-12-05').count() == 1 @pytest.mark.parametrize( 'lookup, rhs, expected', (('startswith', 2, 1), ('regex', '^[134]', 3), ('iregex', '^[134]', 3), ('istartswith', 2, 1), ('contains', 5, 2), ('lt', 5, 4), ('endswith', 5, 2), ('iendswith', 5, 2), ('gte', 4, 3), ('iexact', 3, 1), ('exact', 3, 1), ('isnull', True, 0), ('range', (3, 5), 4), ('lte', 2, 2), ('gt', 3, 3), ('icontains', 5, 2), ('in', (1, 0), 1))) @pytest.mark.usefixtures('objects_setup') def test_all_lookups(self, lookup, rhs, expected): kwargs = {'amount1__' + lookup: rhs} assert ModelWithTwoMoneyFields.objects.filter( **kwargs).count() == expected def test_exact_match(self): money = Money('100.0') instance = ModelWithVanillaMoneyField.objects.create(money=money) retrieved = ModelWithVanillaMoneyField.objects.get(money=money) assert instance.pk == retrieved.pk def test_range_search(self): money = Money('3') instance = ModelWithVanillaMoneyField.objects.create( money=Money('100.0')) retrieved = ModelWithVanillaMoneyField.objects.get(money__gt=money) assert instance.pk == retrieved.pk assert ModelWithVanillaMoneyField.objects.filter( money__lt=money).count() == 0 @pytest.mark.parametrize( 'model_class', (ModelWithVanillaMoneyField, ModelWithChoicesMoneyField)) def test_currency_querying(self, model_class): model_class.objects.create(money=Money('100.0', moneyed.ZWN)) assert model_class.objects.filter( money__lt=Money('1000', moneyed.USD)).count() == 0 assert model_class.objects.filter( money__lt=Money('1000', moneyed.ZWN)).count() == 1 @pytest.mark.usefixtures('objects_setup') def test_in_lookup(self): assert ModelWithTwoMoneyFields.objects.filter( amount1__in=(Money(1, 'USD'), Money(5, 'EUR'))).count() == 2 assert ModelWithTwoMoneyFields.objects.filter( Q(amount1__lte=Money(2, 'USD')), amount1__in=(Money(1, 'USD'), Money(3, 'USD'))).count() == 1 assert ModelWithTwoMoneyFields.objects.exclude( amount1__in=(Money(1, 'USD'), Money(5, 'EUR'))).count() == 4 assert ModelWithTwoMoneyFields.objects.filter( amount1__in=(1, Money(5, 'EUR'))).count() == 2 assert ModelWithTwoMoneyFields.objects.filter( amount1__in=(1, 5)).count() == 3 def test_isnull_lookup(self): NullMoneyFieldModel.objects.create(field=None) NullMoneyFieldModel.objects.create(field=Money(100, 'USD')) queryset = NullMoneyFieldModel.objects.filter(field=None) assert queryset.count() == 1 def test_null_default(self): instance = NullMoneyFieldModel.objects.create() assert instance.field is None
def test_deposit(self): """ Add some money to the acc""" self.assertRaises(SystemExit, deposit, ['--client', 'Joe', '--amount', 0.5]) self.assertEqual(Money(1001, settings.CURRENCY), self.acc.current_balance)
def aggregated_sum(self, qs): qs = qs.aggregate(total=Sum('amount')) return Money(qs['total'] or 0, settings.DEFAULT_CURRENCY)
def fuzz(self): return Money(super().fuzz(), 'SEK')
def test_get_stock_price_no_stocks(self): price = get_stock_price(ProductFactory(), Location.LOCATION_STORAGE) self.assertEqual(price, Money(0, "EUR"))
def test_ne(self): assert MoneyPatched(1, 'EUR') != Money(2, 'EUR')
def test_ne_currency(self): assert MoneyPatched(10, 'EUR') != Money(10, 'USD')
def test_sub_with_auto_convert(self, settings): settings.AUTO_CONVERT_MONEY = True result = MoneyPatched(10, 'EUR') - Money(1, 'USD') assert Decimal(str(round(result.amount, 2))) == Decimal('9.23') assert result.currency == moneyed.EUR
def test_eq(self): assert MoneyPatched(1, 'EUR') == Money(1, 'EUR')
def test_sub_default(self): with pytest.raises(TypeError): MoneyPatched(10, 'EUR') - Money(1, 'USD')
def test_patching(self): ProxyModel.objects.create(money=Money('100.0', 'USD')) # This will fail if ProxyModel.objects doesn't have the patched manager assert ProxyModel.objects.filter( money__gt=Money('50.00', 'GBP')).count() == 0
def test_instances(self): ProxyModel.objects.create(money=Money('100.0', 'USD')) assert isinstance(ProxyModel.objects.get(pk=1), ProxyModel)
def clean(self): cleaned_data = super(PaypalFundForm, self).clean() budget = cleaned_data["budget"] bonus = cleaned_data["bonus"] if budget.amount < 10: raise forms.ValidationError('Ensure budget is greater than or equal to %s' % Money(10, CAD)) if bonus.amount > budget.amount: raise forms.ValidationError('Ensure budget is greater than bonus') if bonus > self.account.bonus_budget: raise forms.ValidationError('Ensure bonus is lower than or equal to %s' % self.account.bonus_budget) return cleaned_data
def test_incompatibility(self, settings): settings.AUTO_CONVERT_MONEY = True with pytest.raises(ImproperlyConfigured) as exc: MoneyPatched(10, 'EUR') - Money(1, 'USD') assert str(exc.value) == 'djmoney_rates doesn\'t support Django 1.9+'
class WalletFactory(factory.django.DjangoModelFactory): class Meta: model = models.Wallet owner_id = factory.Sequence(lambda n: str(uuid.uuid4())) balance = Money(0, 'SEK')
def test_properties_access(): with pytest.raises(TypeError) as exc: ModelWithVanillaMoneyField(money=Money(1, 'USD'), bla=1) assert str( exc.value) == "'bla' is an invalid keyword argument for this function"
def test_get_stock_price(self): price = get_stock_price(self.product_a, Location.LOCATION_STORAGE) self.assertEqual(price, Money(2.6, "EUR"))
def test_it_should_prevent_paying_a_zero_amount(self): with raises(PreconditionError, match='Can only pay stricty positive amounts.'): initiate_payment(self.account, Money(0, 'CHF'))
def create(self, validated_data): """ 'title', 'image', - mock 'description', 'duration', 'weight', 'task_address', 'customer_address', 'created_at', 'pay_type', 'delivery_cost', 'product_cost', 'total_money', 'note', 'worker_id', 'worker_rate', 'status', 'id', 'currency', 'items' """ task_data = validated_data title = task_data['title'] desc = task_data['description'] duration = task_data['duration'] weight = task_data['weight'] pay_type = task_data['pay_type'] note = task_data['note'] service = task_data['service'] customer = task_data['customer'] task_address = task_data['task_address'] place = task_data['place'] customer_address = validated_data.pop('customer_address') c_address = Address.objects.get_or_create( **customer_address ) customer_address = c_address[0] delivery_cost = Money(task_data['delivery_cost']) product_cost = Money(task_data['product_cost']) if self.initial_data.get('currency'): delivery_cost.currency = self.initial_data.get('currency') product_cost.currency = self.initial_data.get('currency') task = Task.objects.create( description=desc, title=title, duration=duration, weight=weight, pay_type=pay_type, delivery_cost=delivery_cost, product_cost=product_cost, note=note, service=service, customer=customer, place=place, customer_address=customer_address, task_address=task_address ) task_items = task_data["task_to_product"] for items in task_items: s = TaskItem.objects.create( task=task, product=items['product'], quantity=items['quantity'] ) s.save() task.save() return task
def test_allow_expression_nodes_without_money(): """Allow querying on expression nodes that are not Money""" desc = 'hundred' ModelWithNonMoneyField.objects.create(money=Money(100.0), desc=desc) instance = ModelWithNonMoneyField.objects.filter(desc=F('desc')).get() assert instance.desc == desc
def test_withdraw(self): """ Remove some money from the acc""" self.assertRaises(SystemExit, withdraw, ['--client', 'Joe', '--amount', 1000]) self.assertEqual(Money(0.5, settings.CURRENCY), self.acc.current_balance)
def test_invalid_expressions_access(self, f_obj): instance = ModelWithVanillaMoneyField.objects.create( money=Money(100, 'USD')) with pytest.raises(ValidationError): instance.money = f_obj
def zero_dollars(currency=CAD): return Money('0', currency)
def test_float_to_money(self): data = 10.0 self.assertEqual(self.serializer.to_internal_value(data), Money(10.0, 'EUR'))
def test_it_should_prepare_payment_when_all_is_right(self): params = initiate_payment(self.account, Money(10, 'CHF')) assert params assert not params.use_alias
def test_objects_creation(self): SimpleModel.objects.create(money=Money('100.0', 'USD')) assert SimpleModel.objects.count() == 1
def _zero_balance(self): """Get a balance for this account with all currencies set to zero""" return Balance([Money("0", currency) for currency in self.currencies])
class TestFExpressions: parametrize_f_objects = pytest.mark.parametrize('f_obj, expected', ( (F('money') + Money(100, 'USD'), Money(200, 'USD')), (F('money') - Money(100, 'USD'), Money(0, 'USD')), (F('money') * 2, Money(200, 'USD')), (F('money') * F('integer'), Money(200, 'USD')), (F('money') / 2, Money(50, 'USD')), (F('money') % 98, Money(2, 'USD')), (F('money') / F('integer'), Money(50, 'USD')), (F('money') + F('money'), Money(200, 'USD')), (F('money') - F('money'), Money(0, 'USD')), )) @parametrize_f_objects def test_save(self, f_obj, expected): instance = ModelWithVanillaMoneyField.objects.create(money=Money( 100, 'USD'), integer=2) instance.money = f_obj instance.save() instance = ModelWithVanillaMoneyField.objects.get(pk=instance.pk) assert instance.money == expected @parametrize_f_objects def test_f_update(self, f_obj, expected): instance = ModelWithVanillaMoneyField.objects.create(money=Money( 100, 'USD'), integer=2) ModelWithVanillaMoneyField.objects.update(money=f_obj) instance = ModelWithVanillaMoneyField.objects.get(pk=instance.pk) assert instance.money == expected @pytest.mark.parametrize('create_kwargs, filter_value, in_result', ( ({ 'money': Money(100, 'USD'), 'second_money': Money(100, 'USD') }, { 'money': F('money') }, True), ({ 'money': Money(100, 'USD'), 'second_money': Money(100, 'USD') }, { 'money': F('second_money') }, True), ({ 'money': Money(100, 'USD'), 'second_money': Money(100, 'EUR') }, { 'money': F('second_money') }, False), ({ 'money': Money(50, 'USD'), 'second_money': Money(100, 'USD') }, { 'second_money': F('money') * 2 }, True), ({ 'money': Money(50, 'USD'), 'second_money': Money(100, 'USD') }, { 'second_money': F('money') + Money(50, 'USD') }, True), ({ 'money': Money(50, 'USD'), 'second_money': Money(100, 'EUR') }, { 'second_money': F('money') * 2 }, False), ({ 'money': Money(50, 'USD'), 'second_money': Money(100, 'EUR') }, { 'second_money': F('money') + Money(50, 'USD') }, False), )) def test_filtration(self, create_kwargs, filter_value, in_result): instance = ModelWithVanillaMoneyField.objects.create(**create_kwargs) assert (instance in ModelWithVanillaMoneyField.objects.filter( **filter_value)) is in_result def test_update_fields_save(self): instance = ModelWithVanillaMoneyField.objects.create(money=Money( 100, 'USD'), integer=2) instance.money = F('money') + Money(100, 'USD') instance.save(update_fields=['money']) instance = ModelWithVanillaMoneyField.objects.get(pk=instance.pk) assert instance.money == Money(200, 'USD') INVALID_EXPRESSIONS = [ F('money') + Money(100, 'EUR'), F('money') * F('money'), F('money') / F('money'), F('money') % F('money'), F('money') + F('integer'), F('money') + F('second_money'), F('money')**F('money'), F('money')**F('integer'), F('money')**2, ] @pytest.mark.parametrize('f_obj', INVALID_EXPRESSIONS) def test_invalid_expressions_access(self, f_obj): instance = ModelWithVanillaMoneyField.objects.create( money=Money(100, 'USD')) with pytest.raises(ValidationError): instance.money = f_obj