diff --git a/djmoney/__init__.py b/djmoney/__init__.py index 7743a229..cb501682 100644 --- a/djmoney/__init__.py +++ b/djmoney/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -__version__ = '0.15.dev' -default_app_config = 'djmoney.apps.MoneyConfig' +__version__ = "0.15.dev" +default_app_config = "djmoney.apps.MoneyConfig" diff --git a/djmoney/_compat.py b/djmoney/_compat.py index 3979d749..0be4df03 100644 --- a/djmoney/_compat.py +++ b/djmoney/_compat.py @@ -34,10 +34,10 @@ from django.core.validators import DecimalValidator class MoneyValidator(DecimalValidator): - def __call__(self, value): return super(MoneyValidator, self).__call__(value.amount) + except ImportError: MoneyValidator = None @@ -46,14 +46,17 @@ def setup_managers(sender): from .models.managers import money_manager if VERSION >= (1, 11): - default_manager_name = sender._meta.default_manager_name or 'objects' + default_manager_name = sender._meta.default_manager_name or "objects" for manager in filter(lambda m: m.name == default_manager_name, sender._meta.local_managers): money_manager(manager) else: - sender.copy_managers([ - (_id, name, money_manager(manager)) - for _id, name, manager in sender._meta.concrete_managers if name == 'objects' - ]) + sender.copy_managers( + [ + (_id, name, money_manager(manager)) + for _id, name, manager in sender._meta.concrete_managers + if name == "objects" + ] + ) def get_success_style(style): diff --git a/djmoney/admin.py b/djmoney/admin.py index 8a4d495c..d47c4c62 100644 --- a/djmoney/admin.py +++ b/djmoney/admin.py @@ -29,4 +29,4 @@ def display_for_field(value, field, empty): return original_display_for_field(value, field, empty) for mod in MODULES_TO_PATCH: - setattr(mod, 'display_for_field', display_for_field) + setattr(mod, "display_for_field", display_for_field) diff --git a/djmoney/apps.py b/djmoney/apps.py index 2e9e8230..133f99e8 100644 --- a/djmoney/apps.py +++ b/djmoney/apps.py @@ -3,7 +3,7 @@ class MoneyConfig(AppConfig): - name = 'djmoney' + name = "djmoney" def ready(self): try: diff --git a/djmoney/contrib/django_rest_framework/fields.py b/djmoney/contrib/django_rest_framework/fields.py index 1e16c7a4..6950032b 100644 --- a/djmoney/contrib/django_rest_framework/fields.py +++ b/djmoney/contrib/django_rest_framework/fields.py @@ -17,7 +17,7 @@ class MoneyField(DecimalField): """ def __init__(self, *args, **kwargs): - self.default_currency = kwargs.pop('default_currency', None) + self.default_currency = kwargs.pop("default_currency", None) super(MoneyField, self).__init__(*args, **kwargs) # Rest Framework converts `min_value` / `max_value` to validators, that are not aware about `Money` class # We need to adjust them diff --git a/djmoney/contrib/exchange/admin.py b/djmoney/contrib/exchange/admin.py index 3ad37ab1..48e2af9b 100644 --- a/djmoney/contrib/exchange/admin.py +++ b/djmoney/contrib/exchange/admin.py @@ -4,10 +4,10 @@ class RateAdmin(admin.ModelAdmin): - list_display = ('currency', 'value', 'last_update', 'backend') - list_filter = ('currency', ) - ordering = ('currency', ) - readonly_fields = ('backend', ) + list_display = ("currency", "value", "last_update", "backend") + list_filter = ("currency",) + ordering = ("currency",) + readonly_fields = ("backend",) def last_update(self, instance): return instance.backend.last_update diff --git a/djmoney/contrib/exchange/backends/base.py b/djmoney/contrib/exchange/backends/base.py index 5ded9b4e..f108f4e1 100644 --- a/djmoney/contrib/exchange/backends/base.py +++ b/djmoney/contrib/exchange/backends/base.py @@ -49,7 +49,7 @@ def get_response(self, **params): def parse_json(self, response): if isinstance(response, bytes): - response = response.decode('utf-8') + response = response.decode("utf-8") return json.loads(response, parse_float=Decimal) @atomic @@ -57,14 +57,16 @@ def update_rates(self, base_currency=settings.BASE_CURRENCY, **kwargs): """ Updates rates for the given backend. """ - backend, _ = ExchangeBackend.objects.update_or_create(name=self.name, defaults={'base_currency': base_currency}) + backend, _ = ExchangeBackend.objects.update_or_create(name=self.name, defaults={"base_currency": base_currency}) backend.clear_rates() params = self.get_params() params.update(base_currency=base_currency, **kwargs) - Rate.objects.bulk_create([ - Rate(currency=currency, value=value, backend=backend) - for currency, value in self.get_rates(**params).items() - ]) + Rate.objects.bulk_create( + [ + Rate(currency=currency, value=value, backend=backend) + for currency, value in self.get_rates(**params).items() + ] + ) class SimpleExchangeBackend(BaseExchangeBackend): @@ -75,4 +77,4 @@ class SimpleExchangeBackend(BaseExchangeBackend): def get_rates(self, **params): response = self.get_response(**params) - return self.parse_json(response)['rates'] + return self.parse_json(response)["rates"] diff --git a/djmoney/contrib/exchange/backends/fixer.py b/djmoney/contrib/exchange/backends/fixer.py index 32f08d8e..09503583 100644 --- a/djmoney/contrib/exchange/backends/fixer.py +++ b/djmoney/contrib/exchange/backends/fixer.py @@ -6,13 +6,13 @@ class FixerBackend(SimpleExchangeBackend): - name = 'fixer.io' + name = "fixer.io" def __init__(self, url=settings.FIXER_URL, access_key=settings.FIXER_ACCESS_KEY): if access_key is None: - raise ImproperlyConfigured('settings.FIXER_ACCESS_KEY should be set to use FixerBackend') + raise ImproperlyConfigured("settings.FIXER_ACCESS_KEY should be set to use FixerBackend") self.url = url self.access_key = access_key def get_params(self): - return {'access_key': self.access_key} + return {"access_key": self.access_key} diff --git a/djmoney/contrib/exchange/backends/openexchangerates.py b/djmoney/contrib/exchange/backends/openexchangerates.py index e4802d38..93730f5e 100644 --- a/djmoney/contrib/exchange/backends/openexchangerates.py +++ b/djmoney/contrib/exchange/backends/openexchangerates.py @@ -6,16 +6,16 @@ class OpenExchangeRatesBackend(SimpleExchangeBackend): - name = 'openexchangerates.org' + name = "openexchangerates.org" url = settings.OPEN_EXCHANGE_RATES_URL def __init__(self, url=settings.OPEN_EXCHANGE_RATES_URL, access_key=settings.OPEN_EXCHANGE_RATES_APP_ID): if access_key is None: raise ImproperlyConfigured( - 'settings.OPEN_EXCHANGE_RATES_APP_ID should be set to use OpenExchangeRatesBackend' + "settings.OPEN_EXCHANGE_RATES_APP_ID should be set to use OpenExchangeRatesBackend" ) self.url = url self.access_key = access_key def get_params(self): - return {'app_id': self.access_key} + return {"app_id": self.access_key} diff --git a/djmoney/contrib/exchange/exceptions.py b/djmoney/contrib/exchange/exceptions.py index cb3158ef..1f912e49 100644 --- a/djmoney/contrib/exchange/exceptions.py +++ b/djmoney/contrib/exchange/exceptions.py @@ -1,5 +1,3 @@ - - class MissingRate(Exception): """ Absence of some exchange rate. diff --git a/djmoney/contrib/exchange/management/base.py b/djmoney/contrib/exchange/management/base.py index 9c1a1dd1..88d87a54 100644 --- a/djmoney/contrib/exchange/management/base.py +++ b/djmoney/contrib/exchange/management/base.py @@ -12,10 +12,11 @@ class BaseExchangeCommand(BaseCommand): def add_arguments(self, parser): parser.add_argument( - '-b', '--backend', - action='store', - dest='backend', - help='Importable string for custom exchange rates backend.', + "-b", + "--backend", + action="store", + dest="backend", + help="Importable string for custom exchange rates backend.", required=False, default=settings.EXCHANGE_BACKEND, ) diff --git a/djmoney/contrib/exchange/management/commands/clear_rates.py b/djmoney/contrib/exchange/management/commands/clear_rates.py index 82d42442..7ecb75a8 100644 --- a/djmoney/contrib/exchange/management/commands/clear_rates.py +++ b/djmoney/contrib/exchange/management/commands/clear_rates.py @@ -6,23 +6,25 @@ class Command(BaseExchangeCommand): - help = 'Clears exchange rates.' + help = "Clears exchange rates." def add_arguments(self, parser): super(Command, self).add_arguments(parser) parser.add_argument( - '--all', action='store_true', dest='all', - help='Clear rates for all backends.', + "--all", + action="store_true", + dest="all", + help="Clear rates for all backends.", required=False, default=False, ) def handle(self, *args, **options): - if options['all']: + if options["all"]: Rate.objects.all().delete() - message = 'Successfully cleared all rates' + message = "Successfully cleared all rates" else: - backend = import_string(options['backend']) + backend = import_string(options["backend"]) Rate.objects.filter(backend=backend.name).delete() - message = 'Successfully cleared rates for %s' % backend.name + message = "Successfully cleared rates for %s" % backend.name self.success(message) diff --git a/djmoney/contrib/exchange/management/commands/update_rates.py b/djmoney/contrib/exchange/management/commands/update_rates.py index 1822497e..2d97df58 100644 --- a/djmoney/contrib/exchange/management/commands/update_rates.py +++ b/djmoney/contrib/exchange/management/commands/update_rates.py @@ -4,9 +4,9 @@ class Command(BaseExchangeCommand): - help = 'Updates exchange rates.' + help = "Updates exchange rates." def handle(self, *args, **options): - backend = import_string(options['backend'])() + backend = import_string(options["backend"])() backend.update_rates() - self.success('Successfully updated rates from %s' % backend.name) + self.success("Successfully updated rates from %s" % backend.name) diff --git a/djmoney/contrib/exchange/migrations/0001_initial.py b/djmoney/contrib/exchange/migrations/0001_initial.py index 20a4fd65..24611870 100644 --- a/djmoney/contrib/exchange/migrations/0001_initial.py +++ b/djmoney/contrib/exchange/migrations/0001_initial.py @@ -8,29 +8,30 @@ class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='ExchangeBackend', + name="ExchangeBackend", fields=[ - ('name', models.CharField(max_length=255, primary_key=True, serialize=False)), - ('last_update', models.DateTimeField(auto_now=True)), - ('base_currency', models.CharField(max_length=3)), + ("name", models.CharField(max_length=255, primary_key=True, serialize=False)), + ("last_update", models.DateTimeField(auto_now=True)), + ("base_currency", models.CharField(max_length=3)), ], ), migrations.CreateModel( - name='Rate', + name="Rate", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('currency', models.CharField(max_length=3)), - ('value', models.DecimalField(decimal_places=6, max_digits=20)), - ('backend', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='exchange.ExchangeBackend', related_name='rates')), + ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("currency", models.CharField(max_length=3)), + ("value", models.DecimalField(decimal_places=6, max_digits=20)), + ( + "backend", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="exchange.ExchangeBackend", related_name="rates" + ), + ), ], ), - migrations.AlterUniqueTogether( - name='rate', - unique_together={('currency', 'backend')}, - ), + migrations.AlterUniqueTogether(name="rate", unique_together={("currency", "backend")}), ] diff --git a/djmoney/contrib/exchange/models.py b/djmoney/contrib/exchange/models.py index e8c7b8ee..3ebb6b85 100644 --- a/djmoney/contrib/exchange/models.py +++ b/djmoney/contrib/exchange/models.py @@ -25,10 +25,10 @@ def clear_rates(self): class Rate(models.Model): currency = models.CharField(max_length=3) value = models.DecimalField(max_digits=20, decimal_places=6) - backend = models.ForeignKey(ExchangeBackend, on_delete=models.CASCADE, related_name='rates') + backend = models.ForeignKey(ExchangeBackend, on_delete=models.CASCADE, related_name="rates") class Meta: - unique_together = (('currency', 'backend'), ) + unique_together = (("currency", "backend"),) def get_default_backend_name(): @@ -43,7 +43,7 @@ def get_rate(source, target, backend=None): """ if backend is None: backend = get_default_backend_name() - key = 'djmoney:get_rate:%s:%s:%s' % (source, target, backend) + key = "djmoney:get_rate:%s:%s:%s" % (source, target, backend) result = cache.get(key) if result is not None: return result @@ -56,9 +56,9 @@ def _get_rate(source, target, backend): source, target = text_type(source), text_type(target) if text_type(source) == target: return 1 - rates = Rate.objects.filter(currency__in=(source, target), backend=backend).select_related('backend') + rates = Rate.objects.filter(currency__in=(source, target), backend=backend).select_related("backend") if not rates: - raise MissingRate('Rate %s -> %s does not exist' % (source, target)) + raise MissingRate("Rate %s -> %s does not exist" % (source, target)) if len(rates) == 1: return _try_to_get_rate_directly(source, target, rates[0]) return _get_rate_via_base(rates, target) @@ -75,7 +75,7 @@ def _try_to_get_rate_directly(source, target, rate): elif rate.backend.base_currency == target and rate.currency == source: return 1 / rate.value # Case when target or source is not a base currency - raise MissingRate('Rate %s -> %s does not exist' % (source, target)) + raise MissingRate("Rate %s -> %s does not exist" % (source, target)) def _get_rate_via_base(rates, target): @@ -102,7 +102,7 @@ def _get_rate_via_base(rates, target): def convert_money(value, currency): - if 'djmoney.contrib.exchange' not in settings.INSTALLED_APPS: + if "djmoney.contrib.exchange" not in settings.INSTALLED_APPS: raise ImproperlyConfigured( "You have to add 'djmoney.contrib.exchange' to INSTALLED_APPS in order to use currency exchange" ) diff --git a/djmoney/forms/fields.py b/djmoney/forms/fields.py index 46553762..3dd650dd 100644 --- a/djmoney/forms/fields.py +++ b/djmoney/forms/fields.py @@ -11,16 +11,30 @@ from .widgets import MoneyWidget -__all__ = ('MoneyField',) +__all__ = ("MoneyField",) class MoneyField(MultiValueField): - - def __init__(self, currency_widget=None, currency_choices=CURRENCY_CHOICES, max_value=None, min_value=None, - max_digits=None, decimal_places=None, default_amount=None, default_currency=None, *args, **kwargs): + def __init__( + self, + currency_widget=None, + currency_choices=CURRENCY_CHOICES, + max_value=None, + min_value=None, + max_digits=None, + decimal_places=None, + default_amount=None, + default_currency=None, + *args, + **kwargs + ): amount_field = DecimalField( - *args, max_value=max_value, min_value=min_value, max_digits=max_digits, decimal_places=decimal_places, + *args, + max_value=max_value, + min_value=min_value, + max_digits=max_digits, + decimal_places=decimal_places, **kwargs ) currency_field = ChoiceField(choices=currency_choices) @@ -34,7 +48,7 @@ def __init__(self, currency_widget=None, currency_choices=CURRENCY_CHOICES, max_ self.widget = MoneyWidget( amount_widget=amount_field.widget, currency_widget=currency_field.widget, - default_currency=default_currency + default_currency=default_currency, ) # The two fields that this widget comprises fields = (amount_field, currency_field) @@ -59,10 +73,10 @@ def clean(self, value): def has_changed(self, initial, data): # noqa # Django 1.8 has no 'disabled' attribute - if hasattr(self, 'disabled') and self.disabled: + if hasattr(self, "disabled") and self.disabled: return False if initial is None: - initial = ['' for _ in range(0, len(data))] + initial = ["" for _ in range(0, len(data))] else: if not isinstance(initial, list): initial = self.widget.decompress(initial) diff --git a/djmoney/forms/widgets.py b/djmoney/forms/widgets.py index aa0a5f56..c16a5515 100644 --- a/djmoney/forms/widgets.py +++ b/djmoney/forms/widgets.py @@ -4,13 +4,19 @@ from ..settings import CURRENCY_CHOICES -__all__ = ('MoneyWidget',) +__all__ = ("MoneyWidget",) class MoneyWidget(MultiWidget): - - def __init__(self, choices=CURRENCY_CHOICES, amount_widget=TextInput, currency_widget=None, default_currency=None, - *args, **kwargs): + def __init__( + self, + choices=CURRENCY_CHOICES, + amount_widget=TextInput, + currency_widget=None, + default_currency=None, + *args, + **kwargs + ): self.default_currency = default_currency if not currency_widget: currency_widget = Select(choices=choices) diff --git a/djmoney/models/__init__.py b/djmoney/models/__init__.py index 648970a1..41b74c13 100644 --- a/djmoney/models/__init__.py +++ b/djmoney/models/__init__.py @@ -2,4 +2,4 @@ from django.core import serializers -serializers.register_serializer('json', 'djmoney.serializers') +serializers.register_serializer("json", "djmoney.serializers") diff --git a/djmoney/models/fields.py b/djmoney/models/fields.py index 25edd3ae..1c84c657 100644 --- a/djmoney/models/fields.py +++ b/djmoney/models/fields.py @@ -16,17 +16,12 @@ from djmoney.money import Currency, Money from moneyed import Money as OldMoney -from .._compat import ( - MoneyValidator, - setup_managers, - smart_unicode, - string_types, -) +from .._compat import MoneyValidator, setup_managers, smart_unicode, string_types from ..settings import CURRENCY_CHOICES, DEFAULT_CURRENCY from ..utils import MONEY_CLASSES, get_currency_field_name, prepare_expression -__all__ = ('MoneyField', ) +__all__ = ("MoneyField",) def get_value(obj, expr): @@ -54,13 +49,13 @@ def validate_money_expression(obj, expr): lhs = get_value(obj, expr.lhs) rhs = get_value(obj, expr.rhs) - if (not isinstance(rhs, Money) and connector in ('+', '-')) or connector == '^': - raise ValidationError('Invalid F expression for MoneyField.', code='invalid') + if (not isinstance(rhs, Money) and connector in ("+", "-")) or connector == "^": + raise ValidationError("Invalid F expression for MoneyField.", code="invalid") if isinstance(lhs, Money) and isinstance(rhs, Money): - if connector in ('*', '/', '^', '%%'): - raise ValidationError('Invalid F expression for MoneyField.', code='invalid') + if connector in ("*", "/", "^", "%%"): + raise ValidationError("Invalid F expression for MoneyField.", code="invalid") if lhs.currency != rhs.currency: - raise ValidationError('You cannot use F() with different currencies.', code='invalid') + raise ValidationError("You cannot use F() with different currencies.", code="invalid") def validate_money_value(value): @@ -71,11 +66,7 @@ def validate_money_value(value): - Pairs of numeric value and currency. Currency can't be None. """ if isinstance(value, (list, tuple)) and (len(value) != 2 or value[1] is None): - raise ValidationError( - 'Invalid value for MoneyField: %(value)s.', - code='invalid', - params={'value': value}, - ) + raise ValidationError("Invalid value for MoneyField: %(value)s.", code="invalid", params={"value": value}) def get_currency(value): @@ -89,7 +80,6 @@ def get_currency(value): class MoneyFieldProxy(object): - def __init__(self, field): self.field = field self.currency_field_name = get_currency_field_name(self.field.name, self.field) @@ -114,7 +104,7 @@ def __get__(self, obj, type=None): def __set__(self, obj, value): # noqa if value is not None and self.field._currency_field.null and not isinstance(value, MONEY_CLASSES + (Decimal,)): # For nullable fields we need either both NULL amount and currency or both NOT NULL - raise ValueError('Missing currency value') + raise ValueError("Missing currency value") if isinstance(value, BaseExpression): if isinstance(value, Value): value = self.prepare_value(obj, value.value) @@ -152,7 +142,7 @@ def set_currency(self, obj, value): class CurrencyField(models.CharField): - description = 'A field which stores currency.' + description = "A field which stores currency." def __init__(self, price_field=None, default=DEFAULT_CURRENCY, **kwargs): if isinstance(default, Currency): @@ -167,16 +157,22 @@ def contribute_to_class(self, cls, name): class MoneyField(models.DecimalField): - description = 'A field which stores both the currency and amount of money.' - - def __init__(self, verbose_name=None, name=None, - max_digits=None, decimal_places=None, - default=None, - default_currency=DEFAULT_CURRENCY, - currency_choices=CURRENCY_CHOICES, - currency_max_length=3, - currency_field_name=None, **kwargs): - nullable = kwargs.get('null', False) + description = "A field which stores both the currency and amount of money." + + def __init__( + self, + verbose_name=None, + name=None, + max_digits=None, + decimal_places=None, + default=None, + default_currency=DEFAULT_CURRENCY, + currency_choices=CURRENCY_CHOICES, + currency_max_length=3, + currency_field_name=None, + **kwargs + ): + nullable = kwargs.get("null", False) default = self.setup_default(default, default_currency, nullable) if not default_currency and default is not None: default_currency = default.currency @@ -195,7 +191,7 @@ def setup_default(self, default, default_currency, nullable): try: # handle scenario where default is formatted like: # 'amount currency-code' - amount, currency = default.split(' ') + amount, currency = default.split(" ") except ValueError: # value error would be risen if the default is # without the currency part, i.e @@ -208,7 +204,7 @@ def setup_default(self, default, default_currency, nullable): elif isinstance(default, OldMoney): default.__class__ = Money if default is not None and not isinstance(default, Money): - raise ValueError('default value must be an instance of Money, is: %s' % default) + raise ValueError("default value must be an instance of Money, is: %s" % default) return default def to_python(self, value): @@ -236,14 +232,12 @@ def validators(self): """ Default ``DecimalValidator`` doesn't work with ``Money`` instances. """ - return super(models.DecimalField, self).validators + [ - MoneyValidator(self.max_digits, self.decimal_places) - ] + return super(models.DecimalField, self).validators + [MoneyValidator(self.max_digits, self.decimal_places)] def contribute_to_class(self, cls, name): cls._meta.has_money_field = True - if not hasattr(self, '_currency_field'): + if not hasattr(self, "_currency_field"): self.add_currency_field(cls, name) super(MoneyField, self).contribute_to_class(cls, name) @@ -257,8 +251,10 @@ def add_currency_field(self, cls, name): currency_field = CurrencyField( price_field=self, max_length=self.currency_max_length, - default=self.default_currency, editable=False, - choices=self.currency_choices, null=self.default_currency is None + default=self.default_currency, + editable=False, + choices=self.currency_choices, + null=self.default_currency is None, ) currency_field.creation_counter = self.creation_counter - 1 currency_field_name = get_currency_field_name(name, self) @@ -277,12 +273,12 @@ def get_default(self): return super(MoneyField, self).get_default() def formfield(self, **kwargs): - defaults = {'form_class': forms.MoneyField} + defaults = {"form_class": forms.MoneyField} defaults.update(kwargs) - defaults['currency_choices'] = self.currency_choices - defaults['default_currency'] = self.default_currency + defaults["currency_choices"] = self.currency_choices + defaults["default_currency"] = self.default_currency if self.default is not None: - defaults['default_amount'] = self.default.amount + defaults["default_amount"] = self.default.amount return super(MoneyField, self).formfield(**defaults) def value_to_string(self, obj): @@ -296,15 +292,15 @@ def deconstruct(self): name, path, args, kwargs = super(MoneyField, self).deconstruct() if self.default is None: - del kwargs['default'] + del kwargs["default"] else: - kwargs['default'] = self.default.amount + kwargs["default"] = self.default.amount if self.default_currency != DEFAULT_CURRENCY: - kwargs['default_currency'] = str(self.default_currency) + kwargs["default_currency"] = str(self.default_currency) if self.currency_choices != CURRENCY_CHOICES: - kwargs['currency_choices'] = self.currency_choices + kwargs["currency_choices"] = self.currency_choices if self.currency_field_name: - kwargs['currency_field_name'] = self.currency_field_name + kwargs["currency_field_name"] = self.currency_field_name return name, path, args, kwargs @@ -313,9 +309,9 @@ def patch_managers(sender, **kwargs): Patches models managers. """ if sender._meta.proxy_for_model: - has_money_field = hasattr(sender._meta.proxy_for_model._meta, 'has_money_field') + has_money_field = hasattr(sender._meta.proxy_for_model._meta, "has_money_field") else: - has_money_field = hasattr(sender._meta, 'has_money_field') + has_money_field = hasattr(sender._meta, "has_money_field") if has_money_field: setup_managers(sender) @@ -325,10 +321,9 @@ def patch_managers(sender, **kwargs): class MoneyPatched(Money): - def __init__(self, *args, **kwargs): warn( "'djmoney.models.fields.MoneyPatched' is deprecated. Use 'djmoney.money.Money' instead", - PendingDeprecationWarning + PendingDeprecationWarning, ) super(MoneyPatched, self).__init__(*args, **kwargs) diff --git a/djmoney/models/managers.py b/djmoney/models/managers.py index 160e1ee7..8f72c20f 100644 --- a/djmoney/models/managers.py +++ b/djmoney/models/managers.py @@ -24,7 +24,7 @@ def _get_field(model, name): prev_field = None opts = model._meta for field_name in lookup_fields: - if field_name == 'pk': + if field_name == "pk": field_name = opts.pk.name try: field = opts.get_field(field_name) @@ -34,7 +34,7 @@ def _get_field(model, name): continue else: prev_field = field - if hasattr(field, 'get_path_info'): + if hasattr(field, "get_path_info"): # This field is a relation, update opts to follow the relation path_info = field.get_path_info() opts = path_info[-1].to_opts @@ -42,7 +42,7 @@ def _get_field(model, name): def is_in_lookup(name, value): - return hasattr(value, '__iter__') & (name.split(LOOKUP_SEP)[-1] == 'in') + return hasattr(value, "__iter__") & (name.split(LOOKUP_SEP)[-1] == "in") def _convert_in_lookup(model, field_name, options): @@ -63,16 +63,13 @@ def _convert_in_lookup(model, field_name, options): for value in options: if isinstance(value, MONEY_CLASSES): # amount__in=[Money(1, 'EUR'), Money(2, 'EUR')] - option = { - field.name: value.amount, - get_currency_field_name(field.name, field): value.currency - } + option = {field.name: value.amount, get_currency_field_name(field.name, field): value.currency} elif isinstance(value, F): # amount__in=[Money(1, 'EUR'), F('another_money')] target_field = _get_field(model, value.name) option = { field.name: value, - get_currency_field_name(field.name, field): F(get_currency_field_name(value.name, target_field)) + get_currency_field_name(field.name, field): F(get_currency_field_name(value.name, target_field)), } else: # amount__in=[1, 2, 3] @@ -154,9 +151,9 @@ def _expand_money_kwargs(model, args=(), kwargs=None, exclusions=()): target_field = _get_field(model, value.name) kwargs[currency_field_name] = F(get_currency_field_name(value.name, target_field)) if is_in_lookup(name, value): - args += (_convert_in_lookup(model, name, value), ) + args += (_convert_in_lookup(model, name, value),) del kwargs[name] - elif isinstance(field, CurrencyField) and 'defaults' in exclusions: + elif isinstance(field, CurrencyField) and "defaults" in exclusions: _handle_currency_field(model, name, kwargs) return args, kwargs @@ -167,8 +164,8 @@ def _handle_currency_field(model, name, kwargs): field = _get_field(model, name) money_field = field.price_field if money_field.default is not None and money_field.name not in kwargs: - kwargs['defaults'] = kwargs.get('defaults', {}) - kwargs['defaults'][money_field.name] = money_field.default.amount + kwargs["defaults"] = kwargs.get("defaults", {}) + kwargs["defaults"][money_field.name] = money_field.default.amount def _get_model(args, func): @@ -176,10 +173,10 @@ def _get_model(args, func): Returns the model class for given function. Note, that ``self`` is not available for proxy models. """ - if hasattr(func, '__self__'): + if hasattr(func, "__self__"): # Bound method model = func.__self__.model - elif hasattr(func, '__wrapped__'): + elif hasattr(func, "__wrapped__"): # Proxy model model = func.__wrapped__.__self__.model else: @@ -212,10 +209,8 @@ def wrapper(*args, **kwargs): return wrapper -RELEVANT_QUERYSET_METHODS = ('distinct', 'get', 'get_or_create', 'filter', 'exclude', 'update') -EXPAND_EXCLUSIONS = { - 'get_or_create': ('defaults', ) -} +RELEVANT_QUERYSET_METHODS = ("distinct", "get", "get_or_create", "filter", "exclude", "update") +EXPAND_EXCLUSIONS = {"get_or_create": ("defaults",)} def add_money_comprehension_to_queryset(qs): @@ -248,7 +243,6 @@ def money_manager(manager): # the passed in manager instance). This fails for reasons that # are tricky to get to the bottom of - Manager does funny things. class MoneyManager(manager.__class__): - def get_queryset(self, *args, **kwargs): queryset = super(MoneyManager, self).get_queryset(*args, **kwargs) return add_money_comprehension_to_queryset(queryset) diff --git a/djmoney/models/validators.py b/djmoney/models/validators.py index f5955a84..49d0d6e3 100644 --- a/djmoney/models/validators.py +++ b/djmoney/models/validators.py @@ -9,7 +9,6 @@ class BaseMoneyValidator(BaseValidator): - def get_limit_value(self, cleaned): if isinstance(self.limit_value, Money): if cleaned.currency.code != self.limit_value.currency.code: @@ -30,7 +29,7 @@ def __call__(self, value): return if isinstance(limit_value, (int, Decimal)): cleaned = cleaned.amount - params = {'limit_value': limit_value, 'show_value': cleaned, 'value': value} + params = {"limit_value": limit_value, "show_value": cleaned, "value": value} if self.compare(cleaned, limit_value): raise ValidationError(self.message, code=self.code, params=params) @@ -42,16 +41,16 @@ def __call__(self, value): class MinMoneyValidator(BaseMoneyValidator): - message = _('Ensure this value is greater than or equal to %(limit_value)s.') - code = 'min_value' + message = _("Ensure this value is greater than or equal to %(limit_value)s.") + code = "min_value" def compare(self, a, b): return a < b class MaxMoneyValidator(BaseMoneyValidator): - message = _('Ensure this value is less than or equal to %(limit_value)s.') - code = 'max_value' + message = _("Ensure this value is less than or equal to %(limit_value)s.") + code = "max_value" def compare(self, a, b): return a > b diff --git a/djmoney/money.py b/djmoney/money.py index 627f495d..00e8ff51 100644 --- a/djmoney/money.py +++ b/djmoney/money.py @@ -12,7 +12,7 @@ from .settings import DECIMAL_PLACES -__all__ = ['Money', 'Currency'] +__all__ = ["Money", "Currency"] @deconstructible @@ -20,6 +20,7 @@ class Money(DefaultMoney): """ Extends functionality of Money with Django-related features. """ + use_l10n = None def __init__(self, *args, **kwargs): @@ -55,18 +56,18 @@ def is_localized(self): return self.use_l10n def __unicode__(self): - kwargs = {'money': self, 'decimal_places': self.decimal_places} + kwargs = {"money": self, "decimal_places": self.decimal_places} if self.is_localized: locale = get_current_locale() if locale: - kwargs['locale'] = locale + kwargs["locale"] = locale return format_money(**kwargs) def __str__(self): value = self.__unicode__() if not isinstance(value, str): - value = value.encode('utf8') + value = value.encode("utf8") return value def __html__(self): @@ -85,18 +86,18 @@ def get_current_locale(): if locale.upper() in _FORMATTER.formatting_definitions: return locale - locale = ('%s_%s' % (locale, locale)).upper() + locale = ("%s_%s" % (locale, locale)).upper() if locale in _FORMATTER.formatting_definitions: return locale - return '' + return "" def maybe_convert(value, currency): """ Converts other Money instances to the local currency if `AUTO_CONVERT_MONEY` is set to True. """ - if getattr(settings, 'AUTO_CONVERT_MONEY', False): + if getattr(settings, "AUTO_CONVERT_MONEY", False): from .contrib.exchange.models import convert_money return convert_money(value, currency) diff --git a/djmoney/serializers.py b/djmoney/serializers.py index 0801f2e1..310e191e 100644 --- a/djmoney/serializers.py +++ b/djmoney/serializers.py @@ -21,21 +21,18 @@ def Deserializer(stream_or_string, **options): # noqa """ # Local imports to allow using modified versions of `_get_model` # It could be patched in runtime via `unittest.mock.patch` for example - from django.core.serializers.python import ( - Deserializer as PythonDeserializer, - _get_model, - ) + from django.core.serializers.python import Deserializer as PythonDeserializer, _get_model - ignore = options.pop('ignorenonexistent', False) + ignore = options.pop("ignorenonexistent", False) if not isinstance(stream_or_string, (bytes, six.string_types)): stream_or_string = stream_or_string.read() if isinstance(stream_or_string, bytes): - stream_or_string = stream_or_string.decode('utf-8') + stream_or_string = stream_or_string.decode("utf-8") try: for obj in json.loads(stream_or_string): try: - Model = _get_model(obj['model']) + Model = _get_model(obj["model"]) except DeserializationError: if ignore: continue @@ -44,16 +41,16 @@ def Deserializer(stream_or_string, **options): # noqa money_fields = {} fields = {} field_names = {field.name for field in Model._meta.get_fields()} - for (field_name, field_value) in six.iteritems(obj['fields']): + for (field_name, field_value) in six.iteritems(obj["fields"]): if ignore and field_name not in field_names: # skip fields no longer on model continue field = Model._meta.get_field(field_name) if isinstance(field, MoneyField) and field_value is not None: - money_fields[field_name] = Money(field_value, obj['fields'][get_currency_field_name(field_name)]) + money_fields[field_name] = Money(field_value, obj["fields"][get_currency_field_name(field_name)]) else: fields[field_name] = field_value - obj['fields'] = fields + obj["fields"] = fields for inner_obj in PythonDeserializer([obj], **options): for field, value in money_fields.items(): diff --git a/djmoney/settings.py b/djmoney/settings.py index 92ebc5f9..43395d75 100644 --- a/djmoney/settings.py +++ b/djmoney/settings.py @@ -8,28 +8,27 @@ # The default currency, you can define this in your project's settings module # This has to be a currency object imported from moneyed -DEFAULT_CURRENCY = getattr(settings, 'DEFAULT_CURRENCY', DEFAULT_CURRENCY) +DEFAULT_CURRENCY = getattr(settings, "DEFAULT_CURRENCY", DEFAULT_CURRENCY) # The default currency choices, you can define this in your project's # settings module -PROJECT_CURRENCIES = getattr(settings, 'CURRENCIES', None) -CURRENCY_CHOICES = getattr(settings, 'CURRENCY_CHOICES', None) +PROJECT_CURRENCIES = getattr(settings, "CURRENCIES", None) +CURRENCY_CHOICES = getattr(settings, "CURRENCY_CHOICES", None) if CURRENCY_CHOICES is None: if PROJECT_CURRENCIES: CURRENCY_CHOICES = [(code, CURRENCIES[code].name) for code in PROJECT_CURRENCIES] else: - CURRENCY_CHOICES = [(c.code, c.name) for i, c in CURRENCIES.items() if - c.code != DEFAULT_CURRENCY_CODE] + CURRENCY_CHOICES = [(c.code, c.name) for i, c in CURRENCIES.items() if c.code != DEFAULT_CURRENCY_CODE] CURRENCY_CHOICES.sort(key=operator.itemgetter(1, 0)) -DECIMAL_PLACES = getattr(settings, 'CURRENCY_DECIMAL_PLACES', 2) - -OPEN_EXCHANGE_RATES_URL = getattr(settings, 'OPEN_EXCHANGE_RATES_URL', 'https://openexchangerates.org/api/latest.json') -OPEN_EXCHANGE_RATES_APP_ID = getattr(settings, 'OPEN_EXCHANGE_RATES_APP_ID', None) -FIXER_URL = getattr(settings, 'FIXER_URL', 'http://data.fixer.io/api/latest') -FIXER_ACCESS_KEY = getattr(settings, 'FIXER_ACCESS_KEY', None) -BASE_CURRENCY = getattr(settings, 'BASE_CURRENCY', 'USD') -EXCHANGE_BACKEND = getattr(settings, 'EXCHANGE_BACKEND', 'djmoney.contrib.exchange.backends.OpenExchangeRatesBackend') -RATES_CACHE_TIMEOUT = getattr(settings, 'RATES_CACHE_TIMEOUT', 600) +DECIMAL_PLACES = getattr(settings, "CURRENCY_DECIMAL_PLACES", 2) + +OPEN_EXCHANGE_RATES_URL = getattr(settings, "OPEN_EXCHANGE_RATES_URL", "https://openexchangerates.org/api/latest.json") +OPEN_EXCHANGE_RATES_APP_ID = getattr(settings, "OPEN_EXCHANGE_RATES_APP_ID", None) +FIXER_URL = getattr(settings, "FIXER_URL", "http://data.fixer.io/api/latest") +FIXER_ACCESS_KEY = getattr(settings, "FIXER_ACCESS_KEY", None) +BASE_CURRENCY = getattr(settings, "BASE_CURRENCY", "USD") +EXCHANGE_BACKEND = getattr(settings, "EXCHANGE_BACKEND", "djmoney.contrib.exchange.backends.OpenExchangeRatesBackend") +RATES_CACHE_TIMEOUT = getattr(settings, "RATES_CACHE_TIMEOUT", 600) diff --git a/djmoney/templatetags/djmoney.py b/djmoney/templatetags/djmoney.py index 9b047d2e..fa0de133 100644 --- a/djmoney/templatetags/djmoney.py +++ b/djmoney/templatetags/djmoney.py @@ -14,9 +14,8 @@ class MoneyLocalizeNode(template.Node): - def __repr__(self): - return '' % (self.money.amount, self.money.currency) + return "" % (self.money.amount, self.money.currency) def __init__(self, money=None, amount=None, currency=None, use_l10n=None, var_name=None): if money and (amount or currency): @@ -39,29 +38,31 @@ def handle_token(cls, parser, token): # GET variable var_name if len(tokens) > 3: - if tokens[-2] == 'as': + if tokens[-2] == "as": var_name = parser.compile_filter(tokens[-1]) # remove the already used data tokens = tokens[0:-2] # GET variable use_l10n - if tokens[-1].lower() in ('on', 'off'): - use_l10n = tokens[-1].lower() == 'on' + if tokens[-1].lower() in ("on", "off"): + use_l10n = tokens[-1].lower() == "on" # remove the already used data tokens.pop(-1) # GET variable money if len(tokens) == 2: - return cls(money=parser.compile_filter(tokens[1]), - var_name=var_name, use_l10n=use_l10n) + return cls(money=parser.compile_filter(tokens[1]), var_name=var_name, use_l10n=use_l10n) # GET variable amount and currency if len(tokens) == 3: - return cls(amount=parser.compile_filter(tokens[1]), - currency=parser.compile_filter(tokens[2]), - var_name=var_name, use_l10n=use_l10n) + return cls( + amount=parser.compile_filter(tokens[1]), + currency=parser.compile_filter(tokens[2]), + var_name=var_name, + use_l10n=use_l10n, + ) - raise TemplateSyntaxError('Wrong number of input data to the tag.') + raise TemplateSyntaxError("Wrong number of input data to the tag.") def render(self, context): @@ -76,7 +77,7 @@ def render(self, context): elif amount is not None and currency is not None: money = Money(Decimal(str(amount)), str(currency)) else: - raise TemplateSyntaxError('You must define both variables: amount and currency.') + raise TemplateSyntaxError("You must define both variables: amount and currency.") money.use_l10n = self.use_l10n @@ -85,7 +86,7 @@ def render(self, context): # as context[self.var_name.token] = money - return '' + return "" @register.tag diff --git a/djmoney/utils.py b/djmoney/utils.py index 23f3e17e..47bbf0fa 100644 --- a/djmoney/utils.py +++ b/djmoney/utils.py @@ -10,9 +10,9 @@ def get_currency_field_name(name, field=None): - if field and getattr(field, 'currency_field_name', None): + if field and getattr(field, "currency_field_name", None): return field.currency_field_name - return '%s_currency' % name + return "%s_currency" % name def get_amount(value): diff --git a/setup.cfg b/setup.cfg index 627a7266..a722ed23 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ universal = 1 [isort] -line_length = 79 +line_length = 120 combine_as_imports = true known_django = django known_first_party = tests, djmoney, moneyed @@ -14,13 +14,13 @@ multi_line_output = 3 lines_after_imports = 2 [flake8] -ignore = E226,E302,E41 -max-line-length = 160 +ignore = E226,E302,E41,W503 +max-line-length = 120 max-complexity = 10 exclude = migrations [pep8] ignore = E226,E302,E41,E402 -max-line-length = 160 +max-line-length = 120 exclude = kolibri/*/migrations/* diff --git a/tests/conftest.py b/tests/conftest.py index 1da5de74..809d030d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,29 +1,25 @@ # -*- coding: utf-8 -*- import pytest -from djmoney.contrib.exchange.models import ( - ExchangeBackend, - Rate, - get_default_backend_name, -) +from djmoney.contrib.exchange.models import ExchangeBackend, Rate, get_default_backend_name from djmoney.money import Money from tests.testapp.models import InheritorModel, ModelWithDefaultAsInt @pytest.fixture def m2m_object(): - return ModelWithDefaultAsInt.objects.create(money=Money(100, 'USD')) + return ModelWithDefaultAsInt.objects.create(money=Money(100, "USD")) @pytest.fixture() def backend(): - return ExchangeBackend.objects.create(name=get_default_backend_name(), base_currency='USD') + return ExchangeBackend.objects.create(name=get_default_backend_name(), base_currency="USD") @pytest.fixture() def autoconversion(backend, settings): settings.AUTO_CONVERT_MONEY = True - Rate.objects.create(currency='EUR', value='0.88', backend=backend) + Rate.objects.create(currency="EUR", value="0.88", backend=backend) @pytest.fixture @@ -33,7 +29,7 @@ def concrete_instance(m2m_object): return instance -pytest_plugins = 'pytester' +pytest_plugins = "pytester" @pytest.fixture @@ -41,8 +37,10 @@ def coveragerc(request, testdir): """ Generates .coveragerc to be used to combine test results from different subprocesses. """ - data_file = '%s/.coverage.%s' % (request.config.rootdir, request.node.name) - return testdir.makefile('.ini', coveragerc=''' + data_file = "%s/.coverage.%s" % (request.config.rootdir, request.node.name) + return testdir.makefile( + ".ini", + coveragerc=""" [run] branch = true data_file = %s @@ -51,4 +49,6 @@ def coveragerc(request, testdir): show_missing = true precision = 2 exclude_lines = raise NotImplementedError - ''' % data_file) + """ + % data_file, + ) diff --git a/tests/contrib/exchange/conftest.py b/tests/contrib/exchange/conftest.py index 67c40304..5a555b18 100644 --- a/tests/contrib/exchange/conftest.py +++ b/tests/contrib/exchange/conftest.py @@ -6,10 +6,7 @@ import pytest -from djmoney.contrib.exchange.backends import ( - FixerBackend, - OpenExchangeRatesBackend, -) +from djmoney.contrib.exchange.backends import FixerBackend, OpenExchangeRatesBackend from djmoney.contrib.exchange.backends.base import BaseExchangeBackend from djmoney.contrib.exchange.models import ExchangeBackend, Rate from tests._compat import Mock, patch @@ -18,39 +15,41 @@ pytestmarks = pytest.mark.django_db -OPEN_EXCHANGE_RATES_RESPONSE = '''{ +OPEN_EXCHANGE_RATES_RESPONSE = """{ "disclaimer": "Usage subject to terms: https://openexchangerates.org/terms", "license": "https://openexchangerates.org/license", "timestamp": 1522749600, "base": "USD", "rates": {"EUR": 0.812511, "NOK": 7.847505, "SEK": 8.379037} -}''' -OPEN_EXCHANGE_RATES_EXPECTED = json.loads(OPEN_EXCHANGE_RATES_RESPONSE, parse_float=Decimal)['rates'] +}""" +OPEN_EXCHANGE_RATES_EXPECTED = json.loads(OPEN_EXCHANGE_RATES_RESPONSE, parse_float=Decimal)["rates"] -FIXER_RESPONSE = '''{ +FIXER_RESPONSE = """{ "success":true, "timestamp":1522788248, "base":"EUR", "date":"2018-04-03", "rates":{"USD":1.227439,"NOK":9.624334,"SEK":10.300293} -}''' -FIXER_EXPECTED = json.loads(FIXER_RESPONSE, parse_float=Decimal)['rates'] +}""" +FIXER_EXPECTED = json.loads(FIXER_RESPONSE, parse_float=Decimal)["rates"] @contextmanager def mock_backend(value): response = Mock() response.read.return_value = value - with patch('djmoney.contrib.exchange.backends.base.urlopen', return_value=response): + with patch("djmoney.contrib.exchange.backends.base.urlopen", return_value=response): yield class ExchangeTest: - - @pytest.fixture(autouse=True, params=( - (OpenExchangeRatesBackend, OPEN_EXCHANGE_RATES_RESPONSE, OPEN_EXCHANGE_RATES_EXPECTED), - (FixerBackend, FIXER_RESPONSE, FIXER_EXPECTED) - )) + @pytest.fixture( + autouse=True, + params=( + (OpenExchangeRatesBackend, OPEN_EXCHANGE_RATES_RESPONSE, OPEN_EXCHANGE_RATES_EXPECTED), + (FixerBackend, FIXER_RESPONSE, FIXER_EXPECTED), + ), + ) def setup(self, request): klass, response_value, expected = request.param self.backend = klass() @@ -66,17 +65,17 @@ def assert_rates(self): class FixedOneBackend(BaseExchangeBackend): - name = 'first' + name = "first" def get_rates(self, **params): - return {'EUR': 1} + return {"EUR": 1} class FixedTwoBackend(BaseExchangeBackend): - name = 'second' + name = "second" def get_rates(self, **params): - return {'EUR': 2} + return {"EUR": 2} @pytest.fixture @@ -87,7 +86,7 @@ def two_backends_data(): @pytest.fixture def simple_rates(backend): - Rate.objects.create(currency='EUR', value=2, backend=backend) + Rate.objects.create(currency="EUR", value=2, backend=backend) @pytest.fixture diff --git a/tests/contrib/exchange/test_admin.py b/tests/contrib/exchange/test_admin.py index a7b6cb19..dc4b736c 100644 --- a/tests/contrib/exchange/test_admin.py +++ b/tests/contrib/exchange/test_admin.py @@ -10,5 +10,5 @@ def test_last_update(backend): - rate = Rate(currency='NOK', value=5, backend=backend) + rate = Rate(currency="NOK", value=5, backend=backend) assert RateAdmin(Rate, site).last_update(rate) == backend.last_update diff --git a/tests/contrib/exchange/test_backends.py b/tests/contrib/exchange/test_backends.py index 27ea4e72..90ecfb8e 100644 --- a/tests/contrib/exchange/test_backends.py +++ b/tests/contrib/exchange/test_backends.py @@ -4,10 +4,7 @@ import pytest -from djmoney.contrib.exchange.backends import ( - FixerBackend, - OpenExchangeRatesBackend, -) +from djmoney.contrib.exchange.backends import FixerBackend, OpenExchangeRatesBackend from djmoney.contrib.exchange.models import ExchangeBackend, Rate, get_rate from .conftest import ExchangeTest, FixedOneBackend, FixedTwoBackend @@ -17,7 +14,6 @@ class TestBackends(ExchangeTest): - def test_get_rates(self): assert self.backend.get_rates() == self.expected @@ -34,21 +30,21 @@ def test_second_update_rates(self): assert last_update < backend.last_update -@pytest.mark.parametrize('backend', (FixerBackend, OpenExchangeRatesBackend)) +@pytest.mark.parametrize("backend", (FixerBackend, OpenExchangeRatesBackend)) def test_missing_settings(backend): with pytest.raises(ImproperlyConfigured): backend(access_key=None) -@pytest.mark.usefixtures('two_backends_data') +@pytest.mark.usefixtures("two_backends_data") def test_two_backends(): """ Two different backends should not interfere with each other. """ for backend in (FixedOneBackend, FixedTwoBackend): assert Rate.objects.filter(backend__name=backend.name).count() == 1 - assert get_rate('USD', 'EUR', backend=FixedOneBackend.name) == 1 - assert get_rate('USD', 'EUR', backend=FixedTwoBackend.name) == 2 + assert get_rate("USD", "EUR", backend=FixedOneBackend.name) == 1 + assert get_rate("USD", "EUR", backend=FixedTwoBackend.name) == 2 @pytest.fixture diff --git a/tests/contrib/exchange/test_commands.py b/tests/contrib/exchange/test_commands.py index 09430f11..4cc9d770 100644 --- a/tests/contrib/exchange/test_commands.py +++ b/tests/contrib/exchange/test_commands.py @@ -9,29 +9,27 @@ pytestmark = pytest.mark.django_db -BACKEND_PATH = FixedOneBackend.__module__ + '.' + FixedOneBackend.__name__ +BACKEND_PATH = FixedOneBackend.__module__ + "." + FixedOneBackend.__name__ class TestCommand(ExchangeTest): - def test_update_rates(self): - call_command('update_rates') + call_command("update_rates") self.assert_rates() def test_custom_backend(): - call_command('update_rates', backend=BACKEND_PATH) - assert Rate.objects.filter(currency='EUR', value=1).exists() + call_command("update_rates", backend=BACKEND_PATH) + assert Rate.objects.filter(currency="EUR", value=1).exists() -@pytest.mark.usefixtures('two_backends_data') +@pytest.mark.usefixtures("two_backends_data") class TestClearRates: - def test_for_specific_backend(self): - call_command('clear_rates', backend=BACKEND_PATH) + call_command("clear_rates", backend=BACKEND_PATH) assert not Rate.objects.filter(backend=FixedOneBackend.name).exists() assert Rate.objects.filter(backend=FixedTwoBackend.name).exists() def test_for_all(self): - call_command('clear_rates', all=True) + call_command("clear_rates", all=True) assert not Rate.objects.exists() diff --git a/tests/contrib/exchange/test_model.py b/tests/contrib/exchange/test_model.py index 2befd178..0e03fb27 100644 --- a/tests/contrib/exchange/test_model.py +++ b/tests/contrib/exchange/test_model.py @@ -14,47 +14,45 @@ pytestmark = pytest.mark.django_db -@pytest.mark.parametrize('source, target, expected, queries', ( - ('USD', 'USD', 1, 0), - ('USD', 'EUR', 2, 1), - ('EUR', 'USD', Decimal('0.5'), 1), - (Currency('USD'), 'USD', 1, 0), - ('USD', Currency('USD'), 1, 0), -)) -@pytest.mark.usefixtures('simple_rates') +@pytest.mark.parametrize( + "source, target, expected, queries", + ( + ("USD", "USD", 1, 0), + ("USD", "EUR", 2, 1), + ("EUR", "USD", Decimal("0.5"), 1), + (Currency("USD"), "USD", 1, 0), + ("USD", Currency("USD"), 1, 0), + ), +) +@pytest.mark.usefixtures("simple_rates") def test_get_rate(source, target, expected, django_assert_num_queries, queries): with django_assert_num_queries(queries): assert get_rate(source, target) == expected -@pytest.mark.parametrize('source, target, expected', ( - ('NOK', 'SEK', Decimal('1.067732610555839085161462146')), - ('SEK', 'NOK', Decimal('0.9365640705489186883886537319')), -)) -@pytest.mark.usefixtures('default_openexchange_rates') +@pytest.mark.parametrize( + "source, target, expected", + ( + ("NOK", "SEK", Decimal("1.067732610555839085161462146")), + ("SEK", "NOK", Decimal("0.9365640705489186883886537319")), + ), +) +@pytest.mark.usefixtures("default_openexchange_rates") def test_rates_via_base(source, target, expected, django_assert_num_queries): with django_assert_num_queries(1): assert get_rate(source, target) == expected -@pytest.mark.parametrize('source, target', ( - ('NOK', 'ZAR'), - ('ZAR', 'NOK'), - ('USD', 'ZAR'), - ('ZAR', 'USD'), -)) -@pytest.mark.usefixtures('default_openexchange_rates') +@pytest.mark.parametrize("source, target", (("NOK", "ZAR"), ("ZAR", "NOK"), ("USD", "ZAR"), ("ZAR", "USD"))) +@pytest.mark.usefixtures("default_openexchange_rates") def test_unknown_currency_with_partially_exiting_currencies(source, target): - with pytest.raises(MissingRate, match='Rate %s \\-\\> %s does not exist' % (source, target)): + with pytest.raises(MissingRate, match="Rate %s \\-\\> %s does not exist" % (source, target)): get_rate(source, target) -@pytest.mark.parametrize('source, target', ( - ('USD', 'EUR'), - ('SEK', 'ZWL') -)) +@pytest.mark.parametrize("source, target", (("USD", "EUR"), ("SEK", "ZWL"))) def test_unknown_currency(source, target): - with pytest.raises(MissingRate, match='Rate %s \\-\\> %s does not exist' % (source, target)): + with pytest.raises(MissingRate, match="Rate %s \\-\\> %s does not exist" % (source, target)): get_rate(source, target) @@ -62,19 +60,19 @@ def test_string_representation(backend): assert str(backend) == backend.name -@pytest.mark.usefixtures('simple_rates') +@pytest.mark.usefixtures("simple_rates") def test_cache(): - with patch('djmoney.contrib.exchange.models._get_rate', wraps=_get_rate) as original: - assert get_rate('USD', 'USD') == 1 + with patch("djmoney.contrib.exchange.models._get_rate", wraps=_get_rate) as original: + assert get_rate("USD", "USD") == 1 assert original.call_count == 1 - assert get_rate('USD', 'USD') == 1 + assert get_rate("USD", "USD") == 1 assert original.call_count == 1 def test_bad_configuration(settings): - settings.INSTALLED_APPS.remove('djmoney.contrib.exchange') + settings.INSTALLED_APPS.remove("djmoney.contrib.exchange") with pytest.raises(ImproperlyConfigured): - convert_money(Money(1, 'USD'), 'EUR') + convert_money(Money(1, "USD"), "EUR") def test_without_installed_exchange(testdir): @@ -82,8 +80,9 @@ def test_without_installed_exchange(testdir): If there is no 'djmoney.contrib.exchange' in INSTALLED_APPS importing `Money` should not cause a RuntimeError. Details: GH-385. """ - testdir.mkpydir('money_app') - testdir.makepyfile(app_settings=''' + testdir.mkpydir("money_app") + testdir.makepyfile( + app_settings=""" DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -92,8 +91,11 @@ def test_without_installed_exchange(testdir): } INSTALLED_APPS = ['djmoney'] SECRET_KEY = 'foobar' - ''') - result = testdir.runpython_c(dedent(''' + """ + ) + result = testdir.runpython_c( + dedent( + """ import os os.environ['DJANGO_SETTINGS_MODULE'] = 'app_settings' from django import setup @@ -101,5 +103,7 @@ def test_without_installed_exchange(testdir): setup() from djmoney.money import Money print(Money(1, 'USD')) - ''')) - result.stdout.fnmatch_lines(['US$1.00']) + """ + ) + ) + result.stdout.fnmatch_lines(["US$1.00"]) diff --git a/tests/contrib/test_django_rest_framework.py b/tests/contrib/test_django_rest_framework.py index c679101f..3eed9ff4 100644 --- a/tests/contrib/test_django_rest_framework.py +++ b/tests/contrib/test_django_rest_framework.py @@ -6,25 +6,19 @@ import six from djmoney.money import Money -from ..testapp.models import ( - ModelWithVanillaMoneyField, - NullMoneyFieldModel, - ValidatedMoneyModel, -) +from ..testapp.models import ModelWithVanillaMoneyField, NullMoneyFieldModel, ValidatedMoneyModel pytestmark = pytest.mark.django_db -serializers = pytest.importorskip('rest_framework.serializers') -fields = pytest.importorskip('rest_framework.fields') +serializers = pytest.importorskip("rest_framework.serializers") +fields = pytest.importorskip("rest_framework.fields") class TestMoneyField: - - def get_serializer(self, model_class, field_name=None, instance=None, data=fields.empty, fields_='__all__', - field_kwargs=None): - + def get_serializer( + self, model_class, field_name=None, instance=None, data=fields.empty, fields_="__all__", field_kwargs=None + ): class MetaSerializer(serializers.SerializerMetaclass): - def __new__(cls, name, bases, attrs): from djmoney.contrib.django_rest_framework import MoneyField @@ -41,44 +35,42 @@ class Meta: return Serializer(instance=instance, data=data) @pytest.mark.parametrize( - 'model_class, create_kwargs, expected', ( - (NullMoneyFieldModel, {'field': None}, {'field': None, 'field_currency': 'USD'}), - ( - NullMoneyFieldModel, - {'field': Money(10, 'USD')}, - {'field': '10.00', 'field_currency': 'USD'} - ), + "model_class, create_kwargs, expected", + ( + (NullMoneyFieldModel, {"field": None}, {"field": None, "field_currency": "USD"}), + (NullMoneyFieldModel, {"field": Money(10, "USD")}, {"field": "10.00", "field_currency": "USD"}), ( ModelWithVanillaMoneyField, - {'money': Money(10, 'USD')}, + {"money": Money(10, "USD")}, { - 'integer': 0, - 'money': '10.00', - 'money_currency': 'USD', - 'second_money': '0.00', - 'second_money_currency': 'EUR', - } + "integer": 0, + "money": "10.00", + "money_currency": "USD", + "second_money": "0.00", + "second_money_currency": "EUR", + }, ), - ) + ), ) def test_to_representation(self, model_class, create_kwargs, expected): instance = model_class.objects.create(**create_kwargs) - expected['id'] = instance.id + expected["id"] = instance.id serializer = self.get_serializer(model_class, instance=instance) assert serializer.data == expected @pytest.mark.parametrize( - 'model_class, field, field_kwargs, value, expected', ( - (NullMoneyFieldModel, 'field', None, None, None), - (NullMoneyFieldModel, 'field', {'default_currency': 'EUR', 'allow_null': True}, None, None), - (NullMoneyFieldModel, 'field', None, Money(10, 'USD'), Money(10, 'USD')), - (NullMoneyFieldModel, 'field', {'default_currency': 'EUR'}, Money(10, 'USD'), Money(10, 'USD')), - (NullMoneyFieldModel, 'field', {'default_currency': 'EUR'}, 10, Money(10, 'EUR')), - (ModelWithVanillaMoneyField, 'money', None, Money(10, 'USD'), Money(10, 'USD')), - (ModelWithVanillaMoneyField, 'money', {'default_currency': 'EUR'}, Money(10, 'USD'), Money(10, 'USD')), - (ModelWithVanillaMoneyField, 'money', None, 10, Money(10, 'XYZ')), - (ModelWithVanillaMoneyField, 'money', {'default_currency': 'EUR'}, 10, Money(10, 'EUR')), - ) + "model_class, field, field_kwargs, value, expected", + ( + (NullMoneyFieldModel, "field", None, None, None), + (NullMoneyFieldModel, "field", {"default_currency": "EUR", "allow_null": True}, None, None), + (NullMoneyFieldModel, "field", None, Money(10, "USD"), Money(10, "USD")), + (NullMoneyFieldModel, "field", {"default_currency": "EUR"}, Money(10, "USD"), Money(10, "USD")), + (NullMoneyFieldModel, "field", {"default_currency": "EUR"}, 10, Money(10, "EUR")), + (ModelWithVanillaMoneyField, "money", None, Money(10, "USD"), Money(10, "USD")), + (ModelWithVanillaMoneyField, "money", {"default_currency": "EUR"}, Money(10, "USD"), Money(10, "USD")), + (ModelWithVanillaMoneyField, "money", None, 10, Money(10, "XYZ")), + (ModelWithVanillaMoneyField, "money", {"default_currency": "EUR"}, 10, Money(10, "EUR")), + ), ) def test_to_internal_value(self, model_class, field, field_kwargs, value, expected): serializer = self.get_serializer(model_class, field_name=field, data={field: value}, field_kwargs=field_kwargs) @@ -87,65 +79,71 @@ def test_to_internal_value(self, model_class, field, field_kwargs, value, expect assert getattr(instance, field) == expected def test_invalid_value(self): - serializer = self.get_serializer(ModelWithVanillaMoneyField, data={'money': None}) + serializer = self.get_serializer(ModelWithVanillaMoneyField, data={"money": None}) assert not serializer.is_valid() - error_text = 'This field may not be null.' - assert serializer.errors == {'money': [error_text]} + error_text = "This field may not be null." + assert serializer.errors == {"money": [error_text]} @pytest.mark.parametrize( - 'body, field_kwargs, expected', ( - ({'field': '10', 'field_currency': 'EUR'}, None, Money(10, 'EUR')), - ({'field': '10'}, {'default_currency': 'EUR'}, Money(10, 'EUR')), - ({'field': '12.20', 'field_currency': 'GBP'}, None, Money(12.20, 'GBP')), - ({'field': '15.15', 'field_currency': 'USD'}, None, Money(15.15, 'USD')), - ({'field': None, 'field_currency': None}, None, None), - ({'field': None, 'field_currency': None}, {'default_currency': 'EUR'}, None), - ({'field': '16', 'field_currency': None}, None, Decimal('16.00')), - ({'field': '16', 'field_currency': None}, {'default_currency': 'EUR'}, Decimal('16.00')), - ({'field': None, 'field_currency': 'USD'}, None, None), - ({'field': None, 'field_currency': 'USD'}, {'default_currency': 'EUR'}, None), + "body, field_kwargs, expected", + ( + ({"field": "10", "field_currency": "EUR"}, None, Money(10, "EUR")), + ({"field": "10"}, {"default_currency": "EUR"}, Money(10, "EUR")), + ({"field": "12.20", "field_currency": "GBP"}, None, Money(12.20, "GBP")), + ({"field": "15.15", "field_currency": "USD"}, None, Money(15.15, "USD")), + ({"field": None, "field_currency": None}, None, None), + ({"field": None, "field_currency": None}, {"default_currency": "EUR"}, None), + ({"field": "16", "field_currency": None}, None, Decimal("16.00")), + ({"field": "16", "field_currency": None}, {"default_currency": "EUR"}, Decimal("16.00")), + ({"field": None, "field_currency": "USD"}, None, None), + ({"field": None, "field_currency": "USD"}, {"default_currency": "EUR"}, None), ), ) def test_post_put_values(self, body, field_kwargs, expected): if field_kwargs is not None: - field_kwargs['allow_null'] = True - serializer = self.get_serializer(NullMoneyFieldModel, data=body, field_name='field', field_kwargs=field_kwargs) + field_kwargs["allow_null"] = True + serializer = self.get_serializer(NullMoneyFieldModel, data=body, field_name="field", field_kwargs=field_kwargs) serializer.is_valid() - assert serializer.validated_data['field'] == expected + assert serializer.validated_data["field"] == expected def test_serializer_with_fields(self): - serializer = self.get_serializer(ModelWithVanillaMoneyField, data={'money': '10.00'}, fields_=('money', )) + serializer = self.get_serializer(ModelWithVanillaMoneyField, data={"money": "10.00"}, fields_=("money",)) serializer.is_valid(True) - assert serializer.data == {'money': '10.00'} - - @pytest.mark.parametrize("value, error", ( - (Money(50, 'EUR'), u'Ensure this value is greater than or equal to 100.00 €.'), - (Money(1500, 'EUR'), u'Ensure this value is less than or equal to 1,000.00 €.'), - (Money(40, 'USD'), 'Ensure this value is greater than or equal to $50.00.'), - (Money(600, 'USD'), 'Ensure this value is less than or equal to $500.00.'), - (Money(400, 'NOK'), 'Ensure this value is greater than or equal to 500.00 Nkr.'), - (Money(950, 'NOK'), 'Ensure this value is less than or equal to 900.00 Nkr.'), - (Money(5, 'SEK'), 'Ensure this value is greater than or equal to 10.'), - (Money(1600, 'SEK'), 'Ensure this value is less than or equal to 1500.'), - )) + assert serializer.data == {"money": "10.00"} + + @pytest.mark.parametrize( + "value, error", + ( + (Money(50, "EUR"), u"Ensure this value is greater than or equal to 100.00 €."), + (Money(1500, "EUR"), u"Ensure this value is less than or equal to 1,000.00 €."), + (Money(40, "USD"), "Ensure this value is greater than or equal to $50.00."), + (Money(600, "USD"), "Ensure this value is less than or equal to $500.00."), + (Money(400, "NOK"), "Ensure this value is greater than or equal to 500.00 Nkr."), + (Money(950, "NOK"), "Ensure this value is less than or equal to 900.00 Nkr."), + (Money(5, "SEK"), "Ensure this value is greater than or equal to 10."), + (Money(1600, "SEK"), "Ensure this value is less than or equal to 1500."), + ), + ) def test_model_validators(self, value, error): serializer = self.get_serializer( - ValidatedMoneyModel, - data={"money": value.amount, "money_currency": value.currency.code} + ValidatedMoneyModel, data={"money": value.amount, "money_currency": value.currency.code} ) assert not serializer.is_valid() assert serializer.errors["money"][0] == error - @pytest.mark.parametrize("value, error", ( - (Money(50, "EUR"), "Ensure this value is greater than or equal to 100."), - (Money(1500, "EUR"), "Ensure this value is less than or equal to 1000."), - )) + @pytest.mark.parametrize( + "value, error", + ( + (Money(50, "EUR"), "Ensure this value is greater than or equal to 100."), + (Money(1500, "EUR"), "Ensure this value is less than or equal to 1000."), + ), + ) def test_boundary_values(self, value, error): serializer = self.get_serializer( NullMoneyFieldModel, data={"field": value.amount, "field_currency": value.currency.code}, - field_name='field', - field_kwargs={"min_value": 100, "max_value": 1000} + field_name="field", + field_kwargs={"min_value": 100, "max_value": 1000}, ) assert not serializer.is_valid() assert serializer.errors["field"][0] == error diff --git a/tests/migrations/helpers.py b/tests/migrations/helpers.py index c30e0fdc..8af8674d 100644 --- a/tests/migrations/helpers.py +++ b/tests/migrations/helpers.py @@ -7,7 +7,7 @@ from django import VERSION -MIGRATION_NAME = 'test' +MIGRATION_NAME = "test" def makemigrations(): @@ -16,18 +16,18 @@ def makemigrations(): from django.db.migrations import questioner # We should answer yes for all migrations questioner questions - questioner.input = lambda x: 'y' + questioner.input = lambda x: "y" - os.system('find . -name \*.pyc -delete') + os.system("find . -name \*.pyc -delete") if VERSION >= (1, 11): - call_command('makemigrations', 'money_app', name=MIGRATION_NAME) + call_command("makemigrations", "money_app", name=MIGRATION_NAME) else: # In Django 1.8 first argument name clashes with command option. - Command().execute('money_app', name=MIGRATION_NAME, verbosity=1) + Command().execute("money_app", name=MIGRATION_NAME, verbosity=1) def get_migration(name): - return __import__('money_app.migrations.%s_%s' % (name, MIGRATION_NAME), fromlist=['Migration']).Migration + return __import__("money_app.migrations.%s_%s" % (name, MIGRATION_NAME), fromlist=["Migration"]).Migration def get_operations(migration_name): @@ -37,4 +37,4 @@ def get_operations(migration_name): def migrate(): from django.core.management import call_command - call_command('migrate', 'money_app') + call_command("migrate", "money_app") diff --git a/tests/migrations/test_migrations.py b/tests/migrations/test_migrations.py index de39933f..ae297333 100644 --- a/tests/migrations/test_migrations.py +++ b/tests/migrations/test_migrations.py @@ -12,13 +12,10 @@ from .helpers import get_operations -@pytest.mark.usefixtures('coveragerc') +@pytest.mark.usefixtures("coveragerc") class TestMigrationFramework: - installed_apps = ['djmoney', 'money_app'] - migration_output = [ - '*Applying money_app.0001_test... OK*', - '*Applying money_app.0002_test... OK*', - ] + installed_apps = ["djmoney", "money_app"] + migration_output = ["*Applying money_app.0001_test... OK*", "*Applying money_app.0002_test... OK*"] @pytest.fixture(autouse=True) def setup(self, testdir): @@ -26,8 +23,9 @@ def setup(self, testdir): Creates application module, helpers and settings file with basic config. """ self.testdir = testdir - self.project_root = testdir.mkpydir('money_app') - testdir.makepyfile(app_settings=''' + self.project_root = testdir.mkpydir("money_app") + testdir.makepyfile( + app_settings=""" DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', @@ -36,15 +34,17 @@ def setup(self, testdir): } INSTALLED_APPS = %s SECRET_KEY = 'foobar' - ''' % str(self.installed_apps)) - self.project_root.join('migrations/__init__.py').ensure() + """ + % str(self.installed_apps) + ) + self.project_root.join("migrations/__init__.py").ensure() testdir.syspathinsert() def make_models(self, content): """ Creates models.py file. """ - fd = self.project_root.join('models.py') + fd = self.project_root.join("models.py") fd.write(dedent(content)) def make_migration(self, **fields): @@ -52,50 +52,58 @@ def make_migration(self, **fields): Creates a model with provided fields and creates a migration for it. """ if fields: - fields_definition = ';'.join( - '='.join([field, definition]) for field, definition in fields.items() - ) + fields_definition = ";".join("=".join([field, definition]) for field, definition in fields.items()) else: - fields_definition = 'pass' - self.make_models(''' + fields_definition = "pass" + self.make_models( + """ from django.db import models from djmoney.models.fields import MoneyField class Model(models.Model): - %s''' % fields_definition) + %s""" + % fields_definition + ) tests_path = os.path.dirname(os.path.dirname(tests.__file__)) return self.run( - "import sys; sys.path.append('{}');".format(tests_path) + - "from tests.migrations.helpers import makemigrations; makemigrations();" + "import sys; sys.path.append('{}');".format(tests_path) + + "from tests.migrations.helpers import makemigrations; makemigrations();" ) - def make_default_migration(self, field='MoneyField(max_digits=10, decimal_places=2)'): + def make_default_migration(self, field="MoneyField(max_digits=10, decimal_places=2)"): return self.make_migration(field=field) def run(self, content): - return self.testdir.runpython_c(dedent(''' + return self.testdir.runpython_c( + dedent( + """ import os os.environ['DJANGO_SETTINGS_MODULE'] = 'app_settings' from django import setup setup() %s - ''' % content)) + """ + % content + ) + ) def create_instance(self): - self.run(''' + self.run( + """ from money_app.models import Model from djmoney.money import Money - Model.objects.create(field=Money(10, 'USD'))''') + Model.objects.create(field=Money(10, 'USD'))""" + ) def migrate(self): tests_path = os.path.dirname(os.path.dirname(tests.__file__)) return self.run( - "import sys; sys.path.append('{}');".format(tests_path) + - "from tests.migrations.helpers import migrate; migrate();" + "import sys; sys.path.append('{}');".format(tests_path) + + "from tests.migrations.helpers import migrate; migrate();" ) def assert_migrate(self, output=None): @@ -109,36 +117,34 @@ def assert_migrate(self, output=None): def test_create_initial(self): migration = self.make_default_migration() - migration.stdout.fnmatch_lines([ - "*Migrations for 'money_app':*", - '*0001_test.py*', - '*- Create model Model*', - ]) - operations = get_operations('0001') + migration.stdout.fnmatch_lines(["*Migrations for 'money_app':*", "*0001_test.py*", "*- Create model Model*"]) + operations = get_operations("0001") assert len(operations) == 1 assert isinstance(operations[0], migrations.CreateModel) fields = sorted(operations[0].fields) assert len(fields) == 3 - assert fields[0][0] == 'field' + assert fields[0][0] == "field" assert isinstance(fields[0][1], MoneyField) - assert fields[1][0] == 'field_currency' + assert fields[1][0] == "field_currency" assert isinstance(fields[1][1], CurrencyField) migration = self.migrate() - migration.stdout.fnmatch_lines(['*Applying money_app.0001_test... OK*']) + migration.stdout.fnmatch_lines(["*Applying money_app.0001_test... OK*"]) def test_add_field(self): self.make_migration() migration = self.make_default_migration() - migration.stdout.fnmatch_lines([ - "*Migrations for 'money_app':*", - '*0002_test.py*', - '*- Add field field to model*', - '*- Add field field_currency to model*', - ]) - - operations = get_operations('0002') + migration.stdout.fnmatch_lines( + [ + "*Migrations for 'money_app':*", + "*0002_test.py*", + "*- Add field field to model*", + "*- Add field field_currency to model*", + ] + ) + + operations = get_operations("0002") assert len(operations) == 2 assert isinstance(operations[0], migrations.AddField) assert isinstance(operations[0].field, MoneyField) @@ -148,14 +154,12 @@ def test_add_field(self): def test_alter_field(self): self.make_default_migration() - migration = self.make_migration(field='MoneyField(max_digits=15, decimal_places=2)') - migration.stdout.fnmatch_lines([ - "*Migrations for 'money_app':*", - '*0002_test.py*', - '*- Alter field field on model*', - ]) - - operations = get_operations('0002') + migration = self.make_migration(field="MoneyField(max_digits=15, decimal_places=2)") + migration.stdout.fnmatch_lines( + ["*Migrations for 'money_app':*", "*0002_test.py*", "*- Alter field field on model*"] + ) + + operations = get_operations("0002") assert len(operations) == 1 assert isinstance(operations[0], migrations.AlterField) assert isinstance(operations[0].field, MoneyField) @@ -165,57 +169,59 @@ def test_alter_field(self): def test_remove_field(self): self.make_default_migration() migration = self.make_migration() - migration.stdout.fnmatch_lines([ - "*Migrations for 'money_app':*", - '*0002_test.py*', - '*- Remove field field from model*', - '*- Remove field field_currency from model*', - ]) - - operations = get_operations('0002') + migration.stdout.fnmatch_lines( + [ + "*Migrations for 'money_app':*", + "*0002_test.py*", + "*- Remove field field from model*", + "*- Remove field field_currency from model*", + ] + ) + + operations = get_operations("0002") assert len(operations) == 2 assert isinstance(operations[0], migrations.RemoveField) - assert operations[0].name == 'field' + assert operations[0].name == "field" assert isinstance(operations[1], migrations.RemoveField) - assert operations[1].name == 'field_currency' + assert operations[1].name == "field_currency" self.assert_migrate() def test_rename_field(self): self.make_default_migration() - self.assert_migrate(['*Applying money_app.0001_test... OK*']) + self.assert_migrate(["*Applying money_app.0001_test... OK*"]) self.create_instance() - migration = self.make_migration(new_field='MoneyField(max_digits=10, decimal_places=2)') - migration.stdout.fnmatch_lines([ - "*Migrations for 'money_app':*", - '*0002_test.py*', - '*- Rename field field on model to new_field*', - '*- Rename field field_currency on model to new_field_currency*', - ]) - self.assert_migrate(['*Applying money_app.0002_test... OK*']) - result = self.run(''' + migration = self.make_migration(new_field="MoneyField(max_digits=10, decimal_places=2)") + migration.stdout.fnmatch_lines( + [ + "*Migrations for 'money_app':*", + "*0002_test.py*", + "*- Rename field field on model to new_field*", + "*- Rename field field_currency on model to new_field_currency*", + ] + ) + self.assert_migrate(["*Applying money_app.0002_test... OK*"]) + result = self.run( + """ from money_app.models import Model instance = Model.objects.get() print(instance.new_field) - ''') - result.stdout.fnmatch_lines(['US$10.00']) + """ + ) + result.stdout.fnmatch_lines(["US$10.00"]) def test_migrate_to_moneyfield(self): - self.make_default_migration(field='models.DecimalField(max_digits=10, decimal_places=2)') - migration = self.make_migration(field='MoneyField(max_digits=10, decimal_places=2)') - migration.stdout.fnmatch_lines([ - "*Migrations for 'money_app':*", - '*0002_test.py*', - '*- Add field field_currency to model*', - ]) + self.make_default_migration(field="models.DecimalField(max_digits=10, decimal_places=2)") + migration = self.make_migration(field="MoneyField(max_digits=10, decimal_places=2)") + migration.stdout.fnmatch_lines( + ["*Migrations for 'money_app':*", "*0002_test.py*", "*- Add field field_currency to model*"] + ) self.assert_migrate() def test_migrate_from_moneyfield(self): self.make_default_migration() - migration = self.make_migration(field='models.DecimalField(max_digits=10, decimal_places=2)') - migration.stdout.fnmatch_lines([ - "*Migrations for 'money_app':*", - '*0002_test.py*', - '*- Remove field field_currency from model*', - ]) + migration = self.make_migration(field="models.DecimalField(max_digits=10, decimal_places=2)") + migration.stdout.fnmatch_lines( + ["*Migrations for 'money_app':*", "*0002_test.py*", "*- Remove field field_currency from model*"] + ) self.assert_migrate() diff --git a/tests/settings.py b/tests/settings.py index 3adee16e..2ae9cb07 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -8,59 +8,53 @@ from moneyed.localization import _FORMATTER, DEFAULT -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:', - } -} +DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}} TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.contrib.auth.context_processors.auth', - ], - }, - }, + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": {"context_processors": ["django.contrib.auth.context_processors.auth"]}, + } ] -warnings.simplefilter('ignore', Warning) +warnings.simplefilter("ignore", Warning) INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - - 'djmoney', - 'djmoney.contrib.exchange', - 'reversion', - - 'tests.testapp' + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.sites", + "djmoney", + "djmoney.contrib.exchange", + "reversion", + "tests.testapp", ] SITE_ID = 1 -SECRET_KEY = 'foobar' +SECRET_KEY = "foobar" USE_L10N = True -_FORMATTER.add_sign_definition('pl_PL', moneyed.PLN, suffix=' zł') -_FORMATTER.add_sign_definition(DEFAULT, moneyed.PLN, suffix=' zł') +_FORMATTER.add_sign_definition("pl_PL", moneyed.PLN, suffix=" zł") +_FORMATTER.add_sign_definition(DEFAULT, moneyed.PLN, suffix=" zł") _FORMATTER.add_formatting_definition( - 'pl_PL', group_size=3, group_separator=' ', decimal_point=',', - positive_sign='', trailing_positive_sign='', - negative_sign='-', trailing_negative_sign='', - rounding_method=ROUND_HALF_EVEN + "pl_PL", + group_size=3, + group_separator=" ", + decimal_point=",", + positive_sign="", + trailing_positive_sign="", + negative_sign="-", + trailing_negative_sign="", + rounding_method=ROUND_HALF_EVEN, ) moneyed.add_currency("USDT", "000", "Tether", None) -OPEN_EXCHANGE_RATES_APP_ID = 'test' -FIXER_ACCESS_KEY = 'test' +OPEN_EXCHANGE_RATES_APP_ID = "test" +FIXER_ACCESS_KEY = "test" diff --git a/tests/test_admin.py b/tests/test_admin.py index 1fad4ed9..eda27f6b 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -11,8 +11,8 @@ from .testapp.models import ModelWithVanillaMoneyField -MONEY_FIELD = ModelWithVanillaMoneyField._meta.get_field('money') -INTEGER_FIELD = ModelWithVanillaMoneyField._meta.get_field('integer') +MONEY_FIELD = ModelWithVanillaMoneyField._meta.get_field("money") +INTEGER_FIELD = ModelWithVanillaMoneyField._meta.get_field("integer") def get_args(value, field): @@ -21,22 +21,25 @@ def get_args(value, field): """ if VERSION[:2] == (1, 8): return value, field - return value, field, '' - - -@pytest.mark.parametrize('value, expected', ( - (Money(10, 'RUB'), '10.00 руб.'), # Issue 232 - (Money(1234), '1,234.00 XYZ'), # Issue 220 - (Money(1000, 'SAR'), 'ر.س1,000.00'), # Issue 196 - (Money(1000, 'PLN'), '1,000.00 zł'), # Issue 102 - (Money('3.33', 'EUR'), '3.33 €'), # Issue 90 -)) + return value, field, "" + + +@pytest.mark.parametrize( + "value, expected", + ( + (Money(10, "RUB"), "10.00 руб."), # Issue 232 + (Money(1234), "1,234.00 XYZ"), # Issue 220 + (Money(1000, "SAR"), "ر.س1,000.00"), # Issue 196 + (Money(1000, "PLN"), "1,000.00 zł"), # Issue 102 + (Money("3.33", "EUR"), "3.33 €"), # Issue 90 + ), +) def test_display_for_field(settings, value, expected): settings.USE_L10N = True # This locale has no definitions in py-moneyed, so it will work for localized money representation. - settings.LANGUAGE_CODE = 'cs' + settings.LANGUAGE_CODE = "cs" assert admin_utils.display_for_field(*get_args(value, MONEY_FIELD)) == expected def test_default_display(): - assert admin_utils.display_for_field(*get_args(10, INTEGER_FIELD)) == '10' + assert admin_utils.display_for_field(*get_args(10, INTEGER_FIELD)) == "10" diff --git a/tests/test_form.py b/tests/test_form.py index 1d63fc5a..862a9e2b 100644 --- a/tests/test_form.py +++ b/tests/test_form.py @@ -32,8 +32,8 @@ def test_save(): - money = Money(Decimal('10'), 'SEK') - form = MoneyModelForm({'money_0': money.amount, 'money_1': money.currency}) + money = Money(Decimal("10"), "SEK") + form = MoneyModelForm({"money_0": money.amount, "money_1": money.currency}) assert form.is_valid() instance = form.save() @@ -43,34 +43,30 @@ def test_save(): def test_validate(): - money = Money(Decimal('10'), 'SEK') - form = MoneyForm({'money_0': money.amount, 'money_1': money.currency}) + money = Money(Decimal("10"), "SEK") + form = MoneyForm({"money_0": money.amount, "money_1": money.currency}) assert form.is_valid() - result = form.cleaned_data['money'] + result = form.cleaned_data["money"] assert result == money @pytest.mark.parametrize( - 'data', + "data", ( - {'money_0': 'xyz*|\\', 'money_1': 'SEK'}, - {'money_0': 10000, 'money_1': 'SEK'}, - {'money_0': 1, 'money_1': 'SEK'}, - {'money_0': 10, 'money_1': 'EUR'} - ) + {"money_0": "xyz*|\\", "money_1": "SEK"}, + {"money_0": 10000, "money_1": "SEK"}, + {"money_0": 1, "money_1": "SEK"}, + {"money_0": 10, "money_1": "EUR"}, + ), ) def test_form_is_invalid(data): assert not MoneyForm(data).is_valid() @pytest.mark.parametrize( - 'data, result', - ( - ({'money_0': '', 'money_1': 'SEK'}, []), - ({'money_0': '1.23', 'money_1': 'SEK'}, ['money']), - ) + "data, result", (({"money_0": "", "money_1": "SEK"}, []), ({"money_0": "1.23", "money_1": "SEK"}, ["money"])) ) def test_changed_data(data, result): assert MoneyForm(data).changed_data == result @@ -82,19 +78,18 @@ def test_change_currency_not_amount(): should consider this to be a change. """ form = MoneyFormMultipleCurrencies( - {'money_0': Decimal(10), 'money_1': 'EUR'}, - initial={'money': Money(Decimal(10), 'SEK')} + {"money_0": Decimal(10), "money_1": "EUR"}, initial={"money": Money(Decimal(10), "SEK")} ) - assert form.changed_data == ['money'] + assert form.changed_data == ["money"] @pytest.mark.parametrize( - 'data, result', + "data, result", ( - ({'money_1': 'SEK'}, True), - ({'money_0': '', 'money_1': 'SEK'}, True), - ({'money_0': 'xyz*|\\', 'money_1': 'SEK'}, False), - ) + ({"money_1": "SEK"}, True), + ({"money_0": "", "money_1": "SEK"}, True), + ({"money_0": "xyz*|\\", "money_1": "SEK"}, False), + ), ) def test_optional_money_form(data, result): """ @@ -123,7 +118,7 @@ def test_fields_default_amount_becomes_forms_initial(): and put it in form field's initial value """ form = DefaultMoneyModelForm() - assert form.fields['money'].initial == [123, 'PLN'] + assert form.fields["money"].initial == [123, "PLN"] def test_no_deprecation_warning(): @@ -131,73 +126,61 @@ def test_no_deprecation_warning(): The library's code shouldn't generate any warnings itself. See #262. """ with pytest.warns(None) as warning: - MoneyField(max_digits=10, decimal_places=2, currency_choices=(('USD', 'USD'),)).formfield() + MoneyField(max_digits=10, decimal_places=2, currency_choices=(("USD", "USD"),)).formfield() assert not warning class TestValidation: - - @pytest.mark.parametrize('value, error', ( - (Money(50, 'EUR'), u'Ensure this value is greater than or equal to 100.00 €.'), - (Money(1500, 'EUR'), u'Ensure this value is less than or equal to 1,000.00 €.'), - (Money(40, 'USD'), 'Ensure this value is greater than or equal to $50.00.'), - (Money(600, 'USD'), 'Ensure this value is less than or equal to $500.00.'), - (Money(400, 'NOK'), 'Ensure this value is greater than or equal to 500.00 Nkr.'), - (Money(950, 'NOK'), 'Ensure this value is less than or equal to 900.00 Nkr.'), - (Money(5, 'SEK'), 'Ensure this value is greater than or equal to 10.'), - (Money(1600, 'SEK'), 'Ensure this value is less than or equal to 1500.'), - )) + @pytest.mark.parametrize( + "value, error", + ( + (Money(50, "EUR"), u"Ensure this value is greater than or equal to 100.00 €."), + (Money(1500, "EUR"), u"Ensure this value is less than or equal to 1,000.00 €."), + (Money(40, "USD"), "Ensure this value is greater than or equal to $50.00."), + (Money(600, "USD"), "Ensure this value is less than or equal to $500.00."), + (Money(400, "NOK"), "Ensure this value is greater than or equal to 500.00 Nkr."), + (Money(950, "NOK"), "Ensure this value is less than or equal to 900.00 Nkr."), + (Money(5, "SEK"), "Ensure this value is greater than or equal to 10."), + (Money(1600, "SEK"), "Ensure this value is less than or equal to 1500."), + ), + ) def test_invalid(self, value, error): - form = ValidatedMoneyModelForm(data={'money_0': value.amount, 'money_1': value.currency}) + form = ValidatedMoneyModelForm(data={"money_0": value.amount, "money_1": value.currency}) assert not form.is_valid() - assert form.errors == {'money': [error]} + assert form.errors == {"money": [error]} - @pytest.mark.parametrize('value', (Money(150, 'EUR'), Money(200, 'USD'), Money(50, 'SEK'), Money(600, 'NOK'))) + @pytest.mark.parametrize("value", (Money(150, "EUR"), Money(200, "USD"), Money(50, "SEK"), Money(600, "NOK"))) def test_valid(self, value): - assert ValidatedMoneyModelForm(data={'money_0': value.amount, 'money_1': value.currency}).is_valid() - - @pytest.mark.parametrize('value', ( - Money(-0.01, 'EUR'), - Money(-1, 'USD'), - Money(-10, 'NOK'), - Money(-100, 'SEK'), - )) + assert ValidatedMoneyModelForm(data={"money_0": value.amount, "money_1": value.currency}).is_valid() + + @pytest.mark.parametrize("value", (Money(-0.01, "EUR"), Money(-1, "USD"), Money(-10, "NOK"), Money(-100, "SEK"))) def test_non_negative_validator(self, value): """Fails if Validator(0) silently allows negative values.""" - form = PositiveValidatedMoneyModelForm( - data={'money_0': value.amount, 'money_1': value.currency} - ) + form = PositiveValidatedMoneyModelForm(data={"money_0": value.amount, "money_1": value.currency}) assert not form.is_valid() - assert form.errors == {'money': ['Ensure this value is greater than or equal to 0.']} - - @pytest.mark.parametrize('value', ( - Money(0, 'PHP'), - Money(0.01, 'EUR'), - Money(1, 'USD'), - Money(10, 'NOK'), - Money(100, 'SEK'), - )) + assert form.errors == {"money": ["Ensure this value is greater than or equal to 0."]} + + @pytest.mark.parametrize( + "value", (Money(0, "PHP"), Money(0.01, "EUR"), Money(1, "USD"), Money(10, "NOK"), Money(100, "SEK")) + ) def test_positive_validator(self, value): """Fails if MinMoneyValidator(0) blocks positive values. MinMoneyValidator(0) should also allow exactly 0. """ - form = PositiveValidatedMoneyModelForm( - data={'money_0': value.amount, 'money_1': value.currency} - ) + form = PositiveValidatedMoneyModelForm(data={"money_0": value.amount, "money_1": value.currency}) assert form.is_valid() def test_default_django_validator(self): - form = MoneyModelFormWithValidation(data={'balance_0': 0, 'balance_1': 'GBP'}) + form = MoneyModelFormWithValidation(data={"balance_0": 0, "balance_1": "GBP"}) assert not form.is_valid() - assert form.errors == {'balance': [u'Ensure this value is greater than or equal to GB£100.00.']} + assert form.errors == {"balance": [u"Ensure this value is greater than or equal to GB£100.00."]} @pytest.mark.skipif(VERSION[:2] == (1, 8), reason="Django 1.8 doesn't have `disabled` keyword in fields") class TestDisabledField: - def test_validation(self): - instance = ModelWithVanillaMoneyField.objects.create(money=Money('42.00', 'USD')) + instance = ModelWithVanillaMoneyField.objects.create(money=Money("42.00", "USD")) form = DisabledFieldForm(data={}, instance=instance) assert not form.errors assert form.is_valid() diff --git a/tests/test_managers.py b/tests/test_managers.py index 2d50c163..4b060abe 100644 --- a/tests/test_managers.py +++ b/tests/test_managers.py @@ -12,17 +12,16 @@ def assert_leafs(children): - assert ('money', Money(0, 'USD')) in children - assert ('money_currency', 'USD') in children + assert ("money", Money(0, "USD")) in children + assert ("money_currency", "USD") in children class TestExpandMoneyArgs: - def test_no_args(self): assert _expand_money_args(ModelWithNonMoneyField(), []) == [] def test_non_q_args(self): - assert _expand_money_args(ModelWithNonMoneyField(), ['money']) == ['money'] + assert _expand_money_args(ModelWithNonMoneyField(), ["money"]) == ["money"] def test_exact(self): """ @@ -31,7 +30,7 @@ def test_exact(self): results in; (AND: (AND: ('money', 0 USD), ('money_currency', u'USD'))) """ - actual = _expand_money_args(ModelWithNonMoneyField(), [Q(money=Money(0, 'USD'))]) + actual = _expand_money_args(ModelWithNonMoneyField(), [Q(money=Money(0, "USD"))]) assert len(actual) == 1 arg = actual[0] @@ -50,7 +49,7 @@ def test_and(self): results in; (AND: ('desc', 'foo'), (AND: ('money', 0 USD), ('money_currency', u'USD'))) """ - actual = _expand_money_args(ModelWithNonMoneyField(), [Q(money=Money(0, 'USD'), desc='foo')]) + actual = _expand_money_args(ModelWithNonMoneyField(), [Q(money=Money(0, "USD"), desc="foo")]) assert len(actual) == 1 arg = actual[0] @@ -62,13 +61,13 @@ def test_and(self): # Can't guarantee the ordering of children, thus; for child in arg.children: if isinstance(child, tuple): - assert ('desc', 'foo') == child + assert ("desc", "foo") == child elif isinstance(child, Q): assert child.connector == Q.AND assert len(child.children) == 2 assert_leafs(child.children) else: - pytest.fail('There should only be two elements, a tuple and a Q - not a %s' % child) + pytest.fail("There should only be two elements, a tuple and a Q - not a %s" % child) def test_and_with_or(self): """ @@ -77,7 +76,7 @@ def test_and_with_or(self): results in: (OR: (AND: ('desc', 'foo'), (AND: ('money', 0 USD), ('money_currency', u'USD'))), ('desc', 'bar')) """ - actual = _expand_money_args(ModelWithNonMoneyField(), [Q(money=Money(0, 'USD'), desc='foo') | Q(desc='bar')]) + actual = _expand_money_args(ModelWithNonMoneyField(), [Q(money=Money(0, "USD"), desc="foo") | Q(desc="bar")]) assert len(actual) == 1 arg = actual[0] @@ -89,12 +88,12 @@ def test_and_with_or(self): # Can't guarantee the ordering of children, thus; for child in arg.children: if isinstance(child, tuple): - assert ('desc', 'bar') == child + assert ("desc", "bar") == child elif isinstance(child, Q): assert len(child.children) == 2 for subchild in child.children: if isinstance(subchild, tuple): - assert ('desc', 'foo') == subchild + assert ("desc", "foo") == subchild elif isinstance(subchild, Q): assert_leafs(subchild.children) else: @@ -103,11 +102,34 @@ def test_and_with_or(self): def test_and_with_two_or(self): """ Test - (OR: (OR: (AND: ('desc', 'foo'), ('money', 0 USD)), ('desc', 'eggs')), ('desc', 'bar')) + (OR: + (OR: + (AND: + ('desc', 'foo'), + ('money', 0 USD) + ), + ('desc', 'eggs') + ), + ('desc', 'bar') + ) results in; - (OR: (OR: (AND: ('desc', 'foo'), (AND: ('money', 0 USD), ('money_currency', u'USD'))), ('desc', 'eggs')), ('desc', 'bar')) + (OR: + (OR: + (AND: + ('desc', 'foo'), + (AND: + ('money', 0), + ('money_currency', 'USD') + ) + ), + ('desc', 'eggs') + ), + ('desc', 'bar') + ) """ - actual = _expand_money_args(ModelWithNonMoneyField(), [Q(Q(money=Money(0, 'USD'), desc='foo') | Q(desc='eggs')) | Q(desc='bar')]) + actual = _expand_money_args( + ModelWithNonMoneyField(), [Q(Q(money=Money(0, "USD"), desc="foo") | Q(desc="eggs")) | Q(desc="bar")] + ) arg = actual[0] assert len(actual) == 1 @@ -118,57 +140,57 @@ def test_and_with_two_or(self): # Can't guarantee the ordering of children, thus; for child in arg.children: if isinstance(child, tuple): - assert ('desc', 'bar') == child + assert ("desc", "bar") == child elif isinstance(child, Q): assert len(child.children) == 2 for subchild in child.children: if isinstance(subchild, tuple): - assert ('desc', 'eggs') == subchild + assert ("desc", "eggs") == subchild elif isinstance(subchild, Q): for subsubchild in subchild.children: if isinstance(subsubchild, tuple): - assert ('desc', 'foo') == subsubchild + assert ("desc", "foo") == subsubchild elif isinstance(subsubchild, Q): assert_leafs(subsubchild.children) else: - pytest.fail('There should only be two subsubchild elements, a tuple and a Q - not a %s' % subsubchild) + pytest.fail( + "There should only be two subsubchild elements, a tuple and a Q - not a %s" + % subsubchild + ) else: - pytest.fail('There should only be two subchild elements, a tuple and a Q - not a %s' % subsubchild) + pytest.fail( + "There should only be two subchild elements, a tuple and a Q - not a %s" % subsubchild + ) else: - pytest.fail('There should only be two child elements, a tuple and a Q - not a %s' % child) + pytest.fail("There should only be two child elements, a tuple and a Q - not a %s" % child) class TestKwargsExpand: - @pytest.mark.parametrize( - 'value, expected', ( + "value, expected", + ( ( - ({'money': 100, 'desc': 'test'}, {'money': 100, 'desc': 'test'}), - ({'money': Money(100, 'USD')}, {'money': 100, 'money_currency': 'USD'}), - ({'money': OldMoney(100, 'USD')}, {'money': 100, 'money_currency': 'USD'}), - ({'money': Money(100, 'USD'), 'desc': 'test'}, {'money': 100, 'money_currency': 'USD', 'desc': 'test'}), + ({"money": 100, "desc": "test"}, {"money": 100, "desc": "test"}), + ({"money": Money(100, "USD")}, {"money": 100, "money_currency": "USD"}), + ({"money": OldMoney(100, "USD")}, {"money": 100, "money_currency": "USD"}), + ({"money": Money(100, "USD"), "desc": "test"}, {"money": 100, "money_currency": "USD", "desc": "test"}), ) - ) + ), ) def test_simple(self, value, expected): assert _expand_money_kwargs(ModelWithNonMoneyField, kwargs=value)[1] == expected @pytest.mark.parametrize( - 'value, expected', ( - ( - ({'money': F('money') * 2}, 2), - ({'money': F('money') + Money(100, 'USD')}, 100), - ) - ) + "value, expected", (({"money": F("money") * 2}, 2), ({"money": F("money") + Money(100, "USD")}, 100)) ) def test_complex_f_query(self, value, expected): _, kwargs = _expand_money_kwargs(ModelWithNonMoneyField, kwargs=value) - assert isinstance(kwargs['money_currency'], F) - assert kwargs['money_currency'].name == 'money_currency' - assert get_amount(kwargs['money'].rhs) == expected + assert isinstance(kwargs["money_currency"], F) + assert kwargs["money_currency"].name == "money_currency" + assert get_amount(kwargs["money"].rhs) == expected def test_simple_f_query(self): - _, kwargs = _expand_money_kwargs(ModelWithNonMoneyField, kwargs={'money': F('money')}) - assert isinstance(kwargs['money_currency'], F) - assert kwargs['money_currency'].name == 'money_currency' - assert kwargs['money'].name == 'money' + _, kwargs = _expand_money_kwargs(ModelWithNonMoneyField, kwargs={"money": F("money")}) + assert isinstance(kwargs["money_currency"], F) + assert kwargs["money_currency"].name == "money_currency" + assert kwargs["money"].name == "money" diff --git a/tests/test_models.py b/tests/test_models.py index 82723fb8..66ff7480 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -56,26 +56,25 @@ class TestVanillaMoneyField: - @pytest.mark.parametrize( - 'model_class, kwargs, expected', + "model_class, kwargs, expected", ( - (ModelWithVanillaMoneyField, {'money': Money('100.0')}, Money('100.0')), - (ModelWithVanillaMoneyField, {'money': OldMoney('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': OldMoney('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')), - (CryptoModel, {"money": Money(10, "USDT")}, Money(10, "USDT")) - ) + (ModelWithVanillaMoneyField, {"money": Money("100.0")}, Money("100.0")), + (ModelWithVanillaMoneyField, {"money": OldMoney("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": OldMoney("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")), + (CryptoModel, {"money": Money(10, "USDT")}, Money(10, "USDT")), + ), ) def test_create_defaults(self, model_class, kwargs, expected): instance = model_class.objects.create(**kwargs) @@ -86,23 +85,23 @@ def test_create_defaults(self, model_class, kwargs, expected): def test_old_money_defaults(self): instance = ModelWithDefaultAsOldMoney.objects.create() - assert instance.money == Money('.01', 'RUB') + assert instance.money == Money(".01", "RUB") @pytest.mark.parametrize( - 'model_class, other_value', + "model_class, other_value", ( - (ModelWithVanillaMoneyField, Money('100.0')), - (BaseModel, Money(0, 'USD')), - (ModelWithDefaultAsMoney, Money('0.01', 'RUB')), - (ModelWithDefaultAsFloat, OldMoney('12.05', 'PLN')), - (ModelWithDefaultAsFloat, Money('12.05', 'PLN')), - ) + (ModelWithVanillaMoneyField, Money("100.0")), + (BaseModel, Money(0, "USD")), + (ModelWithDefaultAsMoney, Money("0.01", "RUB")), + (ModelWithDefaultAsFloat, OldMoney("12.05", "PLN")), + (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() + 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 + 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 @@ -118,105 +117,103 @@ def test_revert_to_default(self, model_class, other_value): instance4 = model_class.objects.get(id=pk) assert instance4.money == default_instance - @pytest.mark.parametrize( - 'value', - ( - (1, 'USD', 'extra_string'), - (1, None), - (1, ), - ) - ) + @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('Money', (Money, OldMoney)) - @pytest.mark.parametrize('field_name', ('money', 'second_money')) - def test_save_new_value(self, field_name, Money): - ModelWithVanillaMoneyField.objects.create(**{field_name: Money('100.0')}) + @pytest.mark.parametrize("money_class", (Money, OldMoney)) + @pytest.mark.parametrize("field_name", ("money", "second_money")) + def test_save_new_value(self, field_name, money_class): + ModelWithVanillaMoneyField.objects.create(**{field_name: money_class("100.0")}) # Try setting the value directly retrieved = ModelWithVanillaMoneyField.objects.get() - setattr(retrieved, field_name, Money(1, 'DKK')) + setattr(retrieved, field_name, Money(1, "DKK")) retrieved.save() retrieved = ModelWithVanillaMoneyField.objects.get() - assert getattr(retrieved, field_name) == Money(1, 'DKK') + assert getattr(retrieved, field_name) == Money(1, "DKK") def test_rounding(self): - money = Money('100.0623456781123219') + 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') + assert retrieved.money == Money("100.06") @pytest.fixture(params=[Money, OldMoney]) def objects_setup(self, request): Money = request.param - 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')), - )) + 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', + "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(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=OldMoney(1, 'USD')) | Q(amount1=OldMoney(4, 'USD')) | Q(amount2=OldMoney(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), - ) + (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=OldMoney(1, "USD")) | Q(amount1=OldMoney(4, "USD")) | Q(amount2=OldMoney(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') + @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[:2] == (1, 8), reason="Django 1.8 doesn't support __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') + 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} + kwargs = {"amount1__" + lookup: rhs} assert ModelWithTwoMoneyFields.objects.filter(**kwargs).count() == expected def test_exact_match(self): - money = Money('100.0') + money = Money("100.0") instance = ModelWithVanillaMoneyField.objects.create(money=money) retrieved = ModelWithVanillaMoneyField.objects.get(money=money) @@ -229,9 +226,9 @@ def test_issue_300_regression(self): ModelIssue300.objects.filter(money__created__gt=date) def test_range_search(self): - money = Money('3') + money = Money("3") - instance = ModelWithVanillaMoneyField.objects.create(money=Money('100.0')) + instance = ModelWithVanillaMoneyField.objects.create(money=Money("100.0")) retrieved = ModelWithVanillaMoneyField.objects.get(money__gt=money) assert instance.pk == retrieved.pk @@ -239,36 +236,39 @@ def test_range_search(self): assert ModelWithVanillaMoneyField.objects.filter(money__lt=money).count() == 0 def test_filter_chaining(self): - usd_instance = ModelWithVanillaMoneyField.objects.create(money=Money(100, 'USD')) - ModelWithVanillaMoneyField.objects.create(money=Money(100, 'EUR')) - query = ModelWithVanillaMoneyField.objects.filter().filter(money=Money(100, 'USD')) + usd_instance = ModelWithVanillaMoneyField.objects.create(money=Money(100, "USD")) + ModelWithVanillaMoneyField.objects.create(money=Money(100, "EUR")) + query = ModelWithVanillaMoneyField.objects.filter().filter(money=Money(100, "USD")) assert usd_instance in query assert query.count() == 1 - @pytest.mark.parametrize('model_class', (ModelWithVanillaMoneyField, ModelWithChoicesMoneyField)) + @pytest.mark.parametrize("model_class", (ModelWithVanillaMoneyField, ModelWithChoicesMoneyField)) def test_currency_querying(self, model_class): - model_class.objects.create(money=Money('100.0', 'ZWN')) + model_class.objects.create(money=Money("100.0", "ZWN")) - assert model_class.objects.filter(money__lt=Money('1000', 'USD')).count() == 0 - assert model_class.objects.filter(money__lt=Money('1000', 'ZWN')).count() == 1 + assert model_class.objects.filter(money__lt=Money("1000", "USD")).count() == 0 + assert model_class.objects.filter(money__lt=Money("1000", "ZWN")).count() == 1 - @pytest.mark.usefixtures('objects_setup') + @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=(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 - @pytest.mark.usefixtures('objects_setup') + @pytest.mark.usefixtures("objects_setup") def test_in_lookup_f_expression(self): - assert ModelWithTwoMoneyFields.objects.filter(amount1__in=(Money(4, 'USD'), F('amount2'))).count() == 2 + assert ModelWithTwoMoneyFields.objects.filter(amount1__in=(Money(4, "USD"), F("amount2"))).count() == 2 def test_isnull_lookup(self): NullMoneyFieldModel.objects.create(field=None) - NullMoneyFieldModel.objects.create(field=Money(100, 'USD')) + NullMoneyFieldModel.objects.create(field=Money(100, "USD")) queryset = NullMoneyFieldModel.objects.filter(field=None) assert queryset.count() == 1 @@ -279,78 +279,78 @@ def test_null_default(self): class TestGetOrCreate: - @pytest.mark.parametrize( - 'model, field_name, kwargs, currency', + "model, field_name, kwargs, currency", ( - (ModelWithVanillaMoneyField, 'money', {'money_currency': 'PLN'}, 'PLN'), - (ModelWithVanillaMoneyField, 'money', {'money': Money(0, 'EUR')}, 'EUR'), - (ModelWithVanillaMoneyField, 'money', {'money': OldMoney(0, 'EUR')}, 'EUR'), - (ModelWithSharedCurrency, 'first', {'first': 10, 'second': 15, 'currency': 'CZK'}, 'CZK') - ) + (ModelWithVanillaMoneyField, "money", {"money_currency": "PLN"}, "PLN"), + (ModelWithVanillaMoneyField, "money", {"money": Money(0, "EUR")}, "EUR"), + (ModelWithVanillaMoneyField, "money", {"money": OldMoney(0, "EUR")}, "EUR"), + (ModelWithSharedCurrency, "first", {"first": 10, "second": 15, "currency": "CZK"}, "CZK"), + ), ) def test_get_or_create_respects_currency(self, model, field_name, kwargs, currency): instance, created = model.objects.get_or_create(**kwargs) field = getattr(instance, field_name) - assert str(field.currency) == currency, 'currency should be taken into account in get_or_create' + assert str(field.currency) == currency, "currency should be taken into account in get_or_create" def test_get_or_create_respects_defaults(self): - value = Money(10, 'SEK') + value = Money(10, "SEK") instance = ModelWithUniqueIdAndCurrency.objects.create(money=value) instance, created = ModelWithUniqueIdAndCurrency.objects.get_or_create( - id=instance.id, - money_currency=instance.money_currency + id=instance.id, money_currency=instance.money_currency ) assert not created assert instance.money == value def test_defaults(self): - money = Money(10, 'EUR') - instance, _ = ModelWithVanillaMoneyField.objects.get_or_create(integer=1, defaults={'money': money}) + money = Money(10, "EUR") + instance, _ = ModelWithVanillaMoneyField.objects.get_or_create(integer=1, defaults={"money": money}) assert instance.money == money def test_currency_field_lookup(self): - value = Money(10, 'EUR') + value = Money(10, "EUR") ModelWithVanillaMoneyField.objects.create(money=value) - instance, created = ModelWithVanillaMoneyField.objects.get_or_create(money_currency__iexact='eur') + instance, created = ModelWithVanillaMoneyField.objects.get_or_create(money_currency__iexact="eur") assert not created assert instance.money == value - @pytest.mark.parametrize('model, create_kwargs, get_kwargs', ( - (NullMoneyFieldModel, {'field': Money(100, 'USD')}, {'field': 100, 'field_currency': 'USD'}), - (ModelWithSharedCurrency, {'first': 10, 'second': 15, 'currency': 'USD'}, {'first': 10, 'currency': 'USD'}), - )) + @pytest.mark.parametrize( + "model, create_kwargs, get_kwargs", + ( + (NullMoneyFieldModel, {"field": Money(100, "USD")}, {"field": 100, "field_currency": "USD"}), + (ModelWithSharedCurrency, {"first": 10, "second": 15, "currency": "USD"}, {"first": 10, "currency": "USD"}), + ), + ) def test_no_default_model(self, model, create_kwargs, get_kwargs): model.objects.create(**create_kwargs) instance, created = model.objects.get_or_create(**get_kwargs) assert not created def test_shared_currency(self): - instance, created = ModelWithSharedCurrency.objects.get_or_create(first=10, second=15, currency='USD') - assert instance.first == Money(10, 'USD') - assert instance.second == Money(15, 'USD') + instance, created = ModelWithSharedCurrency.objects.get_or_create(first=10, second=15, currency="USD") + assert instance.first == Money(10, "USD") + assert instance.second == Money(15, "USD") class TestNullableCurrency: - def test_create_nullable(self): instance = ModelWithNullableCurrency.objects.create() assert instance.money is None assert instance.money_currency is None def test_create_default(self): - money = Money(100, 'SEK') + money = Money(100, "SEK") instance = ModelWithNullableCurrency.objects.create(money=money) assert instance.money == money def test_fails_with_null_currency(self): with pytest.raises(ValueError) as exc: ModelWithNullableCurrency.objects.create(money=10) - assert str(exc.value) == 'Missing currency value' + assert str(exc.value) == "Missing currency value" assert not ModelWithNullableCurrency.objects.exists() def test_query_not_null(self): - money = Money(100, 'EUR') + money = Money(100, "EUR") ModelWithNullableCurrency.objects.create(money=money) instance = ModelWithNullableCurrency.objects.get() assert instance.money == money @@ -365,30 +365,30 @@ def test_query_null(self): class TestFExpressions: parametrize_f_objects = pytest.mark.parametrize( - 'f_obj, expected', + "f_obj, expected", ( - (F('money') + Money(100, 'USD'), Money(200, 'USD')), - (F('money') + OldMoney(100, 'USD'), Money(200, 'USD')), - (Money(100, 'USD') + F('money'), Money(200, 'USD')), - (F('money') - Money(100, 'USD'), Money(0, 'USD')), - (Money(100, 'USD') - F('money'), Money(0, 'USD')), - (F('money') * 2, Money(200, 'USD')), - (F('money') * F('integer'), Money(200, 'USD')), - (Money(50, 'USD') * F('integer'), Money(100, 'USD')), - (F('integer') * Money(50, 'USD'), Money(100, 'USD')), - (Money(50, 'USD') / F('integer'), Money(25, 'USD')), - (Money(51, 'USD') % F('integer'), Money(1, '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')), - ) + (F("money") + Money(100, "USD"), Money(200, "USD")), + (F("money") + OldMoney(100, "USD"), Money(200, "USD")), + (Money(100, "USD") + F("money"), Money(200, "USD")), + (F("money") - Money(100, "USD"), Money(0, "USD")), + (Money(100, "USD") - F("money"), Money(0, "USD")), + (F("money") * 2, Money(200, "USD")), + (F("money") * F("integer"), Money(200, "USD")), + (Money(50, "USD") * F("integer"), Money(100, "USD")), + (F("integer") * Money(50, "USD"), Money(100, "USD")), + (Money(50, "USD") / F("integer"), Money(25, "USD")), + (Money(51, "USD") % F("integer"), Money(1, "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 = ModelWithVanillaMoneyField.objects.create(money=Money(100, "USD"), integer=2) instance.money = f_obj instance.save() instance.refresh_from_db() @@ -396,114 +396,88 @@ def test_save(self, f_obj, expected): @parametrize_f_objects def test_f_update(self, f_obj, expected): - instance = ModelWithVanillaMoneyField.objects.create(money=Money(100, 'USD'), integer=2) + instance = ModelWithVanillaMoneyField.objects.create(money=Money(100, "USD"), integer=2) ModelWithVanillaMoneyField.objects.update(money=f_obj) instance.refresh_from_db() assert instance.money == expected def test_default_update(self): - instance = ModelWithVanillaMoneyField.objects.create(money=Money(100, 'USD'), integer=2) - second_money = Money(100, 'USD') + instance = ModelWithVanillaMoneyField.objects.create(money=Money(100, "USD"), integer=2) + second_money = Money(100, "USD") ModelWithVanillaMoneyField.objects.update(second_money=second_money) instance.refresh_from_db() assert instance.second_money == second_money @pytest.mark.parametrize( - 'create_kwargs, filter_value, in_result', + "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(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(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(100, 'USD'), 'second_money': Money(100, 'EUR')}, - {'money': F('second_money')}, - False + {"money": Money(50, "USD"), "second_money": Money(100, "EUR")}, + {"second_money": F("money") + Money(50, "USD")}, + 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.create(money=Money(100, "USD"), integer=2) + instance.money = F("money") + Money(100, "USD") + instance.save(update_fields=["money"]) instance.refresh_from_db() - assert instance.money == Money(200, 'USD') + 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, + 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) + @pytest.mark.parametrize("f_obj", INVALID_EXPRESSIONS) def test_invalid_expressions_access(self, f_obj): - instance = ModelWithVanillaMoneyField.objects.create(money=Money(100, 'USD')) + instance = ModelWithVanillaMoneyField.objects.create(money=Money(100, "USD")) with pytest.raises(ValidationError): instance.money = f_obj class TestExpressions: - def test_conditional_update(self): - ModelWithVanillaMoneyField.objects.bulk_create(( - ModelWithVanillaMoneyField(money=Money(1, 'USD'), integer=0), - ModelWithVanillaMoneyField(money=Money(2, 'USD'), integer=1), - )) - ModelWithVanillaMoneyField.objects.update(money=Case( - When(integer=0, then=Value(10)), - default=Value(0) - )) - assert ModelWithVanillaMoneyField.objects.get(integer=0).money == Money(10, 'USD') - assert ModelWithVanillaMoneyField.objects.get(integer=1).money == Money(0, 'USD') + ModelWithVanillaMoneyField.objects.bulk_create( + ( + ModelWithVanillaMoneyField(money=Money(1, "USD"), integer=0), + ModelWithVanillaMoneyField(money=Money(2, "USD"), integer=1), + ) + ) + ModelWithVanillaMoneyField.objects.update(money=Case(When(integer=0, then=Value(10)), default=Value(0))) + assert ModelWithVanillaMoneyField.objects.get(integer=0).money == Money(10, "USD") + assert ModelWithVanillaMoneyField.objects.get(integer=1).money == Money(0, "USD") @pytest.mark.skipif(VERSION[:2] == (1, 8), reason="Django 1.8 doesn't supports this") def test_create_func(self): - instance = ModelWithVanillaMoneyField.objects.create(money=Func(Value(-10), function='ABS')) + instance = ModelWithVanillaMoneyField.objects.create(money=Func(Value(-10), function="ABS")) instance.refresh_from_db() assert instance.money.amount == 10 @pytest.mark.parametrize( - 'value, expected', ( - (None, None), - (10, Money(10, 'USD')), - (Money(10, 'EUR'), Money(10, 'EUR')), - ) + "value, expected", ((None, None), (10, Money(10, "USD")), (Money(10, "EUR"), Money(10, "EUR"))) ) def test_value_create(self, value, expected): instance = NullMoneyFieldModel.objects.create(field=Value(value)) @@ -512,27 +486,27 @@ def test_value_create(self, value, expected): def test_value_create_invalid(self): with pytest.raises(ValidationError): - ModelWithVanillaMoneyField.objects.create(money=Value('string')) + ModelWithVanillaMoneyField.objects.create(money=Value("string")) def test_expressions_for_non_money_fields(self): - instance = ModelWithVanillaMoneyField.objects.create(money=Money(1, 'USD'), integer=0) - assert ModelWithVanillaMoneyField.objects.get(money=F('integer') + 1) == instance - assert ModelWithVanillaMoneyField.objects.get(Q(money=F('integer') + 1)) == instance + instance = ModelWithVanillaMoneyField.objects.create(money=Money(1, "USD"), integer=0) + assert ModelWithVanillaMoneyField.objects.get(money=F("integer") + 1) == instance + assert ModelWithVanillaMoneyField.objects.get(Q(money=F("integer") + 1)) == instance def test_find_models_related_to_money_models(): - moneyModel = ModelWithVanillaMoneyField.objects.create(money=Money('100.0', 'ZWN')) + moneyModel = ModelWithVanillaMoneyField.objects.create(money=Money("100.0", "ZWN")) ModelRelatedToModelWithMoney.objects.create(moneyModel=moneyModel) - ModelRelatedToModelWithMoney.objects.get(moneyModel__money=Money('100.0', 'ZWN')) - ModelRelatedToModelWithMoney.objects.get(moneyModel__money__lt=Money('1000.0', 'ZWN')) + ModelRelatedToModelWithMoney.objects.get(moneyModel__money=Money("100.0", "ZWN")) + ModelRelatedToModelWithMoney.objects.get(moneyModel__money__lt=Money("1000.0", "ZWN")) def test_allow_expression_nodes_without_money(): """Allow querying on expression nodes that are not Money""" - desc = 'hundred' + desc = "hundred" ModelWithNonMoneyField.objects.create(money=Money(100.0), desc=desc) - instance = ModelWithNonMoneyField.objects.filter(desc=F('desc')).get() + instance = ModelWithNonMoneyField.objects.filter(desc=F("desc")).get() assert instance.desc == desc @@ -540,7 +514,7 @@ def test_base_model(): assert BaseModel.objects.model == BaseModel -@pytest.mark.parametrize('model_class', (InheritedModel, InheritorModel)) +@pytest.mark.parametrize("model_class", (InheritedModel, InheritorModel)) class TestInheritance: """Test inheritance from a concrete and an abstract models""" @@ -548,33 +522,31 @@ def test_model(self, model_class): assert model_class.objects.model == model_class def test_fields(self, model_class): - first_value = Money('100.0', 'ZWN') - second_value = Money('200.0', 'USD') + first_value = Money("100.0", "ZWN") + second_value = Money("200.0", "USD") instance = model_class.objects.create(money=first_value, second_field=second_value) assert instance.money == first_value assert instance.second_field == second_value class TestManager: - def test_manager(self): - assert hasattr(SimpleModel, 'objects') + assert hasattr(SimpleModel, "objects") def test_objects_creation(self): - SimpleModel.objects.create(money=Money('100.0', 'USD')) + SimpleModel.objects.create(money=Money("100.0", "USD")) assert SimpleModel.objects.count() == 1 class TestProxyModel: - def test_instances(self): - ProxyModel.objects.create(money=Money('100.0', 'USD')) + ProxyModel.objects.create(money=Money("100.0", "USD")) assert isinstance(ProxyModel.objects.get(pk=1), ProxyModel) def test_patching(self): - ProxyModel.objects.create(money=Money('100.0', 'USD')) + 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 + assert ProxyModel.objects.filter(money__gt=Money("50.00", "GBP")).count() == 0 class TestDifferentCurrencies: @@ -582,111 +554,101 @@ class TestDifferentCurrencies: def test_add_default(self): with pytest.raises(TypeError): - Money(10, 'EUR') + Money(1, 'USD') + Money(10, "EUR") + Money(1, "USD") def test_sub_default(self): with pytest.raises(TypeError): - Money(10, 'EUR') - Money(1, 'USD') + Money(10, "EUR") - Money(1, "USD") - @pytest.mark.usefixtures('autoconversion') + @pytest.mark.usefixtures("autoconversion") def test_add_with_auto_convert(self): - assert Money(10, 'EUR') + Money(1, 'USD') == Money('10.88', 'EUR') + assert Money(10, "EUR") + Money(1, "USD") == Money("10.88", "EUR") - @pytest.mark.usefixtures('autoconversion') + @pytest.mark.usefixtures("autoconversion") def test_sub_with_auto_convert(self): - assert Money(10, 'EUR') - Money(1, 'USD') == Money('9.12', 'EUR') + assert Money(10, "EUR") - Money(1, "USD") == Money("9.12", "EUR") def test_eq(self): - assert Money(1, 'EUR') == Money(1, 'EUR') + assert Money(1, "EUR") == Money(1, "EUR") def test_ne(self): - assert Money(1, 'EUR') != Money(2, 'EUR') + assert Money(1, "EUR") != Money(2, "EUR") def test_ne_currency(self): - assert Money(10, 'EUR') != Money(10, 'USD') + assert Money(10, "EUR") != Money(10, "USD") @pytest.mark.parametrize( - 'model_class', ( - AbstractModel, - ModelWithNonMoneyField, - InheritorModel, - InheritedModel, - ProxyModel, - ) + "model_class", (AbstractModel, ModelWithNonMoneyField, InheritorModel, InheritedModel, ProxyModel) ) def test_manager_instance_access(model_class): with pytest.raises(AttributeError): model_class().objects.all() -@pytest.mark.skipif(VERSION[:2] != (1, 8), reason='Only Django 1.8 has `get_field_by_name` method of `Options`.') +@pytest.mark.skipif(VERSION[:2] != (1, 8), reason="Only Django 1.8 has `get_field_by_name` method of `Options`.") def test_get_field_by_name(): - assert BaseModel._meta.get_field_by_name('money')[0].__class__.__name__ == 'MoneyField' - assert BaseModel._meta.get_field_by_name('money_currency')[0].__class__.__name__ == 'CurrencyField' + assert BaseModel._meta.get_field_by_name("money")[0].__class__.__name__ == "MoneyField" + assert BaseModel._meta.get_field_by_name("money_currency")[0].__class__.__name__ == "CurrencyField" def test_different_hashes(): - money = BaseModel._meta.get_field('money') - money_currency = BaseModel._meta.get_field('money_currency') + money = BaseModel._meta.get_field("money") + money_currency = BaseModel._meta.get_field("money_currency") assert hash(money) != hash(money_currency) def test_migration_serialization(): if PY2: - serialized = 'djmoney.money.Money(100, b\'GBP\')' + serialized = "djmoney.money.Money(100, b'GBP')" else: - serialized = 'djmoney.money.Money(100, \'GBP\')' - assert MigrationWriter.serialize(Money(100, 'GBP')) == (serialized, {'import djmoney.money'}) + serialized = "djmoney.money.Money(100, 'GBP')" + assert MigrationWriter.serialize(Money(100, "GBP")) == (serialized, {"import djmoney.money"}) -@pytest.mark.parametrize('model, manager_name', ( - (ModelWithVanillaMoneyField, 'objects'), - (ModelWithCustomDefaultManager, 'custom'), -)) +@pytest.mark.parametrize( + "model, manager_name", ((ModelWithVanillaMoneyField, "objects"), (ModelWithCustomDefaultManager, "custom")) +) def test_clear_meta_cache(model, manager_name): """ See issue GH-318. """ if model is ModelWithCustomDefaultManager and VERSION[:2] == (1, 8): - pytest.skip('The `default_manager_name` setting is not available in Django 1.8') + pytest.skip("The `default_manager_name` setting is not available in Django 1.8") model._meta._expire_cache() manager_class = getattr(model, manager_name).__class__ - assert manager_class.__module__ + '.' + manager_class.__name__ == 'djmoney.models.managers.MoneyManager' + assert manager_class.__module__ + "." + manager_class.__name__ == "djmoney.models.managers.MoneyManager" class TestFieldAttributes: - def create_class(self, **field_kwargs): - class Model(models.Model): field = MoneyField(**field_kwargs) class Meta: - app_label = 'test' + app_label = "test" return Model def test_missing_attributes(self): with pytest.raises(ValueError) as exc: self.create_class(default={}) - assert str(exc.value) == 'default value must be an instance of Money, is: {}' + assert str(exc.value) == "default value must be an instance of Money, is: {}" 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' + 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') + assert instance.field == Money(10, "EUR") class TestCustomManager: - def test_method(self): assert ModelWithCustomManager.manager.super_method().count() == 0 def test_package_is_importable(): - __import__('djmoney.__init__') + __import__("djmoney.__init__") def test_hash_uniqueness(): @@ -701,20 +663,22 @@ def test_override_decorator(): """ When current locale is changed, Money instances should be represented correctly. """ - with override('cs'): - assert str(Money(10, 'CZK')) == '10.00 Kč' + with override("cs"): + assert str(Money(10, "CZK")) == "10.00 Kč" def test_deprecation(): with pytest.warns(None) as warnings: - MoneyPatched(1, 'USD') - assert str(warnings[0].message) == "'djmoney.models.fields.MoneyPatched' is deprecated. " \ - "Use 'djmoney.money.Money' instead" + MoneyPatched(1, "USD") + assert ( + str(warnings[0].message) == "'djmoney.models.fields.MoneyPatched' is deprecated. " + "Use 'djmoney.money.Money' instead" + ) def test_properties_access(): with pytest.raises(TypeError) as exc: - ModelWithVanillaMoneyField(money=Money(1, 'USD'), bla=1) + ModelWithVanillaMoneyField(money=Money(1, "USD"), bla=1) if VERSION[:2] > (2, 1): assert str(exc.value) == "ModelWithVanillaMoneyField() got an unexpected keyword argument 'bla'" else: @@ -722,42 +686,38 @@ def test_properties_access(): def parametrize_with_q(**kwargs): - return pytest.mark.parametrize('args, kwargs', ( - ((), kwargs), - ((Q(**kwargs),), {}), - )) + return pytest.mark.parametrize("args, kwargs", (((), kwargs), ((Q(**kwargs),), {}))) class TestSharedCurrency: - @pytest.fixture def instance(self): - return ModelWithSharedCurrency.objects.create(first=10, second=15, currency='USD') + return ModelWithSharedCurrency.objects.create(first=10, second=15, currency="USD") def test_attributes(self, instance): - assert instance.first == Money(10, 'USD') - assert instance.second == Money(15, 'USD') - assert instance.currency == 'USD' + assert instance.first == Money(10, "USD") + assert instance.second == Money(15, "USD") + assert instance.currency == "USD" - @parametrize_with_q(first=Money(10, 'USD')) + @parametrize_with_q(first=Money(10, "USD")) def test_filter_by_money_match(self, instance, args, kwargs): assert instance in ModelWithSharedCurrency.objects.filter(*args, **kwargs) - @parametrize_with_q(first=Money(10, 'EUR')) + @parametrize_with_q(first=Money(10, "EUR")) def test_filter_by_money_no_match(self, instance, args, kwargs): assert instance not in ModelWithSharedCurrency.objects.filter(*args, **kwargs) - @parametrize_with_q(first=F('second')) + @parametrize_with_q(first=F("second")) def test_f_query(self, args, kwargs): - instance = ModelWithSharedCurrency.objects.create(first=10, second=10, currency='USD') + instance = ModelWithSharedCurrency.objects.create(first=10, second=10, currency="USD") assert instance in ModelWithSharedCurrency.objects.filter(*args, **kwargs) - @parametrize_with_q(first__in=[Money(10, 'USD'), Money(100, 'USD')]) + @parametrize_with_q(first__in=[Money(10, "USD"), Money(100, "USD")]) def test_in_lookup(self, instance, args, kwargs): assert instance in ModelWithSharedCurrency.objects.filter(*args, **kwargs) def test_create_with_money(self): - value = Money(10, 'USD') + value = Money(10, "USD") instance = ModelWithSharedCurrency.objects.create(first=value, second=value) assert instance.first == value assert instance.second == value diff --git a/tests/test_money.py b/tests/test_money.py index 01f7e2fd..5d1d9a47 100644 --- a/tests/test_money.py +++ b/tests/test_money.py @@ -9,35 +9,31 @@ def test_repr(): - assert repr(Money('10.5', 'USD')) == '' + assert repr(Money("10.5", "USD")) == "" def test_html_safe(): - assert Money('10.5', 'EUR').__html__() == u'10.50\xa0€' + assert Money("10.5", "EUR").__html__() == u"10.50\xa0€" def test_html_unsafe(): class UnsafeMoney(Money): - def __unicode__(self): - return '