from decimal import Decimal, ROUND_HALF_UP

from django.db import models


class Money(Decimal):
    def __init__(self, amount):
        self.amount = sanitize(amount)

    def __str__(self):
        return '$%s' % self.amount # TODO: use babel

    def __repr__(self):
        return 'Money(%s)' % self

    def __eq__(self, other):
        if isinstance(other, Money):
            return self.amount == other.amount
        elif isinstance(other, (int, long, float, Decimal)):
            return self.amount == sanitize(other)
        else:
            return False

# Set up money arithmetic
def sanitize(amount):
    return Decimal(amount).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

def make_method(name):
    return lambda self, other: Money(getattr(self.amount, name)(Decimal(other)))

ops = 'add radd sub rsub mul rmul floordiv rfloordiv truediv rtruediv div rdiv mod rmod'
for op in ops.split():
    name = '__%s__' % op
    maker = make_compare if op in {'eq', 'ne'} else make_method
    setattr(Money, name, maker(name))


class MoneyField(models.DecimalField):
    __metaclass__ = models.SubfieldBase

    # NOTE: we specify default value for max_digits for extra ease
    def __init__(self, verbose_name=None, name=None, max_digits=12, **kwargs):
        self.max_digits, self.decimal_places = max_digits, 2
        models.Field.__init__(self, verbose_name, name, **kwargs)

    def to_python(self, value):
        value = models.DecimalField.to_python(self, value)
        if value is None:
            return value

        return Money(value)

    def get_prep_value(self, value):
        if value is None:
            return None
        return self.to_python(value).amount

from decimal import Decimal, ROUND_HALF_UP

from django.db import models


class Money(Decimal):
    def __new__(cls, amount):
        return Decimal.__new__(Money, _sanitize(amount))

    def __str__(self):
        return '$%s' % Decimal(self) # TODO: use babel

    def __repr__(self):
        return 'Money(%s)' % self

    def __eq__(self, other):
        if isinstance(other, Money):
            return Decimal.__eq__(self, other)
        elif isinstance(other, (int, long, float, Decimal)):
            return Decimal.__eq__(self, _sanitize(other))
        else:
            return False

# Set up money arithmetic
def _to_decimal(amount):
    if isinstance(amount, Decimal):
        return amount
    elif isinstance(amount, float):
        return Decimal.from_float(amount)
    else:
        return Decimal(amount)

def _sanitize(amount):
    return _to_decimal(amount).quantize(Decimal('0.01'), rounding=ROUND_HALF_UP)

def _make_method(name):
    method = getattr(Decimal, name)
    return lambda self, other, context=None: \
        Money(method(self, _to_decimal(other), context=context))

ops = 'add radd sub rsub mul rmul floordiv rfloordiv truediv rtruediv div rdiv mod rmod'
for op in ops.split():
    name = '__%s__' % op
    maker = make_compare if op in {'eq', 'ne'} else _make_method
    setattr(Money, name, maker(name))


class MoneyField(models.DecimalField):
    __metaclass__ = models.SubfieldBase

    # NOTE: we specify default value for max_digits for extra ease
    def __init__(self, verbose_name=None, name=None, max_digits=12, **kwargs):
        self.max_digits, self.decimal_places = max_digits, 2
        models.Field.__init__(self, verbose_name, name, **kwargs)

    def to_python(self, value):
        value = models.DecimalField.to_python(self, value)
        if value is None:
            return value

        return Money(value)

    def get_prep_value(self, value):
        if value is None:
            return None
        return Decimal(self.to_python(value))


Reimplement Money as true Decimal descendant