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', |
|
0 ignored issues
–
show
Duplication
introduced
by
![]() |
|||
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', |
|
0 ignored issues
–
show
|
|||
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', |
|
0 ignored issues
–
show
|
|||
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', |
|
0 ignored issues
–
show
|
|||
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', |
|
0 ignored issues
–
show
|
|||
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 |