]> asedeno.scripts.mit.edu Git - bluechips.git/blobdiff - bluechips/model/types.py
Added UI for working with tags
[bluechips.git] / bluechips / model / types.py
index 1c54e764fb91e4d6907f68575cab22ffb6f59b17..59f3d35bf8d150e0cc368e939db1058f46b0378a 100644 (file)
 Define special types used in BlueChips
 """
 
+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)
-    def __init__(self, value):
-        if isinstance(value, str):
-            self.value = int(float(value) * 100)
+    __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:
-            self.value = int(value)
+            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):
-        try:
+        """
+        This is overridden for when validators compare a Currency to
+        ''
+        """
+        if other == '':
+            return 1
+        else:
             return self.value.__cmp__(int(other))
-        except:
-            return self.value.__cmp__(0)
     
     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 __str_no_dollar__(self):
-        return str(self)[1:]
+    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):
-        sign = '-' if self.value < 0 else ''
-        cents = abs(self.value) % 100
-        dollars = (abs(self.value) - cents) / 100
-        return '$%s%s.%.02d' % (sign, dollars, cents)
+        return locale.currency(self.value / 100., grouping=True)
+
 
 class DBCurrency(sa.types.TypeDecorator):
     """
@@ -56,3 +155,4 @@ class DBCurrency(sa.types.TypeDecorator):
     
     def convert_result_value(self, value, engine):
         return Currency(value)
+    process_result_value = convert_result_value