1
|
|
|
from pyramid.events import subscriber |
2
|
|
|
from pyramid.events import BeforeRender |
3
|
|
|
from pyramid.httpexceptions import HTTPFound |
4
|
|
|
from pyramid.renderers import render |
5
|
|
|
from pyramid.renderers import render_to_response |
6
|
|
|
from pyramid.response import Response |
7
|
|
|
from pyramid.response import FileResponse |
8
|
|
|
from pyramid.view import view_config, forbidden_view_config |
9
|
|
|
|
10
|
|
|
from sqlalchemy.sql import func |
11
|
|
|
from sqlalchemy.exc import DBAPIError, IntegrityError |
12
|
|
|
from sqlalchemy.orm.exc import NoResultFound |
13
|
|
|
|
14
|
|
|
from . import views_data |
15
|
|
|
|
16
|
|
|
from .models import * |
17
|
|
|
from .models.model import * |
18
|
|
|
from .models import user as __user |
19
|
|
|
from .models.user import User |
20
|
|
|
from .models.item import Item |
21
|
|
|
from .models.box import Box |
22
|
|
|
from .models.box_item import BoxItem |
23
|
|
|
from .models.transaction import Transaction, Deposit, CashDeposit, CCDeposit, BTCDeposit, Purchase |
24
|
|
|
from .models.transaction import Inventory, InventoryLineItem |
25
|
|
|
from .models.transaction import PurchaseLineItem, SubTransaction, SubSubTransaction |
26
|
|
|
from .models.account import Account, VirtualAccount, CashAccount |
27
|
|
|
from .models.event import Event |
28
|
|
|
from .models import event as __event |
29
|
|
|
from .models.vendor import Vendor |
30
|
|
|
from .models.item_vendor import ItemVendor |
31
|
|
|
from .models.box_vendor import BoxVendor |
32
|
|
|
from .models.request import Request |
33
|
|
|
from .models.request_post import RequestPost |
34
|
|
|
from .models.announcement import Announcement |
35
|
|
|
from .models.btcdeposit import BtcPendingDeposit |
36
|
|
|
from .models.receipt import Receipt |
37
|
|
|
from .models.pool import Pool |
38
|
|
|
from .models.pool_user import PoolUser |
39
|
|
|
|
40
|
|
|
from .utility import post_stripe_payment |
41
|
|
|
|
42
|
|
|
from pyramid.security import Allow, Everyone, remember, forget |
43
|
|
|
|
44
|
|
|
import chezbetty.datalayer as datalayer |
45
|
|
|
from .btc import Bitcoin, BTCException |
46
|
|
|
|
47
|
|
|
import uuid |
48
|
|
|
import math |
49
|
|
|
import pytz |
50
|
|
|
import traceback |
51
|
|
|
import arrow |
52
|
|
|
|
53
|
|
|
### |
54
|
|
|
### User Admin |
55
|
|
|
### |
56
|
|
|
|
57
|
|
|
|
58
|
|
|
### Common helper code |
59
|
|
|
|
60
|
|
|
def transaction_history_queries(request, user_or_pool): |
61
|
|
|
# Web library native date format: 2015/06/19 19:00 |
62
|
|
|
# DO NOT CHANGE: The datetimepicker has a bug and tries to parse the |
63
|
|
|
# prepopulated value before reading the format string, |
64
|
|
|
# which means you have to use its native format string |
65
|
|
|
TS_FORMAT = 'YYYY/MM/DD HH:mm' |
66
|
|
|
if 'history-end' not in request.GET: |
67
|
|
|
request.GET['history-end'] =\ |
68
|
|
|
arrow.now()\ |
69
|
|
|
.replace(hours=+1)\ |
70
|
|
|
.floor('hour')\ |
71
|
|
|
.format(TS_FORMAT) |
72
|
|
|
if 'history-start' not in request.GET: |
73
|
|
|
request.GET['history-start'] =\ |
74
|
|
|
arrow.get(request.GET['history-end'], TS_FORMAT)\ |
75
|
|
|
.replace(months=-1)\ |
76
|
|
|
.format(TS_FORMAT) |
77
|
|
|
|
78
|
|
|
start = arrow.get(request.GET['history-start'], TS_FORMAT) |
79
|
|
|
end = arrow.get(request.GET['history-end'], TS_FORMAT) |
80
|
|
|
start = start.replace(tzinfo='US/Eastern') |
81
|
|
|
end = end .replace(tzinfo='US/Eastern') |
82
|
|
|
start = start.to('utc') |
83
|
|
|
end = end .to('utc') |
84
|
|
|
|
85
|
|
|
query = user_or_pool.get_transactions_query() |
86
|
|
|
query = query\ |
87
|
|
|
.filter(event.Event.timestamp > start.datetime)\ |
88
|
|
|
.filter(event.Event.timestamp < end.datetime) |
89
|
|
|
|
90
|
|
|
for t in ('purchase', 'adjustment'): |
91
|
|
|
if 'history-filter-'+t in request.GET: |
92
|
|
|
query = query.filter(event.Event.type!=t) |
93
|
|
|
for t in ('cashdeposit', 'ccdeposit', 'btcdeposit'): |
94
|
|
|
if 'history-filter-'+t in request.GET: |
95
|
|
|
query = query.filter(Transaction.type!=t) |
96
|
|
|
transactions = query.all() |
97
|
|
|
|
98
|
|
|
withdrawls = query.filter(event.Event.type=='purchase').all() |
99
|
|
|
deposits = query.filter(event.Event.type=='deposit').all() |
100
|
|
|
adjustments = query.filter(event.Event.type=='adjustment').all() |
101
|
|
|
|
102
|
|
|
withdrawls = sum(w.amount for w in withdrawls) |
103
|
|
|
deposits = sum(d.amount for d in deposits) |
104
|
|
|
adjustments = sum(a.amount for a in adjustments) if len(adjustments) else None |
105
|
|
|
|
106
|
|
|
return {'transactions': transactions, |
107
|
|
|
'withdrawls': withdrawls, |
108
|
|
|
'deposits': deposits, |
109
|
|
|
'adjustments': adjustments, |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
|
113
|
|
|
|
114
|
|
|
@view_config(route_name='user_ajax_bool', |
115
|
|
|
permission='user') |
116
|
|
|
def user_ajax_bool(request): |
117
|
|
|
obj_str = request.matchdict['object'] |
118
|
|
|
obj_id = int(request.matchdict['id']) |
119
|
|
|
obj_field = request.matchdict['field'] |
120
|
|
|
obj_state = request.matchdict['state'].lower() == 'true' |
121
|
|
|
|
122
|
|
|
if obj_str == 'pool': |
123
|
|
|
obj = Pool.from_id(obj_id) |
124
|
|
|
obj_owner_id = obj.owner |
125
|
|
|
elif obj_str == 'pool_user': |
126
|
|
|
obj = PoolUser.from_id(obj_id) |
127
|
|
|
obj_owner_id = obj.pool.owner |
128
|
|
|
elif obj_str == 'request_post': |
129
|
|
|
obj = RequestPost.from_id(obj_id) |
130
|
|
|
obj_owner_id = obj.user_id |
131
|
|
|
else: |
132
|
|
|
# Return an error, object type not recognized |
133
|
|
|
request.response.status = 502 |
134
|
|
|
return request.response |
135
|
|
|
|
136
|
|
|
if obj_owner_id != request.user.id: |
137
|
|
|
request.response.status = 502 |
138
|
|
|
return request.response |
139
|
|
|
|
140
|
|
|
setattr(obj, obj_field, obj_state) |
141
|
|
|
DBSession.flush() |
142
|
|
|
|
143
|
|
|
return request.response |
144
|
|
|
|
145
|
|
|
@view_config(route_name='user_index', |
146
|
|
|
renderer='templates/user/index.jinja2', |
147
|
|
|
permission='user') |
148
|
|
|
def user_index(request): |
149
|
|
|
r = transaction_history_queries(request, request.user) |
150
|
|
|
r['user'] = request.user |
151
|
|
|
r['my_pools'] = Pool.all_by_owner(request.user) |
152
|
|
|
|
153
|
|
|
return r |
154
|
|
|
|
155
|
|
|
@view_config(route_name='user_index_slash', |
156
|
|
|
renderer='templates/user/index.jinja2', |
157
|
|
|
permission='user') |
158
|
|
|
def user_index_slash(request): |
159
|
|
|
return HTTPFound(location=request.route_url('user_index')) |
160
|
|
|
|
161
|
|
|
@view_config(route_name='user_deposit_cc', |
162
|
|
|
renderer='templates/user/deposit_cc.jinja2', |
163
|
|
|
permission='user') |
164
|
|
|
def user_deposit_cc(request): |
165
|
|
|
pools = Pool.all_accessable(request.user, True) |
166
|
|
|
pool = None |
167
|
|
|
if 'acct' in request.GET: |
168
|
|
|
account = request.GET['acct'] |
169
|
|
|
if account != 'user': |
170
|
|
|
pool = Pool.from_id(account.split('-')[1]) |
171
|
|
|
else: |
172
|
|
|
account = 'user' |
173
|
|
|
return {'user': request.user, |
174
|
|
|
'account': account, |
175
|
|
|
'pool': pool, |
176
|
|
|
'pools': pools, |
177
|
|
|
'stripe_pk': request.registry.settings['stripe.publishable_key'], |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
@view_config(route_name='user_deposit_cc_custom', |
181
|
|
|
renderer='templates/user/deposit_cc_custom.jinja2', |
182
|
|
|
permission='user') |
183
|
|
|
def user_deposit_cc_custom(request): |
184
|
|
|
account = request.GET['betty_to_account'] |
185
|
|
|
if account != 'user': |
186
|
|
|
pool = Pool.from_id(account.split('-')[1]) |
187
|
|
|
else: |
188
|
|
|
pool = None |
189
|
|
|
return {'user': request.user, |
190
|
|
|
'stripe_pk': request.registry.settings['stripe.publishable_key'], |
191
|
|
|
'amount': round(Decimal(request.GET['deposit-amount']), 2), |
192
|
|
|
'account': account, |
193
|
|
|
'pool': pool, |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
@view_config(route_name='user_deposit_cc_submit', |
197
|
|
|
request_method='POST', |
198
|
|
|
permission='user') |
199
|
|
|
def user_deposit_cc_submit(request): |
200
|
|
|
token = request.POST['stripeToken'] |
201
|
|
|
amount = Decimal(request.POST['betty_amount']) |
202
|
|
|
total_cents = int(request.POST['betty_total_cents']) |
203
|
|
|
to_account = request.POST['betty_to_account'] |
204
|
|
|
|
205
|
|
|
try: |
206
|
|
|
if to_account != 'user': |
207
|
|
|
pool = Pool.from_id(to_account.split('-')[1]) |
208
|
|
|
if pool.enabled == False: |
209
|
|
|
print("to_account:", to_account) |
210
|
|
|
raise NotImplementedError |
211
|
|
|
if pool.owner != request.user.id: |
212
|
|
|
if pool not in map(lambda pu: getattr(pu, 'pool'), request.user.pools): |
213
|
|
|
print("to_account:", to_account) |
214
|
|
|
raise NotImplementedError |
215
|
|
|
except Exception as e: |
216
|
|
|
traceback.print_exc() |
217
|
|
|
request.session.flash('Unexpected error processing transaction. Card NOT charged.', 'error') |
218
|
|
|
return HTTPFound(location=request.route_url('user_index')) |
219
|
|
|
|
220
|
|
|
post_stripe_payment( |
221
|
|
|
datalayer, |
222
|
|
|
request, |
223
|
|
|
token, |
224
|
|
|
amount, |
225
|
|
|
total_cents, |
226
|
|
|
request.user, |
227
|
|
|
request.user if to_account == 'user' else pool, |
228
|
|
|
) |
229
|
|
|
|
230
|
|
|
return HTTPFound(location=request.route_url('user_index')) |
231
|
|
|
|
232
|
|
|
|
233
|
|
|
|
234
|
|
View Code Duplication |
@view_config(route_name='user_item_list', |
|
|
|
|
235
|
|
|
renderer='templates/user/item_list.jinja2', |
236
|
|
|
permission='user') |
237
|
|
|
def item_list(request): |
238
|
|
|
items = DBSession.query(Item)\ |
239
|
|
|
.filter(Item.enabled==True)\ |
240
|
|
|
.filter(Item.in_stock>0)\ |
241
|
|
|
.order_by(Item.name).all() |
242
|
|
|
out_of_stock_items = DBSession.query(Item)\ |
243
|
|
|
.filter(Item.enabled==True)\ |
244
|
|
|
.filter(Item.in_stock==0)\ |
245
|
|
|
.order_by(Item.name).all() |
246
|
|
|
disabled_items = DBSession.query(Item)\ |
247
|
|
|
.filter(Item.enabled==False)\ |
248
|
|
|
.order_by(Item.name).all() |
249
|
|
|
return {'items': items, |
250
|
|
|
'out_of_stock_items': out_of_stock_items, |
251
|
|
|
'disabled_items': disabled_items} |
252
|
|
|
|
253
|
|
|
|
254
|
|
|
@view_config(route_name='user_ajax_item_request_fuzzy', |
255
|
|
|
renderer='templates/user/item_request_fuzzy.jinja2', |
256
|
|
|
permission='user') |
257
|
|
|
def item_request_fuzzy(request): |
258
|
|
|
new_item = request.POST['new_item'] |
259
|
|
|
matches = DBSession.query(Item)\ |
260
|
|
|
.filter(Item.name.ilike('%'+new_item+'%'))\ |
261
|
|
|
.order_by(Item.name) |
262
|
|
|
enabled = matches.filter(Item.enabled==True) |
263
|
|
|
in_stock = enabled.filter(Item.in_stock>0).all() |
264
|
|
|
out_of_stock = enabled.filter(Item.in_stock==0).all() |
265
|
|
|
for item in out_of_stock: |
266
|
|
|
purchase = SubTransaction.all_item_purchases(item.id, limit=1)[0] |
267
|
|
|
item.most_recent_purchase = purchase |
268
|
|
|
disabled = matches.filter(Item.enabled==False).all() |
269
|
|
|
return { |
270
|
|
|
'in_stock': in_stock, |
271
|
|
|
'out_of_stock': out_of_stock, |
272
|
|
|
'disabled': disabled, |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
|
276
|
|
|
@view_config(route_name='user_item_request', |
277
|
|
|
renderer='templates/user/item_request.jinja2', |
278
|
|
|
permission='user') |
279
|
|
|
def item_request(request): |
280
|
|
|
requests = Request.all() |
281
|
|
|
vendors = Vendor.all() |
282
|
|
|
return { |
283
|
|
|
'requests': requests, |
284
|
|
|
'vendors': vendors, |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
|
288
|
|
View Code Duplication |
@view_config(route_name='user_item_request_new', |
|
|
|
|
289
|
|
|
request_method='POST', |
290
|
|
|
permission='user') |
291
|
|
|
def item_request_new(request): |
292
|
|
|
try: |
293
|
|
|
request_text = request.POST['request'] |
294
|
|
|
vendor_id = request.POST['vendor'] |
295
|
|
|
vendor = Vendor.from_id(vendor_id) |
296
|
|
|
vendor_url = request.POST['vendor-url'] |
297
|
|
|
if len(request_text) < 5: |
298
|
|
|
raise ValueError() |
299
|
|
|
|
300
|
|
|
datalayer.new_request(request.user, request_text, vendor, vendor_url) |
301
|
|
|
|
302
|
|
|
request.session.flash('Request added successfully', 'success') |
303
|
|
|
return HTTPFound(location=request.route_url('user_item_request')) |
304
|
|
|
|
305
|
|
|
except ValueError: |
306
|
|
|
request.session.flash('Please include a detailed description of the item.', 'error') |
307
|
|
|
return HTTPFound(location=request.route_url('user_item_request')) |
308
|
|
|
|
309
|
|
|
except: |
310
|
|
|
request.session.flash('Error adding request.', 'error') |
311
|
|
|
return HTTPFound(location=request.route_url('user_item_request')) |
312
|
|
|
|
313
|
|
|
|
314
|
|
View Code Duplication |
@view_config(route_name='user_item_request_post_new', |
|
|
|
|
315
|
|
|
request_method='POST', |
316
|
|
|
permission='user') |
317
|
|
|
def item_request_post_new(request): |
318
|
|
|
try: |
319
|
|
|
item_request = Request.from_id(request.matchdict['id']) |
320
|
|
|
post_text = request.POST['post'] |
321
|
|
|
if post_text.strip() == '': |
322
|
|
|
request.session.flash('Empty comment not saved.', 'error') |
323
|
|
|
return HTTPFound(location=request.route_url('user_item_request')) |
324
|
|
|
post = RequestPost(item_request, request.user, post_text) |
325
|
|
|
DBSession.add(post) |
326
|
|
|
DBSession.flush() |
327
|
|
|
except Exception as e: |
328
|
|
|
if request.debug: |
329
|
|
|
raise(e) |
330
|
|
|
else: |
331
|
|
|
print(e) |
332
|
|
|
request.session.flash('Error posting comment.', 'error') |
333
|
|
|
return HTTPFound(location=request.route_url('user_item_request')) |
334
|
|
|
|
335
|
|
|
|
336
|
|
|
|
337
|
|
|
|
338
|
|
|
@view_config(route_name='user_pools', |
339
|
|
|
renderer='templates/user/pools.jinja2', |
340
|
|
|
permission='user') |
341
|
|
|
def user_pools(request): |
342
|
|
|
return {'user': request.user, |
343
|
|
|
'my_pools': Pool.all_by_owner(request.user)} |
344
|
|
|
|
345
|
|
|
|
346
|
|
|
@view_config(route_name='user_pools_new_submit', |
347
|
|
|
request_method='POST', |
348
|
|
|
permission='user') |
349
|
|
|
def user_pools_new_submit(request): |
350
|
|
|
try: |
351
|
|
|
pool_name = request.POST['pool-name'].strip() |
352
|
|
|
if len(pool_name) > 255: |
353
|
|
|
pool_name = pool_name[0:255] |
354
|
|
|
if len(pool_name) < 5: |
355
|
|
|
request.session.flash('Pool names must be at least 5 letters long', 'error') |
356
|
|
|
return HTTPFound(location=request.route_url('user_pools')) |
357
|
|
|
|
358
|
|
|
pool = Pool(request.user, pool_name) |
359
|
|
|
DBSession.add(pool) |
360
|
|
|
DBSession.flush() |
361
|
|
|
|
362
|
|
|
request.session.flash('Pool created.', 'succcess') |
363
|
|
|
return HTTPFound(location=request.route_url('user_pool', pool_id=pool.id)) |
364
|
|
|
|
365
|
|
|
except Exception as e: |
366
|
|
|
if request.debug: raise(e) |
367
|
|
|
request.session.flash('Error creating pool.', 'error') |
368
|
|
|
return HTTPFound(location=request.route_url('user_pools')) |
369
|
|
|
|
370
|
|
|
|
371
|
|
View Code Duplication |
@view_config(route_name='user_pool', |
|
|
|
|
372
|
|
|
renderer='templates/user/pool.jinja2', |
373
|
|
|
permission='user') |
374
|
|
|
def user_pool(request): |
375
|
|
|
try: |
376
|
|
|
pool = Pool.from_id(request.matchdict['pool_id']) |
377
|
|
|
if pool.owner != request.user.id: |
378
|
|
|
request.session.flash('You do not have permission to view that pool.', 'error') |
379
|
|
|
return HTTPFound(location=request.route_url('user_pools')) |
380
|
|
|
|
381
|
|
|
r = transaction_history_queries(request, pool) |
382
|
|
|
r['user'] = request.user |
383
|
|
|
r['pool'] = pool |
384
|
|
|
|
385
|
|
|
return r |
386
|
|
|
except Exception as e: |
387
|
|
|
if request.debug: raise(e) |
388
|
|
|
request.session.flash('Could not load pool.', 'error') |
389
|
|
|
return HTTPFound(location=request.route_url('user_pools')) |
390
|
|
|
|
391
|
|
|
|
392
|
|
|
@view_config(route_name='user_pool_addmember_submit', |
393
|
|
|
request_method='POST', |
394
|
|
|
permission='user') |
395
|
|
|
def user_pool_addmember_submit(request): |
396
|
|
|
try: |
397
|
|
|
pool = Pool.from_id(request.POST['pool-id']) |
398
|
|
|
if pool.owner != request.user.id: |
399
|
|
|
request.session.flash('You do not have permission to view that pool.', 'error') |
400
|
|
|
return HTTPFound(location=request.route_url('user_pools')) |
401
|
|
|
|
402
|
|
|
# Look up the user that is being added to the pool |
403
|
|
|
user = User.from_uniqname(request.POST['uniqname'].strip(), True) |
404
|
|
|
if user == None: |
405
|
|
|
request.session.flash('Could not find that user.', 'error') |
406
|
|
|
return HTTPFound(location=request.route_url('user_pool', pool_id=pool.id)) |
407
|
|
|
|
408
|
|
|
# Can't add yourself |
409
|
|
|
if user.id == pool.owner: |
410
|
|
|
request.session.flash('You cannot add yourself to a pool. By owning the pool you are automatically a part of it.', 'error') |
411
|
|
|
return HTTPFound(location=request.route_url('user_pool', pool_id=pool.id)) |
412
|
|
|
|
413
|
|
|
# Make sure the user isn't already in the pool |
414
|
|
|
for u in pool.users: |
415
|
|
|
if u.user_id == user.id: |
416
|
|
|
request.session.flash('User is already in pool.', 'error') |
417
|
|
|
return HTTPFound(location=request.route_url('user_pool', pool_id=pool.id)) |
418
|
|
|
|
419
|
|
|
# Add the user to the pool |
420
|
|
|
pooluser = PoolUser(pool, user) |
421
|
|
|
DBSession.add(pooluser) |
422
|
|
|
DBSession.flush() |
423
|
|
|
|
424
|
|
|
request.session.flash('{} added to the pool.'.format(user.name), 'succcess') |
425
|
|
|
return HTTPFound(location=request.route_url('user_pool', pool_id=pool.id)) |
426
|
|
|
|
427
|
|
|
except Exception as e: |
428
|
|
|
if request.debug: raise(e) |
429
|
|
|
request.session.flash('Error adding user to pool.', 'error') |
430
|
|
|
return HTTPFound(location=request.route_url('user_pools')) |
431
|
|
|
|
432
|
|
|
|
433
|
|
|
@view_config(route_name='user_pool_changename_submit', |
434
|
|
|
request_method='POST', |
435
|
|
|
permission='user') |
436
|
|
|
def user_pool_changename_submit(request): |
437
|
|
|
try: |
438
|
|
|
pool = Pool.from_id(request.POST['pool-id']) |
439
|
|
|
if pool.owner != request.user.id: |
440
|
|
|
request.session.flash('You do not have permission to view that pool.', 'error') |
441
|
|
|
return HTTPFound(location=request.route_url('user_pools')) |
442
|
|
|
|
443
|
|
|
pool_name = request.POST['newname'].strip() |
444
|
|
|
if len(pool_name) > 255: |
445
|
|
|
pool_name = pool_name[0:255] |
446
|
|
|
if len(pool_name) < 5: |
447
|
|
|
request.session.flash('Pool names must be at least 5 letters long', 'error') |
448
|
|
|
return HTTPFound(location=request.route_url('user_pool', pool_id=int(pool.id))) |
449
|
|
|
|
450
|
|
|
pool.name = pool_name |
451
|
|
|
|
452
|
|
|
request.session.flash('Pool created.', 'succcess') |
453
|
|
|
return HTTPFound(location=request.route_url('user_pool', pool_id=pool.id)) |
454
|
|
|
except Exception as e: |
455
|
|
|
if request.debug: raise(e) |
456
|
|
|
request.session.flash('Error changing pool name.', 'error') |
457
|
|
|
return HTTPFound(location=request.route_url('user_pools')) |
458
|
|
|
|
459
|
|
|
|
460
|
|
|
@view_config(route_name='user_password_edit', |
461
|
|
|
renderer='templates/user/password_edit.jinja2', |
462
|
|
|
permission='user') |
463
|
|
|
def user_password_edit(request): |
464
|
|
|
return {} |
465
|
|
|
|
466
|
|
|
|
467
|
|
View Code Duplication |
@view_config(route_name='user_password_edit_submit', |
|
|
|
|
468
|
|
|
request_method='POST', |
469
|
|
|
permission='user') |
470
|
|
|
def user_password_edit_submit(request): |
471
|
|
|
pwd0 = request.POST['edit-password-0'] |
472
|
|
|
pwd1 = request.POST['edit-password-1'] |
473
|
|
|
if pwd0 != pwd1: |
474
|
|
|
request.session.flash('Error: Passwords do not match', 'error') |
475
|
|
|
return HTTPFound(location=request.route_url('user_password_edit')) |
476
|
|
|
request.user.password = pwd0 |
477
|
|
|
request.session.flash('Password changed successfully.', 'success') |
478
|
|
|
return HTTPFound(location=request.route_url('user_index')) |
479
|
|
|
# check that changing password for actually logged in user |
480
|
|
|
|
481
|
|
|
|
482
|
|
|
|