import sqlalchemy
from datetime import date, timedelta
-from decimal import Decimal
+
+from bluechips.model.types import Currency
from pylons import request
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)
def bluechips():
return '<span class="bluechips">BlueChips</span>'
-def round_currency(value):
- return Decimal(value).quantize(Decimal('0.01'))
-
flash = _Flash()
--- /dev/null
+"""
+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']
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):
"""
# 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)
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
#
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
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,
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,
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,
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,
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
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()
"""
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.
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)
<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>
<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>
<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>
from bluechips import model
from bluechips.model import meta
-from decimal import Decimal
+from bluechips.model.types import Currency
class UserSelect(forms.SingleSelectField):
@staticmethod
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