Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RELEASE-VERSION
dist
*.egg-info
*.egg
*.eggs
*.komodoproject
*.esproj
*.pyc
Expand Down
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: python
python:
- "2.7"
- "3.5"
script: python setup.py test
services:
- postgresql
3 changes: 0 additions & 3 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -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]
55 changes: 26 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@

python-money
============

Primitives for working with money and currencies in Python


Compatibility
=============

Expand All @@ -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
============

Expand All @@ -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
=====

Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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')
Expand Down Expand Up @@ -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
======

Expand All @@ -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
Expand All @@ -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:

...
{
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion money/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from money import *
from .money import *
4 changes: 2 additions & 2 deletions money/contrib/django/forms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from fields import *
from widgets import *
from .fields import *
from .widgets import *
4 changes: 2 additions & 2 deletions money/contrib/django/forms/fields.py
Original file line number Diff line number Diff line change
@@ -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):
Expand Down
22 changes: 11 additions & 11 deletions money/contrib/django/models/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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)

Expand Down Expand Up @@ -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),
)
Expand All @@ -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):
Expand Down
10 changes: 5 additions & 5 deletions money/contrib/django/models/managers.py
Original file line number Diff line number Diff line change
@@ -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',)

Expand All @@ -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):
Expand All @@ -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

Expand Down Expand Up @@ -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)
Loading