]> asedeno.scripts.mit.edu Git - bluechips.git/blob - bluechips/controllers/spend.py
added XSRF protection to all forms and associated tests
[bluechips.git] / bluechips / controllers / spend.py
1 """
2 Handle expenditures
3 """
4
5 import logging
6
7 from decimal import Decimal, InvalidOperation
8
9 from bluechips.lib.base import *
10
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
15
16 from formencode import validators, Schema
17 from formencode.foreach import ForEach
18 from formencode.variabledecode import NestedVariables
19 from formencode.schema import SimpleFormValidator
20
21 from mailer import Message
22
23 log = logging.getLogger(__name__)
24
25
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)
31
32
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)
37
38
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]
49     
50
51 class SpendController(BaseController):
52     def index(self):
53         return self.edit()
54     
55     def edit(self, id=None):
56         c.users = meta.Session.query(model.User.id, model.User)
57         if id is None:
58             c.title = 'Add a New Expenditure'
59             c.expenditure = model.Expenditure()
60             c.expenditure.spender_id = request.environ['user'].id
61
62             num_residents = meta.Session.query(model.User).\
63                     filter_by(resident=True).count()
64             # Pre-populate split percentages for an even split.
65             c.values = {}
66             for ii, user_row in enumerate(c.users):
67                 user_id, user = user_row
68                 val = 0
69                 if user.resident:
70                     val = Decimal(100) / Decimal(num_residents)
71                 c.values['shares-%d.amount' % ii] = val
72         else:
73             c.title = 'Edit an Expenditure'
74             c.expenditure = meta.Session.query(model.Expenditure).get(id)
75             if c.expenditure is None:
76                 abort(404)
77             c.values = {}
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:
84                     percent = 0
85                 else:
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
90
91         return render('/spend/index.mako')
92
93     @redirect_on_get('edit')
94     @authenticate_form
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
98         # old one
99         if id is None:
100             e = model.Expenditure()
101             meta.Session.add(e)
102             op = 'created'
103         else:
104             e = meta.Session.query(model.Expenditure).get(id)
105             if e is None:
106                 abort(404)
107             op = 'updated'
108         
109         # Set the fields that were submitted
110         shares = self.form_result.pop('shares')
111         update_sar(e, self.form_result)
112
113         users = dict(meta.Session.query(model.User.id, model.User).all())
114         split_dict = {}
115         for share_params in shares:
116             user = users[share_params['user_id']]
117             split_dict[user] = Decimal(str(share_params['amount']))
118         e.split(split_dict)
119         
120         meta.Session.commit()
121        
122         show = ("Expenditure of %s paid for by %s %s." %
123                 (e.amount, e.spender, op))
124         h.flash(show)
125
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,
131                                   'op': op})
132         g.handle_notification(involved_users, show, body)
133
134         return h.redirect_to('/')