]> asedeno.scripts.mit.edu Git - bluechips.git/blob - bluechips/model/types.py
5423de9a2ea47188aa8fe02806953a9215414634
[bluechips.git] / bluechips / model / types.py
1 """
2 Define special types used in BlueChips
3 """
4
5 import locale
6 from decimal import Decimal, InvalidOperation
7
8 import sqlalchemy as sa
9 from formencode import validators, Invalid
10 from bluechips.lib.subclass import SmartSubclass
11
12 from weakref import WeakValueDictionary
13
14 def localeconv():
15     "Manually install en_US for systems that don't have it."
16     d = {'currency_symbol': '$',
17      'decimal_point': '.',
18      'frac_digits': 2,
19      'grouping': [3, 3, 0],
20      'int_curr_symbol': 'USD ',
21      'int_frac_digits': 2,
22      'mon_decimal_point': '.',
23      'mon_grouping': [3, 3, 0],
24      'mon_thousands_sep': ',',
25      'n_cs_precedes': 1,
26      'n_sep_by_space': 0,
27      'n_sign_posn': 1,
28      'negative_sign': '-',
29      'p_cs_precedes': 1,
30      'p_sep_by_space': 0,
31      'p_sign_posn': 1,
32      'positive_sign': '',
33      'thousands_sep': ','}
34     return d
35 locale.localeconv = localeconv
36
37
38 class CurrencyValidator(validators.FancyValidator):
39     "A validator to convert to Currency objects."
40     messages = {'amount': "Please enter a valid currency amount",
41                 'precision': "Only two digits after the decimal, please",
42                 'nonzero': "Please enter a non-zero amount"}
43
44     def _to_python(self, value, state):
45         try:
46             dec = Decimal(value)
47         except InvalidOperation:
48             raise Invalid(self.message('amount', state),
49                           value, state)
50         else:
51             ret = dec.quantize(Decimal('1.00'))
52             if ret == 0:
53                 raise Invalid(self.message('nonzero', state),
54                               value, state)
55             elif ret != dec:
56                 raise Invalid(self.message('precision', state),
57                               value, state)
58             else:
59                 return Currency(int(ret * 100))
60
61
62 class Currency(object):
63     """
64     Store currency values as an integral number of cents
65     """
66     __metaclass__ = SmartSubclass(int)
67     __old_values__ = WeakValueDictionary()
68     def __new__(cls, value):
69         if value is None:
70             value = 0
71         elif isinstance(value, str):
72             value = int(float(value) * 100)
73         else:
74             value = int(value)
75         
76         if value not in cls.__old_values__:
77             new_object = super(cls, cls).__new__(cls)
78             new_object.value = value
79             cls.__old_values__[value] = new_object
80             return new_object
81         else:
82             return cls.__old_values__[value]
83     
84     def __int__(self):
85         """
86         If I don't define this, SmartSubclass will return
87         Currency(int(self.value))
88         """
89         return self.value
90     def __float__(self):
91         """
92         If I don't define this, SmartSubclass will return
93         Currency(float(self.value))
94         """
95         return float(self.value)
96     def __long__(self):
97         """
98         If I don't define this, SmartSubclass will return
99         Currency(long(self.value))
100         """
101         return long(self.value)
102     
103     def __cmp__(self, other):
104         """
105         This is overridden for when validators compare a Currency to
106         ''
107         """
108         if other == '':
109             return 1
110         else:
111             return self.value.__cmp__(int(other))
112     
113     def __mul__(self, other):
114         """
115         If I don't define this, SmartSubclass will convert the other
116         argument to an int
117         """
118         return Currency(self.value * other)
119     def __rmul__(self, other):
120         """
121         If I don't define this, SmartSubclass will convert the other
122         argument to an int
123         """
124         return self.__mul__(other)
125     
126     def __str_no_dollar__(self):
127         """
128         Get to the formatted string without the dollar sign
129         """
130         return str(self).replace('$', '')
131     
132     def __repr__(self):
133         return '%s("%s")' % (self.__class__.__name__, str(self))
134     def __str__(self):
135         return locale.currency(self.value / 100., grouping=True)
136
137
138 class DBCurrency(sa.types.TypeDecorator):
139     """
140     A type which represents monetary amounts internally as integers.
141     
142     This avoids binary/decimal float conversion issues
143     """
144     
145     impl = sa.types.Integer
146     
147     def process_bind_param(self, value, engine):
148         return int(value)
149     
150     def convert_result_value(self, value, engine):
151         return Currency(value)
152     process_result_value = convert_result_value