+from decimal import Decimal, InvalidOperation
+
+import sqlalchemy as sa
+from formencode import validators, Invalid
+from bluechips.lib.subclass import SmartSubclass
+
+from weakref import WeakValueDictionary
+
+def localeconv():
+ "Manually install en_US for systems that don't have it."
+ d = {'currency_symbol': '$',
+ 'decimal_point': '.',
+ 'frac_digits': 2,
+ 'grouping': [3, 3, 0],
+ 'int_curr_symbol': 'USD ',
+ 'int_frac_digits': 2,
+ 'mon_decimal_point': '.',
+ 'mon_grouping': [3, 3, 0],
+ 'mon_thousands_sep': ',',
+ 'n_cs_precedes': 1,
+ 'n_sep_by_space': 0,
+ 'n_sign_posn': 1,
+ 'negative_sign': '-',
+ 'p_cs_precedes': 1,
+ 'p_sep_by_space': 0,
+ 'p_sign_posn': 1,
+ 'positive_sign': '',
+ 'thousands_sep': ','}
+ return d
+locale.localeconv = localeconv
+
+
+class CurrencyValidator(validators.FancyValidator):
+ "A validator to convert to Currency objects."
+ messages = {'amount': "Please enter a valid currency amount",
+ 'precision': "Only two digits after the decimal, please",
+ 'nonzero': "Please enter a non-zero amount"}
+
+ def _to_python(self, value, state):
+ try:
+ dec = Decimal(value)
+ except InvalidOperation:
+ raise Invalid(self.message('amount', state),
+ value, state)
+ else:
+ ret = dec.quantize(Decimal('1.00'))
+ if ret == 0:
+ raise Invalid(self.message('nonzero', state),
+ value, state)
+ elif ret != dec:
+ raise Invalid(self.message('precision', state),
+ value, state)
+ else:
+ return Currency(int(ret * 100))
+
+
+class Currency(object):
+ """
+ Store currency values as an integral number of cents
+ """
+ __metaclass__ = SmartSubclass(int)
+ __old_values__ = WeakValueDictionary()
+ def __new__(cls, value):
+ if value is None:
+ value = 0
+ elif isinstance(value, str):
+ value = int(float(value) * 100)
+ else:
+ value = int(value)
+
+ if value not in cls.__old_values__:
+ new_object = super(cls, cls).__new__(cls)
+ new_object.value = value
+ cls.__old_values__[value] = new_object
+ return new_object
+ else:
+ return cls.__old_values__[value]
+
+ def __int__(self):
+ """
+ If I don't define this, SmartSubclass will return
+ Currency(int(self.value))
+ """
+ return self.value
+ def __float__(self):
+ """
+ If I don't define this, SmartSubclass will return
+ Currency(float(self.value))
+ """
+ return float(self.value)
+ def __long__(self):
+ """
+ If I don't define this, SmartSubclass will return
+ Currency(long(self.value))
+ """
+ return long(self.value)
+
+ def __cmp__(self, other):
+ """
+ This is overridden for when validators compare a Currency to
+ ''
+ """
+ if other == '':
+ return 1
+ else:
+ return self.value.__cmp__(int(other))
+
+ def __mul__(self, other):
+ """
+ If I don't define this, SmartSubclass will convert the other
+ argument to an int
+ """
+ return Currency(self.value * other)
+ def __rmul__(self, other):
+ """
+ If I don't define this, SmartSubclass will convert the other
+ argument to an int
+ """
+ return self.__mul__(other)
+ def __div__(self, other):
+ """
+ If I don't define this, SmartSubclass will convert the other
+ argument to an int
+ """
+ return Currency(self.value / other)
+ def __truediv__(self, other):
+ """
+ If I don't define this, SmartSubclass will convert the other
+ argument to an int
+ """
+ return Currency(self.value / other)
+
+ def __repr__(self):
+ return '%s("%s")' % (self.__class__.__name__, str(self))
+ def __str__(self):
+ return locale.currency(self.value / 100., grouping=True)
+