7 from decimal import Decimal, InvalidOperation
9 from bluechips.lib.base import *
11 from pylons import request, app_globals as g
12 from pylons.decorators.rest import dispatch_on
13 from pylons.decorators import validate
14 from pylons.controllers.util import abort
16 from formencode import validators, Schema
17 from formencode.foreach import ForEach
18 from formencode.variabledecode import NestedVariables
19 from formencode.schema import SimpleFormValidator
21 from mailer import Message
23 log = logging.getLogger(__name__)
26 class ShareSchema(Schema):
27 "Validate individual user shares."
28 allow_extra_fields = False
29 user_id = validators.Int(not_empty=True)
30 amount = validators.Number(not_empty=True)
33 def validate_state(value_dict, state, validator):
34 if all(s['amount'] == 0 for s in value_dict['shares']):
35 return {'shares-0.amount': 'Need at least one non-zero share'}
36 ValidateNotAllZero = SimpleFormValidator(validate_state)
39 class ExpenditureSchema(Schema):
40 "Validate an expenditure."
41 allow_extra_fields = False
42 pre_validators = [NestedVariables()]
43 spender_id = validators.Int(not_empty=True)
44 amount = model.types.CurrencyValidator(not_empty=True)
45 description = validators.UnicodeString()
46 date = validators.DateConverter()
47 shares = ForEach(ShareSchema)
48 chained_validators = [ValidateNotAllZero]
51 class SpendController(BaseController):
55 def edit(self, id=None):
56 c.users = meta.Session.query(model.User.id, model.User)
58 c.title = 'Add a New Expenditure'
59 c.expenditure = model.Expenditure()
60 c.expenditure.spender_id = request.environ['user'].id
62 num_residents = meta.Session.query(model.User).\
63 filter_by(resident=True).count()
64 # Pre-populate split percentages for an even split.
66 for ii, user_row in enumerate(c.users):
67 user_id, user = user_row
69 val = Decimal(100) / Decimal(num_residents)
72 c.values['shares-%d.amount' % ii] = val
74 c.title = 'Edit an Expenditure'
75 c.expenditure = meta.Session.query(model.Expenditure).get(id)
76 if c.expenditure is None:
79 for ii, user_row in enumerate(c.users):
80 user_id, user = user_row
82 share = [s.share for s in c.expenditure.splits
84 if c.expenditure.amount == 0:
87 percent = (Decimal(100) * Decimal(int(share)) /
88 Decimal(int(c.expenditure.amount))).\
89 quantize(Decimal("0.001"))
92 c.values['shares-%d.amount' % ii] = percent
94 return render('/spend/index.mako')
96 @redirect_on_get('edit')
97 @validate(schema=ExpenditureSchema(), form='edit', variable_decode=True)
98 def update(self, id=None):
99 # Either create a new object, or, if we're editing, get the
102 e = model.Expenditure()
106 e = meta.Session.query(model.Expenditure).get(id)
111 # Set the fields that were submitted
112 shares = self.form_result.pop('shares')
113 update_sar(e, self.form_result)
115 users = dict(meta.Session.query(model.User.id, model.User).all())
117 for share_params in shares:
118 user = users[share_params['user_id']]
119 split_dict[user] = Decimal(str(share_params['amount']))
122 meta.Session.commit()
124 show = ("Expenditure of %s paid for by %s %s." %
125 (e.amount, e.spender, op))
128 # Send email notification to involved users if they have an email set.
129 involved_users = set(sp.user for sp in e.splits if sp.share != 0)
130 involved_users.add(e.spender)
131 body = render('/emails/expenditure.txt',
132 extra_vars={'expenditure': e,
134 g.handle_notification(involved_users, show, body)
136 return h.redirect_to('/')