Tracking PR for registration rewrite #5

Merged
Muirrum merged 32 commits from rewrite into master 2022-12-15 18:32:08 -05:00
9 changed files with 333 additions and 23 deletions
Showing only changes of commit 72624a3f4e - Show all commits

View file

@ -3,12 +3,14 @@ from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
from flask_login import LoginManager from flask_login import LoginManager
from flask_assets import Bundle, Environment from flask_assets import Bundle, Environment
from flask_cors import CORS
db = SQLAlchemy() db = SQLAlchemy()
migrate = Migrate() migrate = Migrate()
login = LoginManager() login = LoginManager()
environment = Environment() environment = Environment()
cors = CORS()
def create_app(): def create_app():
app = Flask(__name__) app = Flask(__name__)
@ -19,6 +21,7 @@ def create_app():
migrate.init_app(app, db) migrate.init_app(app, db)
login.init_app(app) login.init_app(app)
environment.init_app(app) environment.init_app(app)
cors.init_app(app)
scss = Bundle('css/style.scss', filters='scss', scss = Bundle('css/style.scss', filters='scss',
output='css/style.css') output='css/style.css')
@ -28,8 +31,10 @@ def create_app():
from . import registration from . import registration
from . import dashboard from . import dashboard
from . import admin
app.register_blueprint(registration.bp) app.register_blueprint(registration.bp)
app.register_blueprint(dashboard.bp) app.register_blueprint(dashboard.bp)
app.register_blueprint(admin.bp)
return app 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) shirt_size = Column(String, nullable=True)
accomodations = Column(String, nullable=True) accomodations = Column(String, nullable=True)
checked_in = Column(Boolean, nullable=False, default=False) checked_in = Column(Boolean, nullable=False, default=False)
school = Column(String, nullable=True)
phone = Column(String, nullable=True)
gender = Column(String, nullable=True)
Muirrum commented 2022-12-08 20:25:22 -05:00 (Migrated from github.com)
Review

@willhockey20 is this something that actually needs to be stored? I only included it bc it was in the original registration system.

@willhockey20 is this something that actually needs to be stored? I only included it bc it was in the original registration system.
wfryan commented 2022-12-08 20:29:57 -05:00 (Migrated from github.com)
Review

The data was required by mlh, still could be nice to have so I'd keep it

The data was required by mlh, still could be nice to have so I'd keep it
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 @login.user_loader

View file

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

View file

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