Merge branch 'master' into rewrite
This commit is contained in:
commit
c863b4064f
12 changed files with 322 additions and 2 deletions
2
Makefile
2
Makefile
|
@ -20,7 +20,7 @@ init_env:
|
||||||
source .venv/bin/activate && pip3 install -r requirements.txt txt
|
source .venv/bin/activate && pip3 install -r requirements.txt txt
|
||||||
|
|
||||||
upgrade_env:
|
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:
|
make_migrations:
|
||||||
source .venv/bin/activate && flask db migrate
|
source .venv/bin/activate && flask db migrate
|
||||||
|
|
|
@ -6,6 +6,8 @@ from flask_assets import Bundle, Environment
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_mail import Mail, email_dispatched
|
from flask_mail import Mail, email_dispatched
|
||||||
from flask_bootstrap import Bootstrap5
|
from flask_bootstrap import Bootstrap5
|
||||||
|
from flask_qrcode import QRcode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
@ -15,6 +17,7 @@ environment = Environment()
|
||||||
cors = CORS()
|
cors = CORS()
|
||||||
mail = Mail()
|
mail = Mail()
|
||||||
bootstrap = Bootstrap5()
|
bootstrap = Bootstrap5()
|
||||||
|
qrcode = QRcode()
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
@ -28,6 +31,7 @@ def create_app():
|
||||||
cors.init_app(app)
|
cors.init_app(app)
|
||||||
mail.init_app(app)
|
mail.init_app(app)
|
||||||
bootstrap.init_app(app)
|
bootstrap.init_app(app)
|
||||||
|
qrcode.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')
|
||||||
|
@ -38,10 +42,12 @@ def create_app():
|
||||||
from . import registration
|
from . import registration
|
||||||
from . import dashboard
|
from . import dashboard
|
||||||
from . import admin
|
from . import admin
|
||||||
|
from . import events
|
||||||
|
|
||||||
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)
|
app.register_blueprint(admin.bp)
|
||||||
|
app.register_blueprint(events.bp)
|
||||||
|
|
||||||
|
|
||||||
from goathacks import cli
|
from goathacks import cli
|
||||||
|
|
|
@ -7,6 +7,7 @@ from goathacks.models import User
|
||||||
bp = Blueprint("admin", __name__, url_prefix="/admin")
|
bp = Blueprint("admin", __name__, url_prefix="/admin")
|
||||||
|
|
||||||
from goathacks import db, mail as app_mail
|
from goathacks import db, mail as app_mail
|
||||||
|
from goathacks.admin import events
|
||||||
|
|
||||||
@bp.route("/")
|
@bp.route("/")
|
||||||
@login_required
|
@login_required
|
||||||
|
|
94
goathacks/admin/events.py
Normal file
94
goathacks/admin/events.py
Normal 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
12
goathacks/admin/forms.py
Normal 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")
|
31
goathacks/events/__init__.py
Normal file
31
goathacks/events/__init__.py
Normal 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"))
|
|
@ -41,7 +41,6 @@ class User(db.Model, UserMixin):
|
||||||
|
|
||||||
return hackers
|
return hackers
|
||||||
|
|
||||||
|
|
||||||
@login.user_loader
|
@login.user_loader
|
||||||
def user_loader(user_id):
|
def user_loader(user_id):
|
||||||
return User.query.filter_by(id=user_id).first()
|
return User.query.filter_by(id=user_id).first()
|
||||||
|
@ -56,3 +55,45 @@ class PwResetRequest(db.Model):
|
||||||
id = Column(String, primary_key=True)
|
id = Column(String, primary_key=True)
|
||||||
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
|
user_id = Column(Integer, ForeignKey('user.id'), nullable=False)
|
||||||
expires = Column(DateTime, 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)
|
||||||
|
|
43
goathacks/templates/events/list.html
Normal file
43
goathacks/templates/events/list.html
Normal 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 %}
|
39
goathacks/templates/events/new_event.html
Normal file
39
goathacks/templates/events/new_event.html
Normal 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 %}
|
6
goathacks/templates/events/qrcode.html
Normal file
6
goathacks/templates/events/qrcode.html
Normal 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>
|
46
migrations/versions/858e0d45876f_create_event_table.py
Normal file
46
migrations/versions/858e0d45876f_create_event_table.py
Normal 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 ###
|
|
@ -1,6 +1,7 @@
|
||||||
alembic==1.8.1
|
alembic==1.8.1
|
||||||
click==8.1.3
|
click==8.1.3
|
||||||
Flask==2.2.2
|
Flask==2.2.2
|
||||||
|
Flask-QRCode
|
||||||
Flask-Assets
|
Flask-Assets
|
||||||
Flask-CORS
|
Flask-CORS
|
||||||
Flask-Mail
|
Flask-Mail
|
||||||
|
|
Loading…
Add table
Reference in a new issue