diff --git a/.gitignore b/.gitignore index ef74e87..229ff4b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ RELEASE-VERSION dist *.egg-info *.egg +*.eggs *.komodoproject *.esproj *.pyc diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..58896d3 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: python +python: + - "2.7" + - "3.5" +script: python setup.py test +services: + - postgresql diff --git a/MANIFEST.in b/MANIFEST.in index f99c169..1aedc82 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,7 @@ include MANIFEST.in include RELEASE-VERSION -include *.txt include *.md include *.py recursive-include money * -recursive-include tests * -recursive-exclude * __pycache__ recursive-exclude * *.py[co] diff --git a/README.md b/README.md index 1b8430e..b29fd2f 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ - python-money ============ Primitives for working with money and currencies in Python - Compatibility ============= @@ -19,7 +17,6 @@ postgres specific field, and a `py.test` based test suite among other changes. You are free to use this version but please look at other forks as well as they may better suit your use case. - Installation ============ @@ -29,7 +26,6 @@ You can install this project directly from the git repository using pip: You do not have to specify the version number but it might be a good idea. - Usage ===== @@ -44,16 +40,16 @@ For example: Currency(code='BZD', numeric='084', name='Belize Dollar', countries=['BELIZE']) -There is a dict of all ISO-4217 currencies: +There is a dictionary of all ISO-4217 currencies: - >>> from money import CURRENCY + >>> from money.money import CURRENCY >>> print CURRENCY['GBP'].name Pound Sterling ### Money Class The Money class is available for doing arithmetic on values in defined -currencies. It wraps the python Decimal type and gives you several convienience +currencies. It wraps the python Decimal type and gives you several convenience methods. Using this prevents you from making mistakes like adding Pounds and Dollars together, multiplying two money values or comparing different currencies. For example: @@ -96,7 +92,7 @@ currencies. For example: This package assumes that you have a preferred default currency. Somewhere in your software's initialization you should set that currency: - >>> from money import set_default_currency + >>> from money.money import set_default_currency >>> set_default_currency(code='USD') >>> print Money(amount=23.45) USD 23.45 @@ -106,19 +102,12 @@ If you don't you will get a non-specified 'XXX' currency: >>> print Money(amount=23.45) XXX 23.45 -There is also an exchange rate that may be set: - -This default currency and exchange rate is used for arithmetic addition. If you -add two monetary values that are in differing currency, they will first be -converted into the default currency, and then added together. - - A Note About Equality and Math Operations ----------------------------------------- -The way equlity is currently implemented, `USD 0` is not equal to `EUR 0` however, +The way equality is currently implemented, `USD 0` is not equal to `EUR 0` however, `USD 0` is considered equal to `0`. This means you can only compare similar -currencies to each other, but it is safe to compare a currancy to the value `0`. +currencies to each other, but it is safe to compare a currency to the value `0`. Comparing two differing currencies is undefined and will raise a `money.CurrencyMismatchException`. Prior versions of this project would do an @@ -132,7 +121,7 @@ example, `Money(10, 'USD') - Money(3, 'JPY')` is not allowed due to differing currencies. Both `Money(3, 'USD') * Money(3, 'USD')` and `Money(9, 'USD') / Money(3, 'USD')` -are undefined. There are 3 conceiveable ways to handle division: +are undefined. There are 3 conceivable ways to handle division: Money(9, 'USD') / Money(3, 'USD') # Money(3, 'XXX') # where 'XXX' denotes undefined currency Money(9, 'USD') / Money(3, 'USD') # Decimal('3') @@ -164,12 +153,11 @@ python `Decimal` class: bool(Money('0.01', 'USD')) # True bool(Money('1')) # True -To test for the existance of the objects, compare the value to `None`: +To test for the existence of the objects, compare the value to `None`: if amount is None: amount = Money(0) - Django ====== @@ -188,9 +176,9 @@ the Django DecimalField: price = MoneyField(default=0, max_digits=12, decimal_places=2) ... -Now run ./manage.py dbsync or South migrate. Your database table will contain a +Now run `./manage.py dbsync` or South migrate. Your database table will contain a field for holding the value and a second field for the currency type. In -postgresql it might look like this: +PostgreSQL it might look like this: price | numeric(12,2) | not null default NULL::numeric price_currency | character varying(3) | not null @@ -205,22 +193,22 @@ The value you get from your model will be a `Money` class: ### User Defined Precision of Decimals in Postgres It can be difficult to represent decimals exactly as the user entered them with -django. If you use postgres, you can preserve the user's entered precision by -using the Postgresql `numeric` type. Simply use the `InfiniteDecimalField` +Django. If you use postgres, you can preserve the user's entered precision by +using the PostgreSQL `numeric` type. Simply use the `InfiniteDecimalField` class and the value will be stored as entered by the user without having to define the precision in the model class. InfiniteDecimalField -This allows you to store and later retreive a values like `3`, `3.0`, and +This allows you to store and later retrieve a values like `3`, `3.0`, and `3.000` without losing the precision. The `MoneyField` class already extends this by default. ### Fixtures -When loading from or searializing to fixtures, the field class expects the values -to be specified separately: +When loading from or serializing to fixtures, the field class expects the +values to be specified separately: ... { @@ -243,7 +231,7 @@ The form field used by the `models.MoneyField` is also called `MoneyField` ### Running Tests -The test suite requires `py.test`, `django` and several other libaries to be +The test suite requires `py.test`, `django` and several other libraries to be installed. They will be downloaded and installed automatically when run. Tests can be run via the `setup.py` script: @@ -277,7 +265,16 @@ TODO CHANGELOG === -* Version 1.0.0 +* Version 1.1 + + - Python 3 compatibility + - Fix queryset returning the wrong value when running in Django 1.8 + +* Version 1.0.1 (tagged 0.3.3) + + - Add support or the `db_column` parameter + +* Version 1.0.0 (tagged 0.3.2) Note: This fork of the project is now going to be version-managed separate from other forks. This is the first release that we consider to be fully diff --git a/money/__init__.py b/money/__init__.py index 4ca3f60..17576c8 100644 --- a/money/__init__.py +++ b/money/__init__.py @@ -1 +1 @@ -from money import * +from .money import * diff --git a/money/contrib/django/forms/__init__.py b/money/contrib/django/forms/__init__.py index ad6b12e..2f76c29 100644 --- a/money/contrib/django/forms/__init__.py +++ b/money/contrib/django/forms/__init__.py @@ -1,2 +1,2 @@ -from fields import * -from widgets import * \ No newline at end of file +from .fields import * +from .widgets import * diff --git a/money/contrib/django/forms/fields.py b/money/contrib/django/forms/fields.py index 86951b4..f9a387d 100644 --- a/money/contrib/django/forms/fields.py +++ b/money/contrib/django/forms/fields.py @@ -1,7 +1,7 @@ from django import forms -from widgets import CurrencySelectWidget +from .widgets import CurrencySelectWidget -from money import Money, CURRENCY +from money.money import Money, CURRENCY class MoneyField(forms.MultiValueField): diff --git a/money/contrib/django/models/fields.py b/money/contrib/django/models/fields.py index 9ed3933..2ec2f41 100644 --- a/money/contrib/django/models/fields.py +++ b/money/contrib/django/models/fields.py @@ -4,7 +4,7 @@ from django.utils.translation import ugettext_lazy from money.contrib.django import forms -from money import Money +from money.money import Money __all__ = ('MoneyField', 'currency_field_name', 'NotSupportedLookup') @@ -64,24 +64,24 @@ def __get__(self, obj, *args): return Money(amount, currency) def __set__(self, obj, value): - if value is None: # Money(0) is False + if value is None: # Money(0) is False self._set_values(obj, None, '') elif isinstance(value, Money): self._set_values(obj, value.amount, value.currency) elif isinstance(value, Decimal): - _, currency = self._get_values(obj) # use what is currently set + _, currency = self._get_values(obj) # use what is currently set self._set_values(obj, value, currency) else: # It could be an int, or some other python native type try: amount = Decimal(str(value)) - _, currency = self._get_values(obj) # use what is currently set + _, currency = self._get_values(obj) # use what is currently set self._set_values(obj, amount, currency) except TypeError: # Lastly, assume string type 'XXX 123' or something Money can # handle. try: - _, currency = self._get_values(obj) # use what is currently set + _, currency = self._get_values(obj) # use what is currently set m = Money.from_string(str(value)) self._set_values(obj, m.amount, m.currency) except TypeError: @@ -93,7 +93,7 @@ class InfiniteDecimalField(models.DecimalField): def db_type(self, connection): engine = connection.settings_dict['ENGINE'] - if 'psycopg2' in engine: + if 'postgresql' in engine: return 'numeric' return super(InfiniteDecimalField, self).db_type(connection=connection) @@ -135,7 +135,7 @@ class MoneyField(InfiniteDecimalField): # Don't extend SubfieldBase since we need to have access to both fields when # to_python is called. We need our code there instead of subfieldBase - #__metaclass__ = models.SubfieldBase + # __metaclass__ = models.SubfieldBase def __init__(self, *args, **kwargs): # We add the currency field except when using frozen south orm. See introspection rules below. @@ -146,10 +146,10 @@ def __init__(self, *args, **kwargs): self.blankable = kwargs.get('blank', False) if isinstance(default, Money): - self.default_currency = default.currency # use the default's currency + self.default_currency = default.currency # use the default's currency kwargs['default'] = default.amount else: - self.default_currency = default_currency or '' # use the kwarg passed in + self.default_currency = default_currency or '' # use the kwarg passed in super(MoneyField, self).__init__(*args, **kwargs) @@ -185,7 +185,7 @@ def contribute_to_class(self, cls, name): max_length=3, default=self.default_currency, editable=False, - null=False, # empty char fields should be '' + null=False, # empty char fields should be '' blank=self.blankable, db_column=currency_field_db_column(self.db_column), ) @@ -202,7 +202,7 @@ def contribute_to_class(self, cls, name): # Set our custom manager if not hasattr(cls, '_default_manager'): - from managers import MoneyManager + from .managers import MoneyManager cls.add_to_class('objects', MoneyManager()) def get_db_prep_save(self, value, *args, **kwargs): diff --git a/money/contrib/django/models/managers.py b/money/contrib/django/models/managers.py index 0a77e2d..8708903 100755 --- a/money/contrib/django/models/managers.py +++ b/money/contrib/django/models/managers.py @@ -1,7 +1,7 @@ from django.db import models from django.db.models.query import QuerySet -from django.utils.encoding import smart_unicode -from fields import currency_field_name +from django.utils.encoding import smart_text +from .fields import currency_field_name __all__ = ('QuerysetWithMoney', 'MoneyManager',) @@ -10,7 +10,7 @@ class QuerysetWithMoney(QuerySet): def _update_params(self, kwargs): from django.db.models.constants import LOOKUP_SEP - from money import Money + from money.money import Money to_append = {} for name, value in kwargs.items(): if isinstance(value, Money): @@ -19,7 +19,7 @@ def _update_params(self, kwargs): field_name = currency_field_name(path[0]) else: field_name = currency_field_name(name) - to_append[field_name] = smart_unicode(value.currency) + to_append[field_name] = smart_text(value.currency) kwargs.update(to_append) return kwargs @@ -81,5 +81,5 @@ def values(self, *args, **kwargs): class MoneyManager(models.Manager): - def get_query_set(self): + def get_queryset(self): return QuerysetWithMoney(self.model) diff --git a/money/money.py b/money/money.py index 542574f..b91f53d 100644 --- a/money/money.py +++ b/money/money.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- -from __future__ import division -import six - -import exceptions +from __future__ import division, unicode_literals from decimal import Decimal +import six + class Currency(object): code = "XXX" @@ -43,11 +42,11 @@ def __ne__(self, other): DEFAULT_CURRENCY = CURRENCY['XXX'] -class IncorrectMoneyInputError(exceptions.Exception): +class IncorrectMoneyInputError(Exception): """Invalid input for the Money object""" -class CurrencyMismatchException(exceptions.ArithmeticError): +class CurrencyMismatchException(ArithmeticError): """Raised when an operation is not allowed between differing currencies""" @@ -55,6 +54,7 @@ class InvalidOperationException(TypeError): """Raised when an operation is never allowed""" +@six.python_2_unicode_compatible class Money(object): """ An amount of money with an optional currency @@ -111,7 +111,7 @@ def from_string(cls, value): def _currency_check(self, other): """ Compare the currencies matches and raise if not """ if self._currency != other.currency: - raise CurrencyMismatchException(u"Currency mismatch: %s != %s" % (self._currency, other.currency,)) + raise CurrencyMismatchException("Currency mismatch: %s != %s" % (self._currency, other.currency,)) def __init__(self, amount=None, currency=None): if isinstance(amount, Decimal): @@ -154,9 +154,6 @@ def currency(self): def __str__(self): return "{} {}".format(self._currency, self._amount) - def __unicode__(self): - return u"{} {}".format(self._currency, self._amount) - def __repr__(self): return str(self) @@ -190,11 +187,11 @@ def __rsub__(self, other): # In the case where both values are Money, the left hand one will be # called. In the case where we are subtracting Money from another # value, we want to disallow it - raise TypeError("Can not subtact Money from %r" % other) + raise TypeError("Cannot subtract Money from %r" % other) def __mul__(self, other): if isinstance(other, Money): - raise InvalidOperationException(u'Cannot multiply monetary quantities') + raise InvalidOperationException("Cannot multiply monetary quantities") return Money(amount=self._amount*Decimal(str(other)), currency=self._currency) def __truediv__(self, other): @@ -203,29 +200,26 @@ def __truediv__(self, other): another Money value is undefined """ if isinstance(other, Money): - raise InvalidOperationException(u'Cannot divide two monetary quantities') + raise InvalidOperationException("Cannot divide two monetary quantities") return Money(amount=self._amount / other, currency=self._currency) __div__ = __truediv__ def __floordiv__(self, other): - raise InvalidOperationException(u'Floor division not supported for monetary quantities') + raise InvalidOperationException("Floor division not supported for monetary quantities") def __rtruediv__(self, other): - raise InvalidOperationException(u'Cannot divide by monetary quantities') + raise InvalidOperationException("Cannot divide by monetary quantities") __rdiv__ = __rtruediv__ - # Communative operations + # Commutative operations __radd__ = __add__ __rmul__ = __mul__ # Boolean def __bool__(self): - if self._amount != 0: - return True - else: - return False + return self._amount != 0 __nonzero__ = __bool__ @@ -244,16 +238,16 @@ def __ne__(self, other): def __lt__(self, other): if isinstance(other, Money): self._currency_check(other) - return (self._amount < other.amount) + return self._amount < other.amount else: - return (self._amount < Decimal(str(other))) + return self._amount < Decimal(str(other)) def __gt__(self, other): if isinstance(other, Money): self._currency_check(other) - return (self._amount > other.amount) + return self._amount > other.amount else: - return (self._amount > Decimal(str(other))) + return self._amount > Decimal(str(other)) def __le__(self, other): return self < other or self == other diff --git a/money/tests/models.py b/money/tests/models.py index fbcdc7f..01c0e90 100644 --- a/money/tests/models.py +++ b/money/tests/models.py @@ -1,53 +1,62 @@ +from __future__ import unicode_literals + +import six + from django.db import models + from money.contrib.django.models import fields -from money import Money +from money.money import Money # Tests for Django models. We set up three types of models with different # ways of specifying defaults +@six.python_2_unicode_compatible class SimpleMoneyModel(models.Model): name = models.CharField(max_length=100) price = fields.MoneyField(max_digits=12, decimal_places=3) - def __unicode__(self): - return self.name + u" " + unicode(self.price) + def __str__(self): + return self.name + " " + str(self.price) class Meta: app_label = 'tests' +@six.python_2_unicode_compatible class MoneyModelDefaultMoneyUSD(models.Model): name = models.CharField(max_length=100) price = fields.MoneyField(max_digits=12, decimal_places=3, default=Money("123.45", "USD")) zero = fields.MoneyField(max_digits=12, decimal_places=3, default=Money("0", "USD")) - def __unicode__(self): - return self.name + u" " + unicode(self.price) + def __str__(self): + return self.name + " " + str(self.price) class Meta: app_label = 'tests' +@six.python_2_unicode_compatible class MoneyModelDefaults(models.Model): name = models.CharField('Name', max_length=100) price = fields.MoneyField('Price', max_digits=12, decimal_places=3, default="123.45", default_currency="USD") zero = fields.MoneyField('Zero', max_digits=12, decimal_places=3, default="0", default_currency="USD") - def __unicode__(self): - return self.name + u" " + unicode(self.price) + def __str__(self): + return self.name + " " + str(self.price) class Meta: app_label = 'tests' +@six.python_2_unicode_compatible class NullableMoneyModel(models.Model): name = models.CharField(max_length=100) price = fields.MoneyField(max_digits=12, decimal_places=3, null=True) - def __unicode__(self): - return self.name + u" " + unicode(self.price) + def __str__(self): + return self.name + " " + str(self.price) class Meta: app_label = 'tests' diff --git a/money/tests/settings.py b/money/tests/settings.py index 784e277..68d2ead 100644 --- a/money/tests/settings.py +++ b/money/tests/settings.py @@ -9,16 +9,30 @@ STATIC_ROOT = os.path.join(TEST_DIR, 'static') DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': ':memory:', - }, # 'default': { - # 'ENGINE': 'django.db.backends.postgresql_psycopg2', - # 'NAME': 'money', - # } + # 'ENGINE': 'django.db.backends.sqlite3', + # 'NAME': ':memory:', + # }, + 'default': { + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'money', + 'USER': '', + 'PASSWORD': '', + } } + +if os.environ.get('CIRCLECI'): + DATABASES['default']['USER'] = 'ubuntu' +elif os.environ.get('TRAVIS'): + DATABASES['default']['USER'] = 'postgres' +elif os.environ.get('SNAP_CI'): + DATABASES['default']['USER'] = os.environ['SNAP_DB_PG_USER'] + DATABASES['default']['PASSWORD'] = os.environ['SNAP_DB_PG_PASSWORD'] + DATABASES['default']['HOST'] = os.environ['SNAP_DB_PG_HOST'] + DATABASES['default']['PORT'] = os.environ['SNAP_DB_PG_PORT'] + + INSTALLED_APPS = ( 'money', 'money.tests', diff --git a/money/tests/test_currency.py b/money/tests/test_currency.py index 8133f10..cbd9765 100644 --- a/money/tests/test_currency.py +++ b/money/tests/test_currency.py @@ -1,4 +1,4 @@ -from money import ( +from money.money import ( Currency, ) diff --git a/money/tests/test_dajngo_defaults.py b/money/tests/test_dajngo_defaults.py index 9f01dbe..690bbc8 100644 --- a/money/tests/test_dajngo_defaults.py +++ b/money/tests/test_dajngo_defaults.py @@ -1,6 +1,6 @@ import pytest -from money import Money +from money.money import Money from money.tests.models import ALL_PARAMETRIZED_MODELS diff --git a/money/tests/test_django.py b/money/tests/test_django.py index 64ec2af..c2f751b 100644 --- a/money/tests/test_django.py +++ b/money/tests/test_django.py @@ -1,8 +1,10 @@ +from __future__ import unicode_literals + import pytest from django.test import TestCase from django.db import IntegrityError -from money import Money, CURRENCY +from money.money import Money, CURRENCY from money.contrib.django.models.fields import NotSupportedLookup from money.tests.models import ( SimpleMoneyModel, @@ -15,12 +17,12 @@ @pytest.mark.django_db class MoneyFieldTestCase(TestCase): - def assertSameCurrency(self, moneys, currency=None): + def assertSameCurrency(self, moneys, currency_code=None): """ Utility function to assert a collection of currencies are all the same """ - currencies = set([m.currency for m in moneys]) - self.assertTrue(len(currencies) == 1) - if currency: - self.assertEqual(currencies.pop().code, currency) + currency_codes = set([m.currency.code for m in moneys]) + self.assertTrue(len(currency_codes) == 1) + if currency_code: + self.assertEqual(currency_codes.pop(), currency_code) def test_non_null(self): instance = SimpleMoneyModel() @@ -31,7 +33,7 @@ def test_creating(self): ind = 0 for code, currency in CURRENCY.items(): ind = ind + 1 - price = Money(ind*1000.0, code) + price = Money(ind * 1000.0, code) SimpleMoneyModel.objects.create(name=currency.name, price=price.amount, price_currency=price.currency) count = SimpleMoneyModel.objects.all().count() self.assertEqual(len(CURRENCY), count) @@ -51,16 +53,16 @@ def test_retrive(self): price = Money(100, "USD") SimpleMoneyModel.objects.create(name="one hundred dollars", price=price) - #Filter + # Filter qset = SimpleMoneyModel.objects.filter(price=price) self.assertEqual(qset.count(), 1) self.assertEqual(qset[0].price, price) - #Get + # Get entry = SimpleMoneyModel.objects.get(price=price) self.assertEqual(entry.price, price) - #test retrieving without currency + # test retrieving without currency entry = SimpleMoneyModel.objects.get(price=100) self.assertEqual(entry.price, price) @@ -68,42 +70,42 @@ def test_assign(self): price = Money(100, "USD") ent = SimpleMoneyModel(name='test', price=price.amount, price_currency=price.currency) ent.save() - self.assertEquals(ent.price, Money(100, "USD")) + self.assertEqual(ent.price, Money(100, "USD")) ent.price = Money(10, "USD") ent.save() - self.assertEquals(ent.price, Money(10, "USD")) + self.assertEqual(ent.price, Money(10, "USD")) ent_same = SimpleMoneyModel.objects.get(pk=ent.id) - self.assertEquals(ent_same.price, Money(10, "USD")) + self.assertEqual(ent_same.price, Money(10, "USD")) - def test_retreive_and_update(self): + def test_retrieve_and_update(self): created = SimpleMoneyModel.objects.create(name="one hundred dollars", price=Money(100, "USD")) created.save() - self.assertEquals(created.price, Money(100, "USD")) + self.assertEqual(created.price, Money(100, "USD")) ent = SimpleMoneyModel.objects.filter(price__exact=Money(100, "USD")).get() - self.assertEquals(ent.price, Money(100, "USD")) + self.assertEqual(ent.price, Money(100, "USD")) ent.price = Money(300, "USD") ent.save() ent = SimpleMoneyModel.objects.filter(price__exact=Money(300, "USD")).get() - self.assertEquals(ent.price, Money(300, "USD")) + self.assertEqual(ent.price, Money(300, "USD")) def test_defaults_as_money_objects(self): ent = MoneyModelDefaultMoneyUSD.objects.create(name='123.45 USD') - self.assertEquals(ent.price, Money('123.45', 'USD')) + self.assertEqual(ent.price, Money('123.45', 'USD')) ent = MoneyModelDefaultMoneyUSD.objects.get(pk=ent.id) - self.assertEquals(ent.price, Money('123.45', 'USD')) + self.assertEqual(ent.price, Money('123.45', 'USD')) def test_defaults_as_separate_values(self): ent = MoneyModelDefaults.objects.create(name='100 USD', price=100) - self.assertEquals(ent.price, Money(100, 'USD')) + self.assertEqual(ent.price, Money(100, 'USD')) ent = MoneyModelDefaults.objects.get(pk=ent.id) - self.assertEquals(ent.price, Money(100, 'USD')) + self.assertEqual(ent.price, Money(100, 'USD')) def test_lookup(self): USD100 = Money(100, "USD") @@ -111,18 +113,18 @@ def test_lookup(self): UAH100 = Money(100, "UAH") SimpleMoneyModel.objects.create(name="one hundred dollars", price=USD100) - SimpleMoneyModel.objects.create(name="one hundred and one dollars", price=USD100+1) - SimpleMoneyModel.objects.create(name="ninety nine dollars", price=USD100-1) + SimpleMoneyModel.objects.create(name="one hundred and one dollars", price=USD100 + 1) + SimpleMoneyModel.objects.create(name="ninety nine dollars", price=USD100 - 1) SimpleMoneyModel.objects.create(name="one hundred euros", price=EUR100) - SimpleMoneyModel.objects.create(name="one hundred and one euros", price=EUR100+1) - SimpleMoneyModel.objects.create(name="ninety nine euros", price=EUR100-1) + SimpleMoneyModel.objects.create(name="one hundred and one euros", price=EUR100 + 1) + SimpleMoneyModel.objects.create(name="ninety nine euros", price=EUR100 - 1) SimpleMoneyModel.objects.create(name="one hundred hrivnyas", price=UAH100) - SimpleMoneyModel.objects.create(name="one hundred and one hrivnyas", price=UAH100+1) - SimpleMoneyModel.objects.create(name="ninety nine hrivnyas", price=UAH100-1) + SimpleMoneyModel.objects.create(name="one hundred and one hrivnyas", price=UAH100 + 1) + SimpleMoneyModel.objects.create(name="ninety nine hrivnyas", price=UAH100 - 1) - #Exact: + # Exact: qset = SimpleMoneyModel.objects.filter(price__exact=USD100) self.assertEqual(qset.count(), 1) qset = SimpleMoneyModel.objects.filter(price__exact=EUR100) @@ -130,33 +132,33 @@ def test_lookup(self): qset = SimpleMoneyModel.objects.filter(price__exact=UAH100) self.assertEqual(qset.count(), 1) - #Less than: + # Less than: qset = SimpleMoneyModel.objects.filter(price__lt=USD100) self.assertEqual(qset.count(), 1) - self.assertEqual(qset[0].price, USD100-1) + self.assertEqual(qset[0].price, USD100 - 1) qset = SimpleMoneyModel.objects.filter(price__lt=EUR100) self.assertEqual(qset.count(), 1) - self.assertEqual(qset[0].price, EUR100-1) + self.assertEqual(qset[0].price, EUR100 - 1) qset = SimpleMoneyModel.objects.filter(price__lt=UAH100) self.assertEqual(qset.count(), 1) - self.assertEqual(qset[0].price, UAH100-1) + self.assertEqual(qset[0].price, UAH100 - 1) - #Greater than: + # Greater than: qset = SimpleMoneyModel.objects.filter(price__gt=USD100) self.assertEqual(qset.count(), 1) - self.assertEqual(qset[0].price, USD100+1) + self.assertEqual(qset[0].price, USD100 + 1) qset = SimpleMoneyModel.objects.filter(price__gt=EUR100) self.assertEqual(qset.count(), 1) - self.assertEqual(qset[0].price, EUR100+1) + self.assertEqual(qset[0].price, EUR100 + 1) qset = SimpleMoneyModel.objects.filter(price__gt=UAH100) self.assertEqual(qset.count(), 1) - self.assertEqual(qset[0].price, UAH100+1) + self.assertEqual(qset[0].price, UAH100 + 1) - #Less than or equal: + # Less than or equal: qset = SimpleMoneyModel.objects.filter(price__lte=USD100) self.assertEqual(qset.count(), 2) self.assertSameCurrency([ent.price for ent in qset], "USD") @@ -175,7 +177,7 @@ def test_lookup(self): for ent in qset: self.assertTrue(ent.price.amount <= 100) - #Greater than or equal: + # Greater than or equal: qset = SimpleMoneyModel.objects.filter(price__gte=USD100) self.assertEqual(qset.count(), 2) self.assertSameCurrency([ent.price for ent in qset], "USD") @@ -212,29 +214,24 @@ def test_price_amount_to_string(self): e2 = SimpleMoneyModel(price=Money('200.0', 'JPY')) self.assertEqual(str(e1.price), "JPY 200") - self.assertEqual(unicode(e1.price), "JPY 200") self.assertEqual(str(e1.price.amount), "200") - self.assertEqual(unicode(e1.price.amount), "200") self.assertEqual(str(e2.price.amount), "200.0") - self.assertEqual(unicode(e2.price.amount), "200.0") def test_price_amount_to_string_non_money(self): e1 = MoneyModelDefaults() self.assertEqual(str(e1.price), "USD 123.45") - self.assertEqual(unicode(e1.price), "USD 123.45") self.assertEqual(str(e1.price.amount), "123.45") - self.assertEqual(unicode(e1.price.amount), "123.45") def test_zero_edge_case(self): created = SimpleMoneyModel.objects.create(name="zero dollars", price=Money(0, "USD")) - self.assertEquals(created.price, Money(0, "USD")) + self.assertEqual(created.price, Money(0, "USD")) ent = SimpleMoneyModel.objects.filter(price__exact=Money(0, "USD")).get() - self.assertEquals(ent.price, Money(0, "USD")) + self.assertEqual(ent.price, Money(0, "USD")) def test_unsupported_lookup(self): with pytest.raises(NotSupportedLookup): @@ -247,22 +244,22 @@ def test_currency_accessor(self): # can be removed as well. created = SimpleMoneyModel.objects.create(name="zero dollars", price=Money(0)) - self.assertEquals(created.price_currency, "XXX") - self.assertEquals(created.price.currency, "XXX") + self.assertEqual(created.price_currency, "XXX") + self.assertEqual(created.price.currency, "XXX") created = SimpleMoneyModel.objects.create(name="zero dollars", price=Money(0, "USD")) - self.assertEquals(created.price_currency, "USD") - self.assertEquals(created.price.currency, "USD") + self.assertEqual(created.price_currency, "USD") + self.assertEqual(created.price.currency, "USD") # This actually wouldn't work in the old code without a round trip to the db created.price_currency = 'EUR' - self.assertEquals(created.price_currency, "EUR") - self.assertEquals(created.price.currency, "EUR") + self.assertEqual(created.price_currency, "EUR") + self.assertEqual(created.price.currency, "EUR") created.save() created = SimpleMoneyModel.objects.get(pk=created.pk) - self.assertEquals(created.price_currency, "EUR") - self.assertEquals(created.price.currency, "EUR") + self.assertEqual(created.price_currency, "EUR") + self.assertEqual(created.price.currency, "EUR") @pytest.mark.django_db @@ -270,18 +267,18 @@ class TestNullability(TestCase): def test_nullable_model_instance(self): instance = NullableMoneyModel() - self.assertEquals(instance.price, None) + self.assertEqual(instance.price, None) def test_nullable_model_save(self): instance = NullableMoneyModel() instance.save() - self.assertEquals(instance.price, None) + self.assertEqual(instance.price, None) def test_nullable_model_create_and_lookup(self): name = "test_nullable_model_create_and_lookup" NullableMoneyModel.objects.create(name=name) instance = NullableMoneyModel.objects.get(name=name) - self.assertEquals(instance.price, None) + self.assertEqual(instance.price, None) def test_nullable_model_lookup_by_null_amount(self): name = "test_nullable_model_lookup_by_null_amount" @@ -289,7 +286,7 @@ def test_nullable_model_lookup_by_null_amount(self): # Assert NULL currency has "blank" currency instance = NullableMoneyModel.objects.filter(price_currency='')[0] - self.assertEquals(instance.name, name) + self.assertEqual(instance.name, name) def test_nullable_model_lookup_by_null_currency(self): name = "test_nullable_model_lookup_by_null_currency" @@ -297,26 +294,26 @@ def test_nullable_model_lookup_by_null_currency(self): # Assert NULL currency has "blank" currency instance = NullableMoneyModel.objects.filter(price__isnull=True)[0] - self.assertEquals(instance.name, name) + self.assertEqual(instance.name, name) def test_nullable_null_currency_vs_undefined_currency(self): name = "test_nullable_null_currency_vs_undefined_currency" - NullableMoneyModel.objects.create(name=name+"_null", price=None) - NullableMoneyModel.objects.create(name=name+"_undefined", price=Money(0)) - self.assertEquals(NullableMoneyModel.objects.all().count(), 2) + NullableMoneyModel.objects.create(name=name + "_null", price=None) + NullableMoneyModel.objects.create(name=name + "_undefined", price=Money(0)) + self.assertEqual(NullableMoneyModel.objects.all().count(), 2) # Assert NULL currency has "blank" currency - self.assertEquals(NullableMoneyModel.objects.filter(price__isnull=True).count(), 1) + self.assertEqual(NullableMoneyModel.objects.filter(price__isnull=True).count(), 1) null_instance = NullableMoneyModel.objects.filter(price__isnull=True)[0] - self.assertEquals(null_instance.name, name + "_null") + self.assertEqual(null_instance.name, name + "_null") null_instance = NullableMoneyModel.objects.filter(price_currency='')[0] - self.assertEquals(null_instance.name, name + "_null") + self.assertEqual(null_instance.name, name + "_null") - self.assertEquals(NullableMoneyModel.objects.filter(price__isnull=False).count(), 1) + self.assertEqual(NullableMoneyModel.objects.filter(price__isnull=False).count(), 1) undefined_instance = NullableMoneyModel.objects.filter(price__isnull=False)[0] - self.assertEquals(undefined_instance.name, name+"_undefined") + self.assertEqual(undefined_instance.name, name + "_undefined") undefined_instance = NullableMoneyModel.objects.filter(price_currency='XXX')[0] - self.assertEquals(undefined_instance.name, name + "_undefined") + self.assertEqual(undefined_instance.name, name + "_undefined") @pytest.mark.django_db diff --git a/money/tests/test_django_rendering.py b/money/tests/test_django_rendering.py index 49dfd14..18a8a2d 100644 --- a/money/tests/test_django_rendering.py +++ b/money/tests/test_django_rendering.py @@ -1,3 +1,5 @@ +from __future__ import unicode_literals + import pytest from django.core.urlresolvers import reverse @@ -9,7 +11,12 @@ SimpleMoneyModel, ) -from money.tests.views import * +from money.tests.views import ( + instance_view, + model_form_view, + model_from_db_view, + model_view, +) class TestView(TestCase): @@ -63,7 +70,6 @@ class TestEditView(TestCase): def setUp(self): self.client = Client() - def test_form_GET(self): url = reverse(model_form_view, kwargs={'amount': '987.00', 'currency': 'JPY'}) response = self.client.get(url) @@ -92,7 +98,7 @@ def test_form_POST(self): # Find the object we created... obj = SimpleMoneyModel.objects.last() - self.assertEqual(unicode(obj.price), u"JPY 555.5") + self.assertEqual(str(obj.price), "JPY 555.5") self.assertContains(response, '|item:name|value:ABC|') self.assertContains(response, '|item:price|value:JPY 555.5|') diff --git a/money/tests/test_money.py b/money/tests/test_money.py index 968cdd7..89a63da 100644 --- a/money/tests/test_money.py +++ b/money/tests/test_money.py @@ -1,7 +1,7 @@ from decimal import Decimal from unittest import TestCase -from money import ( +from money.money import ( Money, CURRENCY, Currency, diff --git a/money/tests/test_money_operations.py b/money/tests/test_money_operations.py index c614f29..a4bb560 100644 --- a/money/tests/test_money_operations.py +++ b/money/tests/test_money_operations.py @@ -1,9 +1,9 @@ -from __future__ import division +from __future__ import division, unicode_literals import pytest from decimal import Decimal -from money import Money, CurrencyMismatchException +from money.money import Money, CurrencyMismatchException MONEY_STRINGS = [ @@ -32,11 +32,6 @@ def test_str(value, expected): assert str(value) == expected -@pytest.mark.parametrize("value,expected", MONEY_STRINGS) -def test_unicode(value, expected): - assert unicode(value) == expected - - @pytest.mark.parametrize("value,expected", MONEY_STRINGS) def test_repr(value, expected): assert repr(value) == expected diff --git a/money/tests/views.py b/money/tests/views.py index c6a594f..4bc8f29 100644 --- a/money/tests/views.py +++ b/money/tests/views.py @@ -1,20 +1,26 @@ +from __future__ import print_function from django import forms from django.shortcuts import render_to_response, get_object_or_404 -from money import Money +from money.money import Money from money.contrib.django.forms.fields import MoneyField from money.tests.models import SimpleMoneyModel -class TestForm(forms.Form): +class SampleForm(forms.Form): price = MoneyField() -class TestModelForm(forms.ModelForm): +class SampleModelForm(forms.ModelForm): class Meta: model = SimpleMoneyModel + fields = ( + 'name', + 'price', + ) + def instance_view(request): money = Money('0.0', 'JPY') @@ -32,8 +38,6 @@ def model_from_db_view(request, amount='0', currency='XXX'): instance = SimpleMoneyModel.objects.create(price=Money(amount, currency)) instance = SimpleMoneyModel.objects.get(pk=instance.pk) - print instance, instance.pk - money = instance.price return render_to_response('view.html', {'money': money}) @@ -41,51 +45,52 @@ def model_from_db_view(request, amount='0', currency='XXX'): def model_form_view(request, amount='0', currency='XXX'): cleaned_data = {} if request.method == 'POST': - form = TestModelForm(request.POST) + form = SampleModelForm(request.POST) if form.is_valid(): cleaned_data = form.cleaned_data form.save() # Most views would redirect here but we continue so we can render the data else: - form = TestModelForm(initial={'price': Money(amount, currency)}) + form = SampleModelForm(initial={'price': Money(amount, currency)}) return render_to_response('form.html', {'form': form, 'cleaned_data': cleaned_data}) def regular_form(request): if request.method == 'POST': - form = TestForm(request.POST) - print form.is_valid() + form = SampleForm(request.POST) + if form.is_valid(): price = form.cleaned_data['price'] - return render_to_response('form.html', {'price':price} ) + return render_to_response('form.html', {'price': price}) else: - form = TestForm() - return render_to_response('form.html', {'form':form} ) + form = SampleForm() + return render_to_response('form.html', {'form': form}) + def regular_form_edit(request, id): instance = get_object_or_404(SimpleMoneyModel, pk=id) if request.method == 'POST': - form = TestForm(request.POST, initial={'price':instance.price}) - print form.is_valid() + form = SampleForm(request.POST, initial={'price': instance.price}) + form = SampleForm(request.POST, initial={'price': instance.price}) + if form.is_valid(): price = form.cleaned_data['price'] - return render_to_response('form.html', {'price':price} ) + return render_to_response('form.html', {'price': price}) else: - form = TestForm(initial={'price':instance.price}) - return render_to_response('form.html', {'form':form} ) - + form = SampleForm(initial={'price': instance.price}) + return render_to_response('form.html', {'form': form}) def model_form_edit(request, id): instance = get_object_or_404(SimpleMoneyModel, pk=id) if request.method == 'POST': - form = TestModelForm(request.POST, instance=instance) - print form.is_valid() + form = SampleModelForm(request.POST, instance=instance) + if form.is_valid(): price = form.cleaned_data['price'] form.save() - return render_to_response('form.html', {'price':price} ) + return render_to_response('form.html', {'price': price}) else: - form = TestModelForm(instance=instance) - return render_to_response('form.html', {'form':form} ) + form = SampleModelForm(instance=instance) + return render_to_response('form.html', {'form': form}) diff --git a/setup.py b/setup.py index 598b038..e746c2c 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ def finalize_options(self): self.test_suite = True def run_tests(self): - #import here, cause outside the eggs aren't loaded + # import here, cause outside the eggs aren't loaded import pytest errno = pytest.main(self.test_args) sys.exit(errno) @@ -38,8 +38,9 @@ def run_tests(self): tests_require = [ 'pytest-django', 'pytest-cov', - 'django<1.7', + 'django<1.9', 'psycopg2', + 'six', ] requirements = [ @@ -47,7 +48,7 @@ def run_tests(self): ] extras_require = { - 'django': ['Django < 1.7', ], + 'django': ['Django < 1.8', ], } dependency_links = [] @@ -55,7 +56,6 @@ def run_tests(self): setup( name='python-money', version=get_git_version(), - source_label=get_git_hash(), description='Primitives for working with money and currencies in Python', url='http://github.com/poswald/python-money', maintainer='Paul Oswald', @@ -64,7 +64,6 @@ def run_tests(self): platforms=["any"], keywords=keywords, long_description=README, - #test_suite='tests', packages=[ 'money', ], diff --git a/version.py b/version.py index db3e65c..f75cef5 100644 --- a/version.py +++ b/version.py @@ -46,6 +46,7 @@ # first number: major business changes/milestones # second number: database changes # third number: code changes/patches +from __future__ import print_function, unicode_literals from subprocess import Popen, PIPE @@ -151,4 +152,4 @@ def get_git_hash(): return sha if __name__ == "__main__": - print get_git_version() + print(get_git_version())