Issues (24)

chezbetty/views_user.py (5 issues)

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
This code seems to be duplicated in your project.
Loading history...
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
This code seems to be duplicated in your project.
Loading history...
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
This code seems to be duplicated in your project.
Loading history...
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
This code seems to be duplicated in your project.
Loading history...
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
This code seems to be duplicated in your project.
Loading history...
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