Issues (6)

app/main.py (1 issue)

1
from flask import request, redirect, url_for, send_from_directory, flash, send_file, Response
2
from flask_login import login_required, current_user
3
from flask import Blueprint, render_template
4
from .models import Schedule, Balance, User, Settings, Email, Hold, Skip
5
from app import db
6
from datetime import datetime
7
import os
8
from sqlalchemy import desc, extract, asc
9
from werkzeug.security import generate_password_hash, check_password_hash
10
from .cashflow import update_cash, plot_cash
11
from .auth import admin_required, global_admin_required, account_owner_required
12
from .files import export, upload, version
13
14
15
main = Blueprint('main', __name__)
16
17
18
def get_effective_user_id():
19
    """
20
    Get the effective user ID for data filtering.
21
    For guest users, returns their account owner's ID.
22
    For account owners, returns their own ID.
23
    """
24
    if current_user.account_owner_id:
25
        # Guest user - return account owner's ID
26
        return current_user.account_owner_id
27
    else:
28
        # Account owner or standalone user - return their own ID
29
        return current_user.id
30
31
32
@main.route('/', methods=('GET', 'POST'))
33
@login_required
34
def index():
35
    # Get effective user ID (account owner for guests, self for owners)
36
    user_id = get_effective_user_id()
37
38
    # get today's date
39
    todaydate = datetime.today().strftime('%A, %B %d, %Y')
40
41
    # query the latest balance information for this user
42
    balance = Balance.query.filter_by(user_id=user_id).order_by(desc(Balance.date), desc(Balance.id)).first()
43
44
    try:
45
        float(balance.amount)
46
        db.session.query(Balance).filter_by(user_id=user_id).delete()
47
        balance = Balance(amount=balance.amount, date=datetime.today(), user_id=user_id)
48
        db.session.add(balance)
49
        db.session.commit()
50
    except:
51
        balance = Balance(amount='0', date=datetime.today(), user_id=user_id)
52
        db.session.add(balance)
53
        db.session.commit()
54
55
    # Pre-filter data by user before passing to cashflow
56
    schedules = Schedule.query.filter_by(user_id=user_id).all()
57
    holds = Hold.query.filter_by(user_id=user_id).all()
58
    skips = Skip.query.filter_by(user_id=user_id).all()
59
60
    trans, run = update_cash(float(balance.amount), schedules, holds, skips)
61
62
    # plot cash flow results
63
    minbalance, graphJSON = plot_cash(run)
64
65
    if current_user.admin:
66
        return render_template('index.html', title='Index', todaydate=todaydate, balance=balance.amount,
67
                           minbalance=minbalance, graphJSON=graphJSON)
68
    else:
69
        return render_template('index_guest.html', title='Index', todaydate=todaydate, balance=balance.amount,
70
                           minbalance=minbalance, graphJSON=graphJSON)
71
72
73
@main.route('/refresh')
74
@login_required
75
def refresh():
76
77
    return redirect(url_for('main.index'))
78
79
80
@main.route('/settings')
81
@login_required
82
def settings():
83
    # get about info - available to all users
84
    about = version()
85
86
    if current_user.admin:
87
        return render_template('settings.html', about=about)
88
    else:
89
        return render_template('settings_guest.html', about=about)
90
91
92
@main.route('/schedule')
93
@login_required
94
@admin_required
95
def schedule():
96
    user_id = get_effective_user_id()
97
    schedule = Schedule.query.filter_by(user_id=user_id).order_by(asc(extract('day', Schedule.startdate)))
98
99
    return render_template('schedule_table.html', title='Schedule Table', schedule=schedule)
100
101
102
@main.route('/holds')
103
@login_required
104
@admin_required
105
def holds():
106
    user_id = get_effective_user_id()
107
    hold = Hold.query.filter_by(user_id=user_id)
108
    skip = Skip.query.filter_by(user_id=user_id)
109
110
    return render_template('holds_table.html', title='Holds Table', hold=hold, skip=skip)
111
112
113
@main.route('/create', methods=('GET', 'POST'))
114
@login_required
115
@admin_required
116
def create():
117
    # create a new schedule item
118
    user_id = get_effective_user_id()
119
    format = '%Y-%m-%d'
120
    if request.method == 'POST':
121
        name = request.form['name']
122
        amount = request.form['amount']
123
        frequency = request.form['frequency']
124
        startdate = request.form['startdate']
125
        type = request.form['type']
126
        schedule = Schedule(name=name,
127
                            type=type,
128
                            amount=amount,
129
                            frequency=frequency,
130
                            startdate=datetime.strptime(startdate, format).date(),
131
                            firstdate=datetime.strptime(startdate, format).date(),
132
                            user_id=user_id)
133
        existing = Schedule.query.filter_by(name=name, user_id=user_id).first()
134
        if existing:
135
            flash("Schedule already exists")
136
            return redirect(url_for('main.schedule'))
137
        db.session.add(schedule)
138
        db.session.commit()
139
        flash("Added Successfully")
140
141
        return redirect(url_for('main.schedule'))
142
143
    return redirect(url_for('main.schedule'))
144
145
146
@main.route('/update', methods=['GET', 'POST'])
147
@login_required
148
@admin_required
149
def update():
150
    # update an existing schedule item
151
    user_id = get_effective_user_id()
152
    format = '%Y-%m-%d'
153
154
    if request.method == 'POST':
155
        current = Schedule.query.filter_by(id=int(request.form['id']), user_id=user_id).first()
156
        existing = Schedule.query.filter_by(name=request.form['name'], user_id=user_id).first()
157
        if existing:
158
            if current.name != request.form['name']:
159
                flash("Schedule name already exists")
160
                return redirect(url_for('main.schedule'))
161
        my_data = Schedule.query.filter_by(id=int(request.form.get('id')), user_id=user_id).first()
162
        my_data.name = request.form['name']
163
        my_data.amount = request.form['amount']
164
        my_data.type = request.form['type']
165
        my_data.frequency = request.form['frequency']
166
        startdate = request.form['startdate']
167
        if (datetime.strptime(startdate, format).date() != my_data.startdate and my_data.startdate.day !=
168
                datetime.strptime(startdate, format).day):
169
            my_data.firstdate = datetime.strptime(startdate, format).date()
170
        my_data.startdate = datetime.strptime(startdate, format).date()
171
        db.session.commit()
172
        flash("Updated Successfully")
173
174
        return redirect(url_for('main.schedule'))
175
176
    return redirect(url_for('main.schedule'))
177
178
179
@main.route('/addhold/<id>')
180
@login_required
181
@admin_required
182
def addhold(id):
183
    # add a hold item from the schedule
184
    user_id = get_effective_user_id()
185
    schedule = Schedule.query.filter_by(id=int(id), user_id=user_id).first()
186
    hold = Hold(name=schedule.name, type=schedule.type, amount=schedule.amount, user_id=user_id)
187
    db.session.add(hold)
188
    db.session.commit()
189
    flash("Added Hold")
190
191
    return redirect(url_for('main.schedule'))
192
193
194
@main.route('/addskip/<id>')
195
@login_required
196
@admin_required
197
def addskip(id):
198
    # add a skip item from the schedule
199
    user_id = get_effective_user_id()
200
    balance = Balance.query.filter_by(user_id=user_id).order_by(desc(Balance.date), desc(Balance.id)).first()
201
202
    # Pre-filter data by user
203
    schedules = Schedule.query.filter_by(user_id=user_id).all()
204
    holds = Hold.query.filter_by(user_id=user_id).all()
205
    skips = Skip.query.filter_by(user_id=user_id).all()
206
207
    trans, run = update_cash(float(balance.amount), schedules, holds, skips)
208
    transaction = trans.loc[int(id)]
209
    trans_type = ""
210
    if transaction[1] == "Expense":
211
        trans_type = "Income"
212
    elif transaction[1] == "Income":
213
        trans_type = "Expense"
214
    skip = Skip(name=transaction[0] + " (SKIP)", type=trans_type, amount=transaction[2], date=transaction[3], user_id=user_id)
215
    db.session.add(skip)
216
    db.session.commit()
217
    flash("Added Skip")
218
219
    return redirect(url_for('main.transactions'))
220
221
222
@main.route('/deletehold/<id>')
223
@login_required
224
@admin_required
225
def holds_delete(id):
226
    # delete a hold item
227
    user_id = get_effective_user_id()
228
    hold = Hold.query.filter_by(id=int(id), user_id=user_id).first()
229
230
    if hold:
231
        db.session.delete(hold)
232
        db.session.commit()
233
        flash("Deleted Successfully")
234
235
    return redirect(url_for('main.holds'))
236
237
238
@main.route('/deleteskip/<id>')
239
@login_required
240
@admin_required
241
def skips_delete(id):
242
    # delete a skip item
243
    user_id = get_effective_user_id()
244
    skip = Skip.query.filter_by(id=int(id), user_id=user_id).first()
245
246
    if skip:
247
        db.session.delete(skip)
248
        db.session.commit()
249
        flash("Deleted Successfully")
250
251
    return redirect(url_for('main.holds'))
252
253
254
@main.route('/clearholds')
255
@login_required
256
@admin_required
257
def clear_holds():
258
    # clear holds
259
    user_id = get_effective_user_id()
260
    db.session.query(Hold).filter_by(user_id=user_id).delete()
261
    db.session.commit()
262
263
    return redirect(url_for('main.holds'))
264
265
266
@main.route('/clearskips')
267
@login_required
268
@admin_required
269
def clear_skips():
270
    # clear skips
271
    user_id = get_effective_user_id()
272
    db.session.query(Skip).filter_by(user_id=user_id).delete()
273
    db.session.commit()
274
275
    return redirect(url_for('main.holds'))
276
277
278
@main.route('/delete/<id>')
279
@login_required
280
@admin_required
281
def schedule_delete(id):
282
    # delete a schedule item
283
    user_id = get_effective_user_id()
284
    schedule = Schedule.query.filter_by(id=int(id), user_id=user_id).first()
285
286
    if schedule:
287
        db.session.delete(schedule)
288
        db.session.commit()
289
        flash("Deleted Successfully")
290
291
    return redirect(url_for('main.schedule'))
292
293
294
@main.route('/favicon')
295
def favicon():
296
    return send_from_directory(os.path.join(main.root_path, 'static'),
297
                               'favicon.ico', mimetype='image/vnd.microsoft.icon')
298
299
300
@main.route('/appleicon')
301
def appleicon():
302
    return send_from_directory(os.path.join(main.root_path, 'static'),
303
                               'apple-touch-icon.png', mimetype='image/png')
304
305
306
@main.route('/balance', methods=('GET', 'POST'))
307
@login_required
308
@admin_required
309
def balance():
310
    # manually update the balance from the balance button
311
    user_id = get_effective_user_id()
312
    format = '%Y-%m-%d'
313
    if request.method == 'POST':
314
        amount = request.form['amount']
315
        dateentry = request.form['date']
316
        balance = Balance(amount=amount,
317
                          date=datetime.strptime(dateentry, format).date(),
318
                          user_id=user_id)
319
        db.session.add(balance)
320
        db.session.commit()
321
322
        return redirect(url_for('main.index'))
323
324
325
@main.route('/changepw', methods=('GET', 'POST'))
326
@login_required
327
def changepw():
328
    # change the users password from the settings page
329
    if request.method == 'POST':
330
        curr_user = current_user.id
331
        my_user = User.query.filter_by(id=curr_user).first()
332
        current = request.form['current']
333
        password = request.form['password']
334
        password2 = request.form['password2']
335
        if password == password2 and check_password_hash(my_user.password, current):
336
            my_user.password = generate_password_hash(password, method='scrypt')
337
            db.session.commit()
338
            flash('Password change successful')
339
        elif password != password2:
340
            flash('Passwords do not match')
341
        elif not check_password_hash(my_user.password, current):
342
            flash('Incorrect password')
343
344
        return redirect(url_for('main.settings'))
345
346
    return redirect(url_for('main.settings'))
347
348
349
@main.route('/signups', methods=('GET', 'POST'))
350
@login_required
351
@global_admin_required
352
def signups():
353
    # set the settings options, in this case disable signups, from the profile page
354
    if request.method == 'POST':
355
        signupsettingname = Settings.query.filter_by(name='signup').first()
356
357
        if signupsettingname:
358
            if request.form['signupvalue'] == "True":
359
                signupvalue = True
360
            else:
361
                signupvalue = False
362
            signupsettingname.value = signupvalue
363
            db.session.commit()
364
365
            return redirect(url_for('main.settings'))
366
367
        # store the signup option value in the database to check when the user clicks signup
368
        if request.form['signupvalue'] == "True":
369
            signupvalue = True
370
        else:
371
            signupvalue = False
372
        settings = Settings(name="signup", value=signupvalue)
373
        db.session.add(settings)
374
        db.session.commit()
375
376
        return redirect(url_for('main.settings'))
377
378
    return redirect(url_for('main.settings'))
379
380
381
@main.route('/transactions')
382
@login_required
383
@admin_required
384
def transactions():
385
    user_id = get_effective_user_id()
386
    balance = Balance.query.filter_by(user_id=user_id).order_by(desc(Balance.date), desc(Balance.id)).first()
387
388
    # Pre-filter data by user
389
    schedules = Schedule.query.filter_by(user_id=user_id).all()
390
    holds = Hold.query.filter_by(user_id=user_id).all()
391
    skips = Skip.query.filter_by(user_id=user_id).all()
392
393
    trans, run = update_cash(float(balance.amount), schedules, holds, skips)
394
395
    return render_template('transactions_table.html', total=trans.to_dict(orient='records'))
396
397
398
@main.route('/email', methods=('GET', 'POST'))
399
@login_required
400
@admin_required
401
def email():
402
    # set the users email address, password, and server for the auto email balance update
403
    user_id = get_effective_user_id()
404
405
    if request.method == 'POST':
406
        emailsettings = Email.query.filter_by(user_id=user_id).first()
407
408
        if emailsettings:
409
            email = request.form['email']
410
            password = request.form['password']
411
            server = request.form['server']
412
            subjectstr = request.form['subject_str']
413
            startstr = request.form['start_str']
414
            endstr = request.form['end_str']
415
            emailsettings.email = email
416
            emailsettings.password = password
417
            emailsettings.server = server
418
            emailsettings.subjectstr = subjectstr
419
            emailsettings.startstr = startstr
420
            emailsettings.endstr = endstr
421
            db.session.commit()
422
423
            return redirect(url_for('main.settings'))
424
425
        email = request.form['email']
426
        password = request.form['password']
427
        server = request.form['server']
428
        subjectstr = request.form['subject_str']
429
        startstr = request.form['start_str']
430
        endstr = request.form['end_str']
431
        emailentry = Email(email=email, password=password, server=server, subjectstr=subjectstr, startstr=startstr,
432
                           endstr=endstr, user_id=user_id)
433
        db.session.add(emailentry)
434
        db.session.commit()
435
436
        return redirect(url_for('main.settings'))
437
438
    return redirect(url_for('main.settings'))
439
440
441
@main.route('/update_user', methods=['GET', 'POST'])
442
@login_required
443
@admin_required
444
def update_user():
445
    # update an existing user
446
    if request.method == 'POST':
447
        current = User.query.filter_by(id=int(request.form['id'])).first()
448
449
        # Check if the current user has permission to update this user
450
        if not current_user.is_global_admin and current.account_owner_id != current_user.id:
451
            flash("You don't have permission to update this user")
452
            if current_user.is_global_admin:
453
                return redirect(url_for('main.global_admin_panel'))
454
            else:
455
                return redirect(url_for('main.manage_guests'))
456
457
        existing = User.query.filter_by(email=request.form['email']).first()
458
        if existing:
459
            if current.email != request.form['email']:
460
                flash("Email already exists")
461
                if current_user.is_global_admin:
462
                    return redirect(url_for('main.global_admin_panel'))
463
                else:
464
                    return redirect(url_for('main.manage_guests'))
465
        my_data = User.query.get(request.form.get('id'))
466
        my_data.name = request.form['name']
467
        my_data.email = request.form['email']
468
469
        # Global admins can change roles, regular admins cannot
470
        if current_user.is_global_admin:
471
            # Handle role assignment
472
            role = request.form.get('role', 'user')
473
            if role == 'global_admin':
474
                my_data.admin = True
475
                my_data.is_global_admin = True
476
                # IMPORTANT: Global admins must always be active
477
                my_data.is_active = True
478
            elif role == 'admin':
479
                my_data.admin = True
480
                my_data.is_global_admin = False
481
            else:  # user
482
                my_data.admin = False
483
                my_data.is_global_admin = False
484
485
        db.session.commit()
486
        flash("Updated Successfully")
487
488
        # Redirect based on user role
489
        if current_user.is_global_admin:
490
            return redirect(url_for('main.global_admin_panel'))
491
        else:
492
            return redirect(url_for('main.manage_guests'))
493
494
    # Redirect based on user role
495
    if current_user.is_global_admin:
496
        return redirect(url_for('main.global_admin_panel'))
497
    else:
498
        return redirect(url_for('main.manage_guests'))
499
500
501 View Code Duplication
@main.route('/delete_user/<id>')
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
502
@login_required
503
@admin_required
504
def delete_user(id):
505
    # delete a user
506
    user = User.query.filter_by(id=int(id)).first()
507
508
    if user:
509
        # Global admins can delete any user (except themselves), regular admins can only delete their guests
510
        if current_user.is_global_admin or user.account_owner_id == current_user.id:
511
            # Prevent deleting yourself
512
            if user.id == current_user.id:
513
                flash("You cannot delete your own account")
514
                if current_user.is_global_admin:
515
                    return redirect(url_for('main.global_admin_panel'))
516
                else:
517
                    return redirect(url_for('main.manage_guests'))
518
519
            db.session.delete(user)
520
            db.session.commit()
521
            flash("Deleted Successfully")
522
        else:
523
            flash("You don't have permission to delete this user")
524
525
    # Redirect based on user role
526
    if current_user.is_global_admin:
527
        return redirect(url_for('main.global_admin_panel'))
528
    else:
529
        return redirect(url_for('main.manage_guests'))
530
531
532
@main.route('/activate_user/<id>')
533
@login_required
534
@admin_required
535
def activate_user(id):
536
    # activate a user account
537
    user = User.query.filter_by(id=int(id)).first()
538
539
    if user:
540
        # Global admins can activate any user, regular admins can only activate their guests
541
        if current_user.is_global_admin or user.account_owner_id == current_user.id:
542
            user.is_active = True
543
            db.session.commit()
544
            flash(f"User {user.name} has been activated successfully")
545
        else:
546
            flash("You don't have permission to activate this user")
547
548
    # Redirect based on user role
549
    if current_user.is_global_admin:
550
        return redirect(url_for('main.global_admin_panel'))
551
    else:
552
        return redirect(url_for('main.manage_guests'))
553
554
555 View Code Duplication
@main.route('/deactivate_user/<id>')
556
@login_required
557
@admin_required
558
def deactivate_user(id):
559
    # deactivate a user account
560
    user = User.query.filter_by(id=int(id)).first()
561
562
    if user:
563
        # IMPORTANT: Global admins are always active and cannot be deactivated
564
        if user.is_global_admin:
565
            flash("Cannot deactivate a global admin. Global admins must always remain active.")
566
            if current_user.is_global_admin:
567
                return redirect(url_for('main.global_admin_panel'))
568
            else:
569
                return redirect(url_for('main.manage_guests'))
570
571
        # Global admins can deactivate any user, regular admins can only deactivate their guests
572
        if current_user.is_global_admin or user.account_owner_id == current_user.id:
573
            user.is_active = False
574
            db.session.commit()
575
            flash(f"User {user.name} has been deactivated successfully")
576
        else:
577
            flash("You don't have permission to deactivate this user")
578
579
    # Redirect based on user role
580
    if current_user.is_global_admin:
581
        return redirect(url_for('main.global_admin_panel'))
582
    else:
583
        return redirect(url_for('main.manage_guests'))
584
585
586
@main.route('/create_user', methods=('GET', 'POST'))
587
@login_required
588
@admin_required
589
def create_user():
590
    # create a new user
591
    if request.method == 'POST':
592
        name = request.form['name']
593
        email = request.form['email']
594
        password = generate_password_hash(request.form['password'], method='scrypt')
595
596
        # Handle role assignment
597
        role = request.form.get('role', 'user')
598
599
        # Global admins can create any type of user
600
        if current_user.is_global_admin:
601
            if role == 'global_admin':
602
                admin = True
603
                is_global_admin = True
604
            elif role == 'admin':
605
                admin = True
606
                is_global_admin = False
607
            else:  # user
608
                admin = False
609
                is_global_admin = False
610
            account_owner_id = None
611
            # Users created by global admin are active by default
612
            is_active = True
613
        else:
614
            # Regular admins can only create guest users (non-admin users)
615
            admin = False
616
            is_global_admin = False
617
            account_owner_id = current_user.id
618
            # Guests created by regular admins are active by default
619
            is_active = True
620
621
        user = User(name=name, email=email, admin=admin, is_global_admin=is_global_admin,
622
                    is_active=is_active, password=password, account_owner_id=account_owner_id)
623
        existing = User.query.filter_by(email=email).first()
624
        if existing:
625
            flash("User already exists")
626
            if current_user.is_global_admin:
627
                return redirect(url_for('main.global_admin_panel'))
628
            else:
629
                return redirect(url_for('main.manage_guests'))
630
        db.session.add(user)
631
        db.session.commit()
632
        flash("Added Successfully")
633
634
        # Redirect based on user role
635
        if current_user.is_global_admin:
636
            return redirect(url_for('main.global_admin_panel'))
637
        else:
638
            return redirect(url_for('main.manage_guests'))
639
640
    # Redirect based on user role
641
    if current_user.is_global_admin:
642
        return redirect(url_for('main.global_admin_panel'))
643
    else:
644
        return redirect(url_for('main.manage_guests'))
645
646
647
@main.route('/export', methods=('GET', 'POST'))
648
@login_required
649
@admin_required
650
def export_csv():
651
    user_id = get_effective_user_id()
652
653
    csv_data = export(user_id)
654
655
    # Create a direct download response with the CSV data and appropriate headers
656
    response = Response(csv_data, content_type="text/csv")
657
    response.headers["Content-Disposition"] = "attachment; filename=schedule_export.csv"
658
659
    return response
660
661
662
@main.route('/import', methods=('GET', 'POST'))
663
@login_required
664
@admin_required
665
def import_csv():
666
    if request.method == 'POST':
667
        user_id = get_effective_user_id()
668
        csv_file = request.files.get('file')
669
670
        upload(csv_file, user_id)
671
672
        flash("Import Successful")
673
674
    return redirect(url_for('main.schedule'))
675
676
677
@main.route('/manage_guests')
678
@login_required
679
@account_owner_required
680
def manage_guests():
681
    """Account owners can manage their guest users"""
682
    guests = User.query.filter_by(account_owner_id=current_user.id).all()
683
    return render_template('manage_guests.html', guests=guests)
684
685
686
@main.route('/add_guest', methods=['POST'])
687
@login_required
688
@account_owner_required
689
def add_guest():
690
    """Create a guest user linked to current account owner"""
691
    email = request.form.get('email')
692
    name = request.form.get('name')
693
694
    # Check if user already exists
695
    existing_user = User.query.filter_by(email=email).first()
696
    if existing_user:
697
        flash('A user with this email already exists')
698
        return redirect(url_for('main.manage_guests'))
699
700
    # Generate a random password for guest (they can change it later)
701
    import secrets
702
    temp_password = secrets.token_urlsafe(16)
703
704
    new_guest = User(
705
        email=email,
706
        name=name,
707
        password=generate_password_hash(temp_password, method='scrypt'),
708
        admin=False,  # Guests are not admins
709
        is_global_admin=False,
710
        account_owner_id=current_user.id
711
    )
712
    db.session.add(new_guest)
713
    db.session.commit()
714
715
    flash(f'Guest user {name} added successfully. Temporary password: {temp_password}')
716
    return redirect(url_for('main.manage_guests'))
717
718
719
@main.route('/remove_guest/<int:guest_id>', methods=['POST'])
720
@login_required
721
@account_owner_required
722
def remove_guest(guest_id):
723
    """Remove a guest user (account owner only)"""
724
    guest = User.query.filter_by(id=int(guest_id), account_owner_id=current_user.id).first()
725
726
    if guest:
727
        db.session.delete(guest)
728
        db.session.commit()
729
        flash('Guest user removed successfully')
730
    else:
731
        flash('Guest user not found or you do not have permission to remove them')
732
733
    return redirect(url_for('main.manage_guests'))
734
735
736
@main.route('/global_admin')
737
@login_required
738
@global_admin_required
739
def global_admin_panel():
740
    """Global admin can see all users and accounts"""
741
    all_users = User.query.all()
742
743
    # Organize users by account owners
744
    account_owners = [u for u in all_users if u.account_owner_id is None and not u.is_global_admin]
745
    global_admins = [u for u in all_users if u.is_global_admin]
746
    standalone_users = [u for u in all_users if u.account_owner_id is None and not u.admin]
747
748
    return render_template('global_admin.html',
749
                         all_users=all_users,
750
                         account_owners=account_owners,
751
                         global_admins=global_admins,
752
                         standalone_users=standalone_users)
753
754
755
@main.route('/manifest.json')
756
def serve_manifest():
757
    return send_file('manifest.json', mimetype='application/manifest+json')
758
759
760
@main.route('/sw.js')
761
def serve_sw():
762
    return send_file('sw.js', mimetype='application/javascript')
763