Skip to content

Commit

Permalink
Merge pull request #554 from wearebasti/bugfix-decimal-places-522
Browse files Browse the repository at this point in the history
fix: inherit decimal places from arguments in arithmetic operations #522
  • Loading branch information
benjaoming committed Jun 10, 2020
2 parents ab4241f + 3dc9e7d commit 58e1a6a
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 4 deletions.
29 changes: 25 additions & 4 deletions djmoney/money.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,40 @@ def __init__(self, *args, **kwargs):
self.decimal_places = kwargs.pop("decimal_places", DECIMAL_PLACES)
super().__init__(*args, **kwargs)

def _fix_decimal_places(self, *args):
""" Make sure to user 'biggest' number of decimal places of all given money instances """
candidates = (getattr(candidate, "decimal_places", 0) for candidate in args)
return max([self.decimal_places, *candidates])

def __add__(self, other):
if isinstance(other, F):
return other.__radd__(self)
other = maybe_convert(other, self.currency)
return super().__add__(other)
result = super().__add__(other)
result.decimal_places = self._fix_decimal_places(other)
return result

def __sub__(self, other):
if isinstance(other, F):
return other.__rsub__(self)
other = maybe_convert(other, self.currency)
return super().__sub__(other)
result = super().__sub__(other)
result.decimal_places = self._fix_decimal_places(other)
return result

def __mul__(self, other):
if isinstance(other, F):
return other.__rmul__(self)
return super().__mul__(other)
result = super().__mul__(other)
result.decimal_places = self._fix_decimal_places(other)
return result

def __truediv__(self, other):
if isinstance(other, F):
return other.__rtruediv__(self)
return super().__truediv__(other)
result = super().__truediv__(other)
result.decimal_places = self._fix_decimal_places(other)
return result

@property
def is_localized(self):
Expand All @@ -70,6 +83,14 @@ def __round__(self, n=None):
amount = round(self.amount, n)
return self.__class__(amount, self.currency)

# DefaultMoney sets those synonym functions
# we overwrite the 'targets' so the wrong synonyms are called
# Example: we overwrite __add__; __radd__ calls __add__ on DefaultMoney...
__radd__ = __add__
__rsub__ = __sub__
__rmul__ = __mul__
__rtruediv__ = __truediv__


def get_current_locale():
# get_language can return None starting from Django 1.8
Expand Down
11 changes: 11 additions & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
Changelog
=========

`1.2`_ - TBA
------------

Fixed
~~~~~

- Resulting Money object from arithmetics (add / sub / ...) inherits maximum decimal_places from arguments `#522`_ (`wearebasti`_)


`1.1`_ - 2020-04-06
-------------------

Expand Down Expand Up @@ -786,6 +795,7 @@ Added
.. _#418: https://github.com/django-money/django-money/issues/418
.. _#411: https://github.com/django-money/django-money/issues/411
.. _#519: https://github.com/django-money/django-money/issues/519
.. _#522: https://github.com/django-money/django-money/issues/522

.. _77cc33: https://github.com/77cc33
.. _AlexRiina: https://github.com/AlexRiina
Expand Down Expand Up @@ -859,3 +869,4 @@ Added
.. _humrochagf: https://github.com/humrochagf
.. _washeck: https://github.com/washeck
.. _fara: https://github.com/fara
.. _wearebasti: https://github.com/wearebasti
42 changes: 42 additions & 0 deletions tests/test_money.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,45 @@ def test_round():
def test_configurable_decimal_number():
# Override default configuration per instance
assert str(Money("10.543", "USD", decimal_places=3)) == "$10.543"


def test_add_decimal_places():
one = Money("1.0000", "USD", decimal_places=4)
two = Money("2.000000005", "USD", decimal_places=10)

result = one + two
assert result.decimal_places == 10


def test_add_decimal_places_zero():
two = Money("2.005", "USD", decimal_places=3)

result = two + 0
assert result.decimal_places == 3


def test_mul_decimal_places():
""" Test __mul__ and __rmul__ """
two = Money("1.0000", "USD", decimal_places=4)

result = 2 * two
assert result.decimal_places == 4

result = two * 2
assert result.decimal_places == 4


def test_fix_decimal_places():
one = Money(1, "USD", decimal_places=7)
assert one._fix_decimal_places(Money(2, "USD", decimal_places=3)) == 7
assert one._fix_decimal_places(Money(2, "USD", decimal_places=30)) == 30


def test_fix_decimal_places_none():
one = Money(1, "USD", decimal_places=7)
assert one._fix_decimal_places(None) == 7


def test_fix_decimal_places_multiple():
one = Money(1, "USD", decimal_places=7)
assert one._fix_decimal_places(None, Money(3, "USD", decimal_places=8)) == 8

0 comments on commit 58e1a6a

Please sign in to comment.