From cc4fe8cea4bbcc2865a7bad9a72f881bfaff072a Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Fri, 1 Dec 2023 13:26:06 -0500 Subject: [PATCH 1/6] Scaffold event models Will be used to check people into workshops and meals to track event attendance over time, could be used to generate a dynamic schedule Signed-Off-By: Cara Salter --- goathacks/models.py | 43 ++++++++++++++++++- .../d6917765911f_create_event_table.py | 24 +++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 migrations/versions/d6917765911f_create_event_table.py diff --git a/goathacks/models.py b/goathacks/models.py index 03d206d..709e0d8 100644 --- a/goathacks/models.py +++ b/goathacks/models.py @@ -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) diff --git a/migrations/versions/d6917765911f_create_event_table.py b/migrations/versions/d6917765911f_create_event_table.py new file mode 100644 index 0000000..da2a817 --- /dev/null +++ b/migrations/versions/d6917765911f_create_event_table.py @@ -0,0 +1,24 @@ +"""create event table + +Revision ID: d6917765911f +Revises: 261c004968a4 +Create Date: 2023-12-01 13:22:41.055221 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'd6917765911f' +down_revision = '261c004968a4' +branch_labels = None +depends_on = None + + +def upgrade(): + pass + + +def downgrade(): + pass -- 2.43.5 From 1432d0d7fb51b6aced3d5218e2c8fbb9a7917b2e Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Fri, 1 Dec 2023 13:26:57 -0500 Subject: [PATCH 2/6] Update makefile Remove spurious 'txt' --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 12da1f2..2cefaa7 100644 --- a/Makefile +++ b/Makefile @@ -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 -- 2.43.5 From 006f54255f56e285e6a563ea86db049084853ff7 Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Fri, 1 Dec 2023 13:31:17 -0500 Subject: [PATCH 3/6] Fix migration for event table Didn't autogenerate, oops --- .../858e0d45876f_create_event_table.py | 46 +++++++++++++++++++ .../d6917765911f_create_event_table.py | 24 ---------- 2 files changed, 46 insertions(+), 24 deletions(-) create mode 100644 migrations/versions/858e0d45876f_create_event_table.py delete mode 100644 migrations/versions/d6917765911f_create_event_table.py diff --git a/migrations/versions/858e0d45876f_create_event_table.py b/migrations/versions/858e0d45876f_create_event_table.py new file mode 100644 index 0000000..736364a --- /dev/null +++ b/migrations/versions/858e0d45876f_create_event_table.py @@ -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 ### diff --git a/migrations/versions/d6917765911f_create_event_table.py b/migrations/versions/d6917765911f_create_event_table.py deleted file mode 100644 index da2a817..0000000 --- a/migrations/versions/d6917765911f_create_event_table.py +++ /dev/null @@ -1,24 +0,0 @@ -"""create event table - -Revision ID: d6917765911f -Revises: 261c004968a4 -Create Date: 2023-12-01 13:22:41.055221 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'd6917765911f' -down_revision = '261c004968a4' -branch_labels = None -depends_on = None - - -def upgrade(): - pass - - -def downgrade(): - pass -- 2.43.5 From 74394abdfec9ce3ef3c9e37a4343bab3e713f5b5 Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Fri, 1 Dec 2023 14:21:48 -0500 Subject: [PATCH 4/6] Scaffold event blueprint Only has /events/checkin/ for now, but that's progress! --- goathacks/__init__.py | 2 ++ goathacks/events/__init__.py | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 goathacks/events/__init__.py diff --git a/goathacks/__init__.py b/goathacks/__init__.py index d11608c..52ec1c0 100644 --- a/goathacks/__init__.py +++ b/goathacks/__init__.py @@ -35,10 +35,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 diff --git a/goathacks/events/__init__.py b/goathacks/events/__init__.py new file mode 100644 index 0000000..e5c5c5d --- /dev/null +++ b/goathacks/events/__init__.py @@ -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/") +@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).one() + 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")) -- 2.43.5 From 9d08c271359fb30a01b444c2f319f725d8b5f69c Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Fri, 1 Dec 2023 16:25:32 -0500 Subject: [PATCH 5/6] Editing and creating events! --- goathacks/admin/__init__.py | 1 + goathacks/admin/events.py | 69 +++++++++++++++++++++++ goathacks/admin/forms.py | 12 ++++ goathacks/events/__init__.py | 2 +- goathacks/templates/events/list.html | 38 +++++++++++++ goathacks/templates/events/new_event.html | 39 +++++++++++++ 6 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 goathacks/admin/events.py create mode 100644 goathacks/admin/forms.py create mode 100644 goathacks/templates/events/list.html create mode 100644 goathacks/templates/events/new_event.html diff --git a/goathacks/admin/__init__.py b/goathacks/admin/__init__.py index b650bd2..4536cf2 100644 --- a/goathacks/admin/__init__.py +++ b/goathacks/admin/__init__.py @@ -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 diff --git a/goathacks/admin/events.py b/goathacks/admin/events.py new file mode 100644 index 0000000..cbcfdfc --- /dev/null +++ b/goathacks/admin/events.py @@ -0,0 +1,69 @@ +from flask import 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 + +@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/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/", 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) diff --git a/goathacks/admin/forms.py b/goathacks/admin/forms.py new file mode 100644 index 0000000..c6e7272 --- /dev/null +++ b/goathacks/admin/forms.py @@ -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") diff --git a/goathacks/events/__init__.py b/goathacks/events/__init__.py index e5c5c5d..81007e1 100644 --- a/goathacks/events/__init__.py +++ b/goathacks/events/__init__.py @@ -15,7 +15,7 @@ def workshop_checkin(id): return redirect(url_for("dashboard.home")) checkin = EventCheckins.query.filter_by(event_id=id, - user_id=current_user.id).one() + 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")) diff --git a/goathacks/templates/events/list.html b/goathacks/templates/events/list.html new file mode 100644 index 0000000..4b37285 --- /dev/null +++ b/goathacks/templates/events/list.html @@ -0,0 +1,38 @@ +{% extends 'base.html' %} + +{% block content %} + +
+

Events

+ + + + + + + + + + + + + + + {% for event in events %} + + + + + + + + + + + {% endfor %} + +
IDNameLocationStartEndCategoryChecked inNew
{{ event.id }}{{ event.name }}{{ event.location }}{{ event.start_time }}{{ event.end_time }}{{ event.category }}{{ event.get_checkins()|length }}Edit
+
+ +{% endblock %} diff --git a/goathacks/templates/events/new_event.html b/goathacks/templates/events/new_event.html new file mode 100644 index 0000000..caba4bb --- /dev/null +++ b/goathacks/templates/events/new_event.html @@ -0,0 +1,39 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+

Create/Edit Event

+
+
+
+
+
+
+ {{ form.csrf_token }} +
+ {{ form.name}}
{{ form.name.label }} +
+
+ {{ form.description}}
{{form.description.label}} +
+
+ {{ form.location}}
{{form.location.label}} +
+
+ {{form.start_time}}
{{form.start_time.label}} +
+
+ {{form.end_time}}
{{form.end_time.label}} +
+
+ {{form.category}}
{{form.category.label}} +
+
+ {{form.submit}} +
+
+
+
+{% endblock %} -- 2.43.5 From dfd0c33be7d726329c06a4ac8d904bd28f7fad7e Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Fri, 1 Dec 2023 17:05:54 -0500 Subject: [PATCH 6/6] Enable creating and editing of events, and QR codes --- goathacks/__init__.py | 3 +++ goathacks/admin/events.py | 27 +++++++++++++++++++++++++- goathacks/templates/events/list.html | 5 +++++ goathacks/templates/events/qrcode.html | 6 ++++++ requirements.txt | 1 + 5 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 goathacks/templates/events/qrcode.html diff --git a/goathacks/__init__.py b/goathacks/__init__.py index 52ec1c0..be0773b 100644 --- a/goathacks/__init__.py +++ b/goathacks/__init__.py @@ -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') diff --git a/goathacks/admin/events.py b/goathacks/admin/events.py index cbcfdfc..4026db6 100644 --- a/goathacks/admin/events.py +++ b/goathacks/admin/events.py @@ -1,9 +1,13 @@ -from flask import render_template, redirect, request, url_for, flash +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(): @@ -14,6 +18,14 @@ def list_events(): 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(): @@ -67,3 +79,16 @@ def edit_event(id): form = forms.EventForm(obj=event) return render_template("events/new_event.html", form=form) + +@bp.route("/events/qrcode/") +@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) diff --git a/goathacks/templates/events/list.html b/goathacks/templates/events/list.html index 4b37285..2aae898 100644 --- a/goathacks/templates/events/list.html +++ b/goathacks/templates/events/list.html @@ -4,6 +4,8 @@

Events

+ Get a JSON readout of events here @@ -15,6 +17,7 @@ + @@ -28,6 +31,8 @@ + {% endfor %} diff --git a/goathacks/templates/events/qrcode.html b/goathacks/templates/events/qrcode.html new file mode 100644 index 0000000..ce48b9e --- /dev/null +++ b/goathacks/templates/events/qrcode.html @@ -0,0 +1,6 @@ + + QR Code for {{ event.name }} + + + + diff --git a/requirements.txt b/requirements.txt index f4c8239..efbd546 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ alembic==1.8.1 click==8.1.3 Flask==2.2.2 +Flask-QRCode Flask-Assets Flask-CORS Flask-Mail -- 2.43.5
End Category Checked inQR Code New
{{ event.end_time }} {{ event.category }} {{ event.get_checkins()|length }}QR Code Edit