admin: Fix admin page

This commit is contained in:
Cara Salter 2022-12-06 12:03:13 -05:00
parent 036ca7b21e
commit 72624a3f4e
No known key found for this signature in database
GPG key ID: 90C66610C82B29CA
9 changed files with 333 additions and 23 deletions

View file

@ -3,12 +3,14 @@ from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_assets import Bundle, Environment
from flask_cors import CORS
db = SQLAlchemy()
migrate = Migrate()
login = LoginManager()
environment = Environment()
cors = CORS()
def create_app():
app = Flask(__name__)
@ -19,6 +21,7 @@ def create_app():
migrate.init_app(app, db)
login.init_app(app)
environment.init_app(app)
cors.init_app(app)
scss = Bundle('css/style.scss', filters='scss',
output='css/style.css')
@ -28,8 +31,10 @@ def create_app():
from . import registration
from . import dashboard
from . import admin
app.register_blueprint(registration.bp)
app.register_blueprint(dashboard.bp)
app.register_blueprint(admin.bp)
return app

182
goathacks/admin/__init__.py Normal file
View file

@ -0,0 +1,182 @@
from flask import Blueprint, jsonify, redirect, render_template, url_for
from flask_login import current_user, login_required
from goathacks.models import User
bp = Blueprint("admin", __name__, url_prefix="/admin")
from goathacks import db
@bp.route("/")
@login_required
def home():
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
male_count = 0
female_count = 0
nb_count = 0
check_in_count = 0
waitlist_count = 0
total_count = 0
shirt_count = {'XS': 0, 'S': 0, 'M': 0, 'L': 0, 'XL': 0}
hackers = db.session.execute(db.select(User)).scalars().all()
schools = {}
for h in hackers:
if h.waitlisted:
waitlist_count += 1
if h.checked_in:
check_in_count += 1
if h.gender == 'F':
female_count += 1
elif h.gender == 'M':
male_count += 1
else:
nb_count += 1
total_count += 1
if h.school not in schools:
schools[h.school] = 1
else:
schools[h.school] += 1
if h.shirt_size not in shirt_count:
shirt_count[h.shirt_size] = 1
else:
shirt_count[h.shirt_size] += 1
return render_template("admin.html", waitlist_count=waitlist_count,
total_count=total_count, shirt_count=shirt_count,
hackers=hackers, male_count=male_count,
female_count=female_count, nb_count=nb_count,
check_in_count=check_in_count, schools=schools)
@bp.route("/check_in/<int:id>")
@login_required
def check_in(id):
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
user = User.query.filter_by(id=id).one()
if user is None:
return {"status": "error", "msg": "No user found"}
user.checked_in = True
db.session.commit()
return {"status": "success"}
@bp.route("/drop/<int:id>")
@login_required
def drop(id):
if not current_user.is_admin and not current_user.id == id:
return redirect(url_for("dashboard.home"))
user = User.query.filter_by(id=id).one()
if user is None:
return {"status": "error", "msg": "user not found"}
if user.checked_in:
return {"status": "error", "msg": "Hacker is already checked in"}
db.session.delete(user)
db.session.commit()
return {"status": "success"}
@bp.route("/change_admin/<int:id>/<string:action>")
@login_required
def change_admin(id, action):
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
user = User.query.filter_by(id=id).one()
if user is None:
return {"status": "error", "msg": "user not found"}
valid_actions = ['promote', 'demote']
if action not in valid_actions:
return {"status": "error", "msg": "invalid action"}
if action == "promote":
user.is_admin = True
else:
user.is_admin = False
db.session.commit()
return {"status": "success"}
@bp.route("/promote_from_waitlist/<int:id>")
@login_required
def promote_waitlist(id):
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
user = User.query.filter_by(id=id).one()
if user is None:
return {"status": "error", "msg": "user not found"}
user.waitlisted = False
db.session.commit()
return {"status": "success"}
@bp.route("/hackers.csv")
@login_required
def hackers_csv():
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
users = User.query.all()
return json_to_csv(User.create_json_output(users))
@bp.route("/hackers")
@login_required
def hackers():
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
users = User.query.all()
return User.create_json_output(users)
import json
import csv
from io import StringIO
def json_to_csv(data):
# Opening JSON file and loading the data
# into the variable data
json_data=[]
if(type(data) is json):
json_data=data
elif(type(data) is str):
json_data=json.loads(data)
else:
json_data = json.loads(json.dumps(data))
# now we will open a file for writing
csv_out = StringIO("")
# create the csv writer object
csv_writer = csv.writer(csv_out)
# Counter variable used for writing
# headers to the CSV file
count = 0
for e in json_data:
if count == 0:
# Writing headers of CSV file
header = e.keys()
csv_writer.writerow(header)
count += 1
# Writing data of CSV file
csv_writer.writerow(e.values())
csv_out.seek(0)
return csv_out.read()

View file

@ -17,6 +17,29 @@ class User(db.Model, UserMixin):
shirt_size = Column(String, nullable=True)
accomodations = Column(String, nullable=True)
checked_in = Column(Boolean, nullable=False, default=False)
school = Column(String, nullable=True)
phone = Column(String, nullable=True)
gender = Column(String, nullable=True)
def create_json_output(lis):
hackers = []
for u in lis:
hackers.append({
'checked_in': u.checked_in,
'waitlisted': u.waitlisted,
'admin': u.is_admin,
'id': u.id,
'email': u.email,
'first_name': u.first_name,
'last_name': u.last_name,
'phone_number': u.phone,
'shirt_size': u.shirt_size,
'special_needs': u.accomodations,
'school': u.school
})
return hackers
@login.user_loader

View file

@ -82,7 +82,7 @@ const promoteFromWaitlist = (id) => {
confirmButtonText: 'Yes, promote!',
confirmButtonColor: successColor
}, () => {
$.get('/promote_from_waitlist?mlh_id=' + id, (data) => {
$.get('/admin/promote_from_waitlist/' + id, (data) => {
let title = ''
let msg = ''
let type = ''
@ -110,7 +110,7 @@ const changeAdmin = (id, action) => {
confirmButtonText: 'Yes, ' + action + '!',
confirmButtonColor: errColor
}, () => {
$.get('/change_admin?mlh_id=' + id + '&action=' + action, (data) => {
$.get('/admin/change_admin/' + id + '/' + action, (data) => {
let title = ''
let msg = ''
let type = ''
@ -138,7 +138,7 @@ const drop = (id) => {
confirmButtonText: 'Yes, drop!',
confirmButtonColor: errColor
}, () => {
$.get('/drop?mlh_id=' + id, (data) => {
$.get('/admin/drop/' + id, (data) => {
let title = ''
let msg = ''
let type = ''
@ -166,7 +166,7 @@ const checkIn = (id) => {
confirmButtonText: 'Yes, check in!',
confirmButtonColor: successColor
}, () => {
$.get('/check_in?mlh_id=' + id, (data) => {
$.get('/admin/check_in/' + id, (data) => {
let title = ''
let msg = ''
let type = ''

View file

@ -13,7 +13,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/sweetalert/1.1.3/sweetalert.min.css">
<script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js"></script>
<script src="../static/js/admin.js"></script>
<script src="{{url_for('static', filename='js/admin.js')}}"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script>
</head>
@ -43,7 +43,8 @@
<h5>JSON object of users from MLH (Including dropped applications):</h5>
<p><a href="{{ mlh_url }}"><b>Do NOT share this URL.</b></a></p>
<h5>Get registered hackers only:</h5>
<p><a href="/hackers">JSON</a> <a href="/hackers.csv">CSV</a></p>
<p><a href="{{url_for('admin.hackers')}}">JSON</a> <a
href="{{url_for('admin.hackers_csv')}}">CSV</a></p>
</div>
<div class="row">
<div class="col-md-4">
@ -81,10 +82,12 @@
<script>
let schoolNames = []
let schoolNums = []
console.log("{{schools}}")
{% for school in schools %}
schoolNames.push('{{ school }}')
schoolNums.push({{ schools[school] }})
console.log("{{school}}")
{% endfor %}
let schoolCtx = document.getElementById('schoolCanvas')
@ -186,13 +189,13 @@
</thead>
<tbody>
<tr>
<td>{{ shirt_count['xxs'] }}</td>
<td>{{ shirt_count['xs'] }}</td>
<td>{{ shirt_count['s'] }}</td>
<td>{{ shirt_count['m'] }}</td>
<td>{{ shirt_count['l'] }}</td>
<td>{{ shirt_count['xl'] }}</td>
<td>{{ shirt_count['xxl'] }}</td>
<td>{{ shirt_count['XXS'] }}</td>
<td>{{ shirt_count['XS'] }}</td>
<td>{{ shirt_count['S'] }}</td>
<td>{{ shirt_count['M'] }}</td>
<td>{{ shirt_count['L'] }}</td>
<td>{{ shirt_count['XL'] }}</td>
<td>{{ shirt_count['XXL'] }}</td>
</tr>
</tbody>
</table>
@ -217,27 +220,27 @@
</thead>
<tbody>
{% for hacker in hackers %}
<tr id="{{ hacker['id'] }}-row">
<tr id="{{ hacker.id }}-row">
<td>
<div class="btn-group">
<a href="#" class="btn btn-primary dropdown-toggle" data-toggle="dropdown"><span
class="caret"></span></a>
<ul class="dropdown-menu">
{% if not hacker['checked_in'] %}
<li><a class="check_in" id="{{ hacker['id'] }}-check_in" href="#">Check In</a>
{% if not hacker.checked_in %}
<li><a class="check_in" id="{{ hacker.id }}-check_in" href="#">Check In</a>
</li>
{% endif %}
{% if hacker['waitlisted'] and not hacker['checked_in'] %}
{% if hacker.waitlisted and not hacker.checked_in %}
<li><a class="promote_from_waitlist"
id="{{ hacker['id'] }}-promote_from_waitlist"
href="#">Promote From Waitlist</a></li>
{% endif %}
<li class="divider"></li>
{% if not hacker['checked_in'] %}
{% if not hacker.checked_in %}
<li><a class="drop" id="{{ hacker['id'] }}-drop" href="#">Drop Application</a>
</li>
{% endif %}
{% if hacker['admin'] %}
{% if hacker.is_admin %}
<li><a class="demote_admin" id="{{ hacker['id'] }}-demote_admin" href="#">Demote
Admin</a>
</li>
@ -251,15 +254,15 @@
</td>
<td id="{{ hacker['id'] }}-checked_in">{{ hacker['checked_in'] }}</td>
<td id="{{ hacker['id'] }}-waitlisted">{{ hacker['waitlisted'] }}</td>
<td>{{ hacker['admin'] }}</td>
<td>{{ hacker.is_admin }}</td>
<td>{{ hacker['id'] }}</td>
<td>{{ hacker['registration_time'] }}</td>
<td>{{ hacker['email'] }}</td>
<td>{{ hacker['first_name'] + ' ' + hacker['last_name'] }}</td>
<td>{{ hacker['phone_number'] }}</td>
<td>{{ hacker['phone'] }}</td>
<td>{{ hacker['shirt_size'] }}</td>
<td>{{ hacker['special_needs'] }}</td>
<td>{{ hacker['school']['name'] }}</td>
<td>{{ hacker['accomodations'] }}</td>
<td>{{ hacker['school'] }}</td>
</tr>
{% endfor %}
</tbody>

View file

@ -0,0 +1,32 @@
"""empty message
Revision ID: 311c62fe5f49
Revises: 3f427be4ce8a
Create Date: 2022-12-06 11:08:18.571528
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '311c62fe5f49'
down_revision = '3f427be4ce8a'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.add_column(sa.Column('school', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.drop_column('school')
# ### end Alembic commands ###

View file

@ -0,0 +1,32 @@
"""empty message
Revision ID: 8d6ae751ec62
Revises: 311c62fe5f49
Create Date: 2022-12-06 11:08:55.896919
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8d6ae751ec62'
down_revision = '311c62fe5f49'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.add_column(sa.Column('phone', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.drop_column('phone')
# ### end Alembic commands ###

View file

@ -0,0 +1,32 @@
"""empty message
Revision ID: a14a95ec57b0
Revises: 8d6ae751ec62
Create Date: 2022-12-06 11:12:30.581556
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a14a95ec57b0'
down_revision = '8d6ae751ec62'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.add_column(sa.Column('gender', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('user', schema=None) as batch_op:
batch_op.drop_column('gender')
# ### end Alembic commands ###

View file

@ -2,6 +2,7 @@ alembic==1.8.1
click==8.1.3
Flask==2.2.2
Flask-Assets
Flask-CORS
Flask-Login==0.6.2
Flask-Migrate==4.0.0
Flask-SQLAlchemy==3.0.2