]> asedeno.scripts.mit.edu Git - bluechips.git/blob - bluechips/model/types.py
Added UI for working with tags
[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     def __div__(self, other):
126         """
127         If I don't define this, SmartSubclass will convert the other
128         argument to an int
129         """
130         return Currency(self.value / other)
131     def __truediv__(self, other):
132         """
133         If I don't define this, SmartSubclass will convert the other
134         argument to an int
135         """
136         return Currency(self.value / other)
137     
138     def __repr__(self):
139         return '%s("%s")' % (self.__class__.__name__, str(self))
140     def __str__(self):
141         return locale.currency(self.value / 100., grouping=True)
142
143
144 class DBCurrency(sa.types.TypeDecorator):
145     """
146     A type which represents monetary amounts internally as integers.
147     
148     This avoids binary/decimal float conversion issues
149     """
150     
151     impl = sa.types.Integer
152     
153     def process_bind_param(self, value, engine):
154         return int(value)
155     
156     def convert_result_value(self, value, engine):
157         return Currency(value)
158     process_result_value = convert_result_value