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 import validate
13 from pylons.decorators.secure import authenticate_form
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(not_empty=True)
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
70 val = Decimal(100) / Decimal(num_residents)
71 c.values['shares-%d.amount' % ii] = val
73 c.title = 'Edit an Expenditure'
74 c.expenditure = meta.Session.query(model.Expenditure).get(id)
75 if c.expenditure is None:
78 for ii, user_row in enumerate(c.users):
79 user_id, user = user_row
80 shares_by_user = dict(((sp.user, sp.share) for sp
81 in c.expenditure.splits))
82 share = shares_by_user.get(user, 0)
83 if c.expenditure.amount == 0:
86 percent = (Decimal(100) * Decimal(int(share)) /
87 Decimal(int(c.expenditure.amount))).\
88 quantize(Decimal("0.001"))
89 c.values['shares-%d.amount' % ii] = percent
91 return render('/spend/index.mako')
93 @redirect_on_get('edit')
95 @validate(schema=ExpenditureSchema(), form='edit', variable_decode=True)
96 def update(self, id=None):
97 # Either create a new object, or, if we're editing, get the
100 e = model.Expenditure()
104 e = meta.Session.query(model.Expenditure).get(id)
109 # Set the fields that were submitted
110 shares = self.form_result.pop('shares')
111 update_sar(e, self.form_result)
113 users = dict(meta.Session.query(model.User.id, model.User).all())
115 for share_params in shares:
116 user = users[share_params['user_id']]
117 split_dict[user] = Decimal(str(share_params['amount']))
120 meta.Session.commit()
122 show = ("Expenditure of %s paid for by %s %s." %
123 (e.amount, e.spender, op))
126 # Send email notification to involved users if they have an email set.
127 involved_users = set(sp.user for sp in e.splits if sp.share != 0)
128 involved_users.add(e.spender)
129 body = render('/emails/expenditure.txt',
130 extra_vars={'expenditure': e,
132 g.handle_notification(involved_users, show, body)
134 return h.redirect_to('/')
136 def delete(self, id):
137 c.title = 'Delete an Expenditure'
138 c.expenditure = meta.Session.query(model.Expenditure).get(id)
139 if c.expenditure is None:
142 return render('/spend/delete.mako')
144 @redirect_on_get('delete')
146 def destroy(self, id):
147 e = meta.Session.query(model.Expenditure).get(id)
151 if 'delete' in request.params:
152 meta.Session.delete(e)
154 meta.Session.commit()
155 show = ("Expenditure of %s paid for by %s deleted." %
156 (e.amount, e.spender))
159 involved_users = set(sp.user for sp in e.splits if sp.share != 0)
160 involved_users.add(e.spender)
161 body = render('/emails/expenditure.txt',
162 extra_vars={'expenditure': e,
164 g.handle_notification(involved_users, show, body)
166 return h.redirect_to('/')