]> asedeno.scripts.mit.edu Git - bluechips.git/commitdiff
Switch from using Decimal to using a new Currency class
authorEvan Broder <broder@mit.edu>
Fri, 18 Jul 2008 07:51:47 +0000 (07:51 +0000)
committerEvan Broder <broder@mit.edu>
Fri, 18 Jul 2008 07:51:47 +0000 (07:51 +0000)
 * Include the SmartSubclass metaclass for making an object that is
   itself but acts like something else
 * Derive a Currency class from that
 * Use it EVERYWHERE!

bluechips/controllers/status.py
bluechips/lib/helpers.py
bluechips/lib/subclass.py [new file with mode: 0644]
bluechips/lib/totals.py
bluechips/model/__init__.py
bluechips/model/expenditure.py
bluechips/model/types.py
bluechips/templates/base.mako
bluechips/templates/status/index.mako
bluechips/widgets/__init__.py

index 5d92d2f26194aa670ce2a887ee9732f35310ea2b..f3373ddc398ba93fcb3c6ebbc512d80be532189a 100644 (file)
@@ -10,7 +10,8 @@ from bluechips.lib.totals import *
 import sqlalchemy
 
 from datetime import date, timedelta
-from decimal import Decimal
+
+from bluechips.model.types import Currency
 
 from pylons import request
 
@@ -49,7 +50,7 @@ class StatusController(BaseController):
         return render('/status/index.mako')
     
     def _total(self, where):
-        return (meta.Session.execute(sqlalchemy.sql.select([
+        return Currency(meta.Session.execute(sqlalchemy.sql.select([
                 sqlalchemy.func.sum(model.expenditures.c.amount).\
                     label('total')]).\
-                    where(where)).scalar() or Decimal("0.00")) / 100
+                    where(where)).scalar() or 0)
index 4bb357663737b58c3dc15477f3fa77a8629e0529..1b8771bc790972d150b4db5b874a6d8a53604e8f 100644 (file)
@@ -14,7 +14,4 @@ from decimal import Decimal
 def bluechips():
     return '<span class="bluechips">BlueChips</span>'
 
-def round_currency(value):
-    return Decimal(value).quantize(Decimal('0.01'))
-
 flash = _Flash()
diff --git a/bluechips/lib/subclass.py b/bluechips/lib/subclass.py
new file mode 100644 (file)
index 0000000..3511d8e
--- /dev/null
@@ -0,0 +1,22 @@
+"""
+Create subclasses that call out to their "superclass" for all methods
+but return the "subclass's" type
+"""
+
+def wrapper(c, func):
+    return (lambda self,*args: c(getattr(self.value, func)(*map(self.value.__class__, args))))
+
+class SmartSubclass(object):
+    def __init__(self, superclass, exclude=[]):
+        self.superclass = superclass
+        self.exclude = exclude
+    def __call__(self, name, bases, dict):
+        c = type(name, bases, dict)
+        for func in dir(self.superclass):
+            if func not in dir(c) and \
+                callable(getattr(self.superclass, func)) and \
+                func not in self.exclude:
+                setattr(c, func, wrapper(c, func))
+        return c
+
+__all__ = ['SmartSubclass']
index 3f18a32d115d46ddee1fce3017412074a6a5a6d5..53392c87060df3d7c5b303e3b256946331541f17 100644 (file)
@@ -5,9 +5,9 @@ Calculate the total state of the books
 from bluechips import model
 from bluechips.model import meta
 
-import sqlalchemy
+from bluechips.model.types import Currency
 
-from decimal import Decimal
+import sqlalchemy
 
 class DirtyBooks(Exception):
     """
@@ -25,7 +25,7 @@ def debts():
     
     # First, credit everyone for expenditures they've made
     for user in users:
-        debts[user] = -sum(map((lambda x: x.amount), user.expenditures))
+        debts[user] = Currency(-sum(map((lambda x: x.amount), user.expenditures)))
     
     # Next, debit everyone for expenditures that they have an
     # investment in (i.e. splits)
@@ -35,7 +35,7 @@ def debts():
         group_by(model.Split.user_id)
     
     for split, total_cents in total_splits:
-        debts[split.user] += (total_cents / 100)
+        debts[split.user] += total_cents
     
     # Finally, move transfers around appropriately
     #
@@ -48,9 +48,9 @@ def debts():
     total_credits = transfer_q.group_by(model.Transfer.creditor_id)
     
     for transfer, total_amount in total_debits:
-        debts[transfer.debtor] -= (total_amount / 100)
+        debts[transfer.debtor] -= total_amount
     for transfer, total_amount in total_credits:
-        debts[transfer.creditor] += (total_amount / 100)
+        debts[transfer.creditor] += total_amount
     
     return debts
 
index 9261b971a794847ac18566a24715d550bb18d396..a5ab0a0e74a9a2daa6364e25a29618e6faf8afc9 100644 (file)
@@ -34,7 +34,7 @@ expenditures = sa.Table('expenditures', meta.metadata,
                         sa.Column('id', sa.types.Integer, primary_key=True),
                         sa.Column('spender_id', sa.types.Integer,
                                   sa.ForeignKey('users.id'), nullable=False),
-                        sa.Column('amount', types.Currency, nullable=False),
+                        sa.Column('amount', types.DBCurrency, nullable=False),
                         sa.Column('description', sa.types.Text),
                         sa.Column('date', sa.types.Date, default=datetime.now),
                         sa.Column('entered_time', sa.types.DateTime, 
@@ -47,7 +47,7 @@ splits = sa.Table('splits', meta.metadata,
                             sa.ForeignKey('expenditures.id'), nullable=False),
                   sa.Column('user_id', sa.types.Integer,
                             sa.ForeignKey('users.id'), nullable=False),
-                  sa.Column('share', types.Currency, nullable=False)
+                  sa.Column('share', types.DBCurrency, nullable=False)
                   )
 
 subitems = sa.Table('subitems', meta.metadata,
@@ -56,7 +56,7 @@ subitems = sa.Table('subitems', meta.metadata,
                               sa.ForeignKey('expenditures.id'), nullable=False),
                     sa.Column('user_id', sa.types.Integer,
                               sa.ForeignKey('users.id'), nullable=False),
-                    sa.Column('amount', types.Currency, nullable=False)
+                    sa.Column('amount', types.DBCurrency, nullable=False)
                     )
 
 transfers = sa.Table('transfers', meta.metadata,
@@ -65,7 +65,7 @@ transfers = sa.Table('transfers', meta.metadata,
                                sa.ForeignKey('users.id'), nullable=False),
                      sa.Column('creditor_id', sa.types.Integer,
                                sa.ForeignKey('users.id'), nullable=False),
-                     sa.Column('amount', types.Currency, nullable=False),
+                     sa.Column('amount', types.DBCurrency, nullable=False),
                      sa.Column('description', sa.Text, default=None),
                      sa.Column('date', sa.types.Date, default=datetime.now),
                      sa.Column('entered_time', sa.types.DateTime,
index 88d65b3b6ae52649cd62bc1723a4c69a981d671f..252a93b714025eb42a98663d808d3fb6c92d1baf 100644 (file)
@@ -1,7 +1,7 @@
 from user import User
 from split import Split
 from bluechips.model import meta
-from bluechips.lib.helpers import round_currency
+from bluechips.model.types import Currency
 from decimal import Decimal
 import random
 
@@ -47,18 +47,18 @@ class Expenditure(object):
         amounts_dict = dict()
         
         for user, share in split_dict.iteritems():
-            amounts_dict[user] = round_currency(split_dict[user] * self.amount)
+            amounts_dict[user] = Currency(split_dict[user] * self.amount)
         
         difference = self.amount - sum(amounts_dict.itervalues())
         
         if difference > 0:
             for i in xrange(difference * 100):
                 winner = random.choice(amounts_dict.keys())
-                amounts_dict[winner] += Decimal('0.01')
+                amounts_dict[winner] += Currency(1)
         elif difference < 0:
             for i in xrange(difference * -100):
                 winner = random.choice(amounts_dict.keys())
-                amounts_dict[winner] -= Decimal('0.01')
+                amounts_dict[winner] -= Currency(1)
         
         for user, share in amounts_dict.iteritems():
             s = Split()
index a81a8ae6ce09db566aaf395d0341c6ef7c6b0055..1c54e764fb91e4d6907f68575cab22ffb6f59b17 100644 (file)
@@ -3,10 +3,46 @@ Define special types used in BlueChips
 """
 
 import sqlalchemy as sa
-from bluechips.lib.helpers import round_currency
-from decimal import Decimal
+from bluechips.lib.subclass import SmartSubclass
 
-class Currency(sa.types.TypeDecorator):
+class Currency(object):
+    __metaclass__ = SmartSubclass(int)
+    def __init__(self, value):
+        if isinstance(value, str):
+            self.value = int(float(value) * 100)
+        else:
+            self.value = int(value)
+    
+    def __int__(self):
+        return self.value
+    def __float__(self):
+        return float(self.value)
+    def __long__(self):
+        return long(self.value)
+    
+    def __cmp__(self, other):
+        try:
+            return self.value.__cmp__(int(other))
+        except:
+            return self.value.__cmp__(0)
+    
+    def __mul__(self, other):
+        return Currency(self.value * other)
+    def __rmul__(self, other):
+        return self.__mul__(other)
+    
+    def __str_no_dollar__(self):
+        return str(self)[1:]
+    
+    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)
+
+class DBCurrency(sa.types.TypeDecorator):
     """
     A type which represents monetary amounts internally as integers.
     
@@ -16,7 +52,7 @@ 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)
index a9865c1f2ae8c277bcdc8d2f405b491d24bfbf11..1aa31cb7bf1030d98bf337fdbbda4d8101089cee 100644 (file)
@@ -49,7 +49,7 @@
         <td>${e.date}</td>
         <td>${e.spender.name}</td>
         <td>${e.description}</td>
-        <td>$${h.round_currency(e.amount)}</td>
+        <td>${e.amount}</td>
     </tr>
     % endfor
 </table>
@@ -70,7 +70,7 @@
         <td>${t.debtor.name}</td>
         <td>${t.creditor.name}</td>
         <td>${t.description}</td>
-        <td>$${h.round_currency(t.amount)}</td>
+        <td>${t.amount}</td>
     </tr>
     % endfor
 </table>
index 060bd4e48471f8182da875020273e49f43700ffe..5e9539dabf3f79565940f06f92a75e2e6d69cab4 100644 (file)
@@ -17,7 +17,7 @@
     <tr>
         <td>${transfer[0].username}</td>
         <td>${transfer[1].username}</td>
-        <td>$${h.round_currency(transfer[2])}</td>
+        <td>${transfer[2]}</td>
     </tr>
     % endfor
 </table>
 <table>
     <tr>
         <td>Total</td>
-        <td>$${h.round_currency(c.total)}</td>
+        <td>${c.total}</td>
     </tr>
     <tr>
         <td>Past year</td>
-        <td>$${h.round_currency(c.year_total)}</td>
+        <td>${c.year_total}</td>
     </tr>
     <tr>
         <td>Year to date</td>
-        <td>$${h.round_currency(c.this_year_total)}</td>
+        <td>${c.this_year_total}</td>
     </tr>
     <tr>
         <td>Month to date</td>
-        <td>$${h.round_currency(c.this_month_total)}</td>
+        <td>${c.this_month_total}</td>
     </tr>
     <tr>
         <td>Last month</td>
-        <td>$${h.round_currency(c.last_month_total)}</td>
+        <td>${c.last_month_total}</td>
     </tr>
 </table>
 
index 3509ee2cf478a83080dcdfe103c1299e5560d0b3..545b88e8a217302ee7d6153248b24ae6adefaa10 100644 (file)
@@ -5,7 +5,7 @@ from tw.forms import validators
 from bluechips import model
 from bluechips.model import meta
 
-from decimal import Decimal
+from bluechips.model.types import Currency
 
 class UserSelect(forms.SingleSelectField):
     @staticmethod
@@ -28,9 +28,9 @@ class AmountField(forms.TextField):
     size = 8
     validator = validators.All(
         validators.Wrapper(
-            to_python=Decimal,
-            from_python=str),
-        validators.Regex(r'^[0-9]*(\.[0-9]{2})?$', not_empty=True))
+            to_python=(lambda x: Currency(float(x) * 100)),
+            from_python=Currency.__str_no_dollar__),
+        validators.Regex(r'^[0-9]*(\.[0-9]{2})?$'))
 
 # This is virtually copied from formencode.validator.FieldsMatch, but
 # I wanted my own version for fields that shouldn't match