X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=bluechips%2Fmodel%2Ftypes.py;h=59f3d35bf8d150e0cc368e939db1058f46b0378a;hb=f03d48ccc07789e1c56b07cec50820f3ad26f8ba;hp=d4ff40142e4cc0724a1b0d39c7037c425d8f7f50;hpb=040562cf56afa0d29449efea4e5a3ad60b6f1338;p=bluechips.git diff --git a/bluechips/model/types.py b/bluechips/model/types.py index d4ff401..59f3d35 100644 --- a/bluechips/model/types.py +++ b/bluechips/model/types.py @@ -2,11 +2,146 @@ Define special types used in BlueChips """ -import sqlalchemy as sa -from bluechips.lib.helpers import round_currency import locale +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) + -class Currency(sa.types.TypeDecorator): +class DBCurrency(sa.types.TypeDecorator): """ A type which represents monetary amounts internally as integers. @@ -16,7 +151,8 @@ class Currency(sa.types.TypeDecorator): impl = sa.types.Integer def process_bind_param(self, value, engine): - return int(value * 100) + return int(value) def convert_result_value(self, value, engine): - return round_currency(Decimal(value) / 100) + return Currency(value) + process_result_value = convert_result_value