Allow for workshop/meal checkins #19

Merged
Muirrum merged 6 commits from meal-checkin into master 2023-12-01 17:30:16 -05:00
12 changed files with 321 additions and 2 deletions

View file

@ -20,7 +20,7 @@ init_env:
source .venv/bin/activate && pip3 install -r requirements.txt txt
upgrade_env:
source .venv/bin/activate && pip3 install --upgrade -r requirements.txt txt
source .venv/bin/activate && pip3 install --upgrade -r requirements.txt
make_migrations:
source .venv/bin/activate && flask db migrate

View file

@ -5,6 +5,7 @@ from flask_login import LoginManager
from flask_assets import Bundle, Environment
from flask_cors import CORS
from flask_mail import Mail, email_dispatched
from flask_qrcode import QRcode
db = SQLAlchemy()
@ -13,6 +14,7 @@ login = LoginManager()
environment = Environment()
cors = CORS()
mail = Mail()
qrcode = QRcode()
def create_app():
app = Flask(__name__)
@ -25,6 +27,7 @@ def create_app():
environment.init_app(app)
cors.init_app(app)
mail.init_app(app)
qrcode.init_app(app)
scss = Bundle('css/style.scss', filters='scss',
output='css/style.css')
@ -35,10 +38,12 @@ def create_app():
from . import registration
from . import dashboard
from . import admin
from . import events
app.register_blueprint(registration.bp)
app.register_blueprint(dashboard.bp)
app.register_blueprint(admin.bp)
app.register_blueprint(events.bp)
from goathacks import cli

View file

@ -7,6 +7,7 @@ from goathacks.models import User
bp = Blueprint("admin", __name__, url_prefix="/admin")
from goathacks import db, mail as app_mail
from goathacks.admin import events
@bp.route("/")
@login_required

94
goathacks/admin/events.py Normal file
View file

@ -0,0 +1,94 @@
import flask
from flask import Response, render_template, redirect, request, url_for, flash
from flask_login import current_user, login_required
from goathacks.admin import bp, forms
from goathacks import db
from goathacks.models import Event
import io, qrcode
import qrcode.image.pure
@bp.route("/events")
@login_required
def list_events():
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
events = Event.query.all()
return render_template("events/list.html", events=events)
@bp.route("/events/events.json")
@login_required
def events_json():
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
events = Event.query.all()
return Event.create_json_output(events)
@bp.route("/events/new", methods=["GET", "POST"])
@login_required
def new_event():
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
form = forms.EventForm(request.form)
if request.method == 'POST':
name = request.form.get("name")
description = request.form.get("description")
location = request.form.get("location")
start_time = request.form.get("start_time")
end_time = request.form.get("end_time")
category = request.form.get("category")
event = Event(
name = name,
description = description,
location = location,
start_time = start_time,
end_time = end_time,
category = category
)
db.session.add(event)
db.session.commit()
flash("Created event")
return redirect(url_for("admin.list_events"))
return render_template("events/new_event.html", form=form)
@bp.route("/events/edit/<int:id>", methods=["GET", "POST"])
@login_required
def edit_event(id):
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
event = Event.query.filter_by(id=id).one()
if event is None:
flash("Event does not exist")
return redirect(url_for("admin.list_events"))
form = forms.EventForm(request.form)
if request.method == 'POST':
form.populate_obj(event)
db.session.commit()
flash("Updated event")
return redirect(url_for("admin.list_events"))
else:
form = forms.EventForm(obj=event)
return render_template("events/new_event.html", form=form)
@bp.route("/events/qrcode/<int:id>")
@login_required
def qrcode_event(id):
if not current_user.is_admin:
return redirect(url_for("dashboard.home"))
event = Event.query.filter_by(id=id).first()
if event is None:
flash("Event does not exist")
return redirect(url_for("admin.list_events"))
return render_template("events/qrcode.html", event=event)

12
goathacks/admin/forms.py Normal file
View file

@ -0,0 +1,12 @@
from flask_wtf import FlaskForm
from wtforms import StringField, DateTimeField, SubmitField, TextAreaField
from wtforms.validators import DataRequired
class EventForm(FlaskForm):
name = StringField("Name", validators=[DataRequired()])
description = TextAreaField("Description")
location = StringField("Location", validators=[DataRequired()])
start_time = DateTimeField("Start Time", validators=[DataRequired()])
end_time = DateTimeField("End Time", validators=[DataRequired()])
category = StringField("Category")
submit = SubmitField("Submit")

View file

@ -0,0 +1,31 @@
from flask import Blueprint, current_app, flash, redirect, url_for
from flask_login import current_user, login_required
from goathacks.models import Event, EventCheckins
from goathacks import db
bp = Blueprint("events", __name__, url_prefix="/events")
@bp.route("/checkin/<int:id>")
@login_required
def workshop_checkin(id):
event = Event.query.filter_by(id=id).one()
if event is None:
flash("That event does not exist!")
return redirect(url_for("dashboard.home"))
checkin = EventCheckins.query.filter_by(event_id=id,
user_id=current_user.id).first()
if checkin is not None:
flash("You've already checked into this event!")
return redirect(url_for("dashboard.home"))
checkin = EventCheckins(
user_id=current_user.id,
event_id=id
)
db.session.add(checkin)
db.session.commit()
flash("You've successfully checked in!")
return redirect(url_for("dashboard.home"))

View file

@ -41,7 +41,6 @@ class User(db.Model, UserMixin):
return hackers
@login.user_loader
def user_loader(user_id):
return User.query.filter_by(id=user_id).first()
@ -56,3 +55,45 @@ class PwResetRequest(db.Model):
id = Column(String, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
expires = Column(DateTime, nullable=False)
"""
Represents an event within the hackathon, that can be checked into
"""
class Event(db.Model):
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
description = Column(String, nullable=True)
location = Column(String, nullable=False)
start_time = Column(DateTime, nullable=False)
end_time = Column(DateTime, nullable=False)
category = Column(String, nullable=True)
def create_json_output(lis):
events = []
for e in lis:
events.append({
'id': e.id,
'name': e.name,
'description': e.description,
'location': e.location,
'start': e.start_time,
'end': e.end_time,
'category': e.category
})
return events
def get_checkins(self):
checkins = EventCheckins.query.filter_by(event_id=self.id).all()
return checkins
class EventCheckins(db.Model):
__tablename__ = "event_checkins"
id = Column(Integer, primary_key=True)
event_id = Column(Integer, ForeignKey('event.id'), nullable=False)
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)

View file

@ -0,0 +1,43 @@
{% extends 'base.html' %}
{% block content %}
<div style="height: 100%; color: white;">
<h2>Events</h2>
Get a JSON readout of events <a href="{{ url_for('admin.events_json')
}}">here</a>
<table class="table table-striped table-hover table-condensed" style="color:
white;">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Location</th>
<th>Start</th>
<th>End</th>
<th>Category</th>
<th>Checked in</th>
<th>QR Code</th>
<th><a href="{{url_for('admin.new_event')}}">New</a></th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr id="{{event.id}}-row">
<td>{{ event.id }}</td>
<td>{{ event.name }}</td>
<td>{{ event.location }}</td>
<td>{{ event.start_time }}</td>
<td>{{ event.end_time }}</td>
<td>{{ event.category }}</td>
<td>{{ event.get_checkins()|length }}</td>
<td><a href='{{ url_for("admin.qrcode_event", id=event.id)
}}'>QR Code</a></td>
<td><a href="{{url_for('admin.edit_event', id=event.id)}}">Edit</a></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}

View file

@ -0,0 +1,39 @@
{% extends 'base.html' %}
{% block content %}
<div style="height: 100%;">
<div id="registration-banner" class="parallax-container valign-wrapper">
<div class="section">
<h3 class="header-center text-darken-2">Create/Edit Event</h3>
</div>
</div>
</div>
<div class="container">
<div class="section" style="background-color: #974355; padding: 20px;">
<form method="post">
{{ form.csrf_token }}
<div>
{{ form.name}}<br/> {{ form.name.label }}
</div>
<div>
{{ form.description}}<br/>{{form.description.label}}
</div>
<div>
{{ form.location}}<br/>{{form.location.label}}
</div>
<div>
{{form.start_time}}<br/>{{form.start_time.label}}
</div>
<div>
{{form.end_time}}<br/>{{form.end_time.label}}
</div>
<div>
{{form.category}}<br/>{{form.category.label}}
</div>
<div>
{{form.submit}}
</div>
</form>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,6 @@
<head>
<title>QR Code for {{ event.name }}</title>
</head>
<body>
<img src="{{ qrcode(url_for('events.workshop_checkin', id=event.id)) }}">
</body>

View file

@ -0,0 +1,46 @@
"""create_event_table
Revision ID: 858e0d45876f
Revises: 261c004968a4
Create Date: 2023-12-01 13:31:00.955470
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '858e0d45876f'
down_revision = '261c004968a4'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('event',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=False),
sa.Column('description', sa.String(), nullable=True),
sa.Column('location', sa.String(), nullable=False),
sa.Column('start_time', sa.DateTime(), nullable=False),
sa.Column('end_time', sa.DateTime(), nullable=False),
sa.Column('category', sa.String(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('event_checkins',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('event_id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['event_id'], ['event.id'], ),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('event_checkins')
op.drop_table('event')
# ### end Alembic commands ###

View file

@ -1,6 +1,7 @@
alembic==1.8.1
click==8.1.3
Flask==2.2.2
Flask-QRCode
Flask-Assets
Flask-CORS
Flask-Mail