Compare commits

..

4 commits

Author SHA1 Message Date
Cara Salter
a0402feab0 about: Update events coordinator ID for automatic photo 2024-08-31 12:57:28 -04:00
Cara Salter
596847ca0a admin: Add events coordinator to officer form options 2024-08-31 12:53:09 -04:00
Cara Salter
228ff85e6a
finish event checkins 2024-08-28 16:51:15 -04:00
Cara Salter
302fe4289a Init event checkin structure 2024-08-28 16:16:45 -04:00
13 changed files with 158 additions and 99 deletions

View file

@ -8,7 +8,7 @@ from io import BytesIO
from PIL import Image from PIL import Image
import base64 import base64
from acmsite.models import Link, Officer, User, Event from acmsite.models import EventCheckin, Link, Officer, User, Event
from acmsite import models from acmsite import models
from .forms import EventForm, LinkForm, OfficerForm from .forms import EventForm, LinkForm, OfficerForm
@ -155,6 +155,28 @@ def update_create_event(id):
return redirect(url_for("admin.events")) return redirect(url_for("admin.events"))
@bp.route("/event/<string:id>/checkins")
@login_required
def event_checkins(id):
if not current_user.is_admin:
flash("Unauthorized")
return redirect(url_for("dashboard.home"))
event = db.session.execute(db.select(Event).where(Event.id == id)).scalar_one_or_none()
if event is None:
flash("Invalid event")
return redirect(url_for("admin.events"))
checkins = db.session.execute(db.select(EventCheckin).where(EventCheckin.event ==
id).join(User)).scalars()
processed_checkins = []
for c in checkins:
user = db.session.execute(db.select(User).where(User.id == c.user)).scalar_one_or_none()
processed_checkins.append({"name": f"{user.first_name} {user.last_name}", "email": user.email})
return render_template("admin/checkins.html", checkins=processed_checkins,e=event)
@bp.route("/links") @bp.route("/links")
@login_required @login_required
def links(): def links():

View file

@ -17,7 +17,8 @@ class LinkForm(FlaskForm):
class OfficerForm(FlaskForm): class OfficerForm(FlaskForm):
position = SelectField("Position", choices=["President", "Vice President", position = SelectField("Position", choices=["President", "Vice President",
"Treasurer", "Secretary", "PR Chair", "Hackathon Manager 1", "Treasurer", "Secretary", "PR Chair",
"Event Coordinator", "Hackathon Manager 1",
"Hackathon Manager 2", "System Administrator"], "Hackathon Manager 2", "System Administrator"],
validators=[DataRequired()]) validators=[DataRequired()])
term_start = DateField("Term Start", validators=[DataRequired()]) term_start = DateField("Term Start", validators=[DataRequired()])

View file

@ -10,12 +10,9 @@ bp = Blueprint('auth', __name__, url_prefix='/auth')
from acmsite import oauth from acmsite import oauth
@bp.route("/login")
def login():
return render_template('login.html')
@bp.route('/oauth') @bp.route('/login')
def oauth_redirect(): def login():
return oauth.azure.authorize_redirect(url_for('auth.oauth2_callback', return oauth.azure.authorize_redirect(url_for('auth.oauth2_callback',
_external=True)) _external=True))

View file

@ -1,8 +1,10 @@
from werkzeug.security import generate_password_hash, check_password_hash
from flask import Blueprint, render_template, request, flash, redirect, url_for
from flask_login import current_user, login_required
from acmsite.dashboard.forms import PasswordForm from datetime import datetime
from flask import Blueprint, current_app, flash, redirect, render_template, url_for
from flask_login import login_required, current_user
from ulid import ulid
from acmsite.models import Event, EventCheckin
from acmsite import db from acmsite import db
@ -11,29 +13,35 @@ bp = Blueprint('dashboard', __name__, url_prefix='/dashboard')
@bp.route("/") @bp.route("/")
@login_required @login_required
def home(): def home():
form = PasswordForm() now = datetime.now()
return render_template('dashboard.html', form=form) events = db.session.execute(db.select(Event).where(Event.start_time < now,
Event.end_time >
now)).scalars()
return render_template('dashboard.html', events=events)
@bp.route("/change_password", methods=["POST"]) @bp.route("/checkin/<string:event_id>")
@login_required @login_required
def change_password(): def checkin(event_id):
form = PasswordForm(request.form) # actually first check if the event even exists
event = db.get_or_404(Event, event_id)
# first check if this user has already checked in
checkins = db.session.execute(db.select(EventCheckin).where(EventCheckin.user ==
current_user.id,
EventCheckin.event
==event_id)).scalar_one_or_none()
if form.validate_on_submit(): current_app.logger.debug(checkins)
current_password = request.form.get("current_password") if checkins is None:
new_password = request.form.get("new_password") # There's not a checkin already, let's create one!
password_confirm = request.form.get("password_confirm") check = EventCheckin(
id = ulid(),
if new_password == password_confirm: user = current_user.id,
if current_password == '' and current_user.password == '': event = event_id)
current_user.password = generate_password_hash(new_password) db.session.add(check)
flash("Password set successfully.")
elif check_password_hash(current_user.password, current_password):
current_user.password = generate_password_hash(new_password)
flash("Password updated successfully.")
else:
flash("Incorrect password.")
else:
flash("Passwords do not match!")
db.session.commit() db.session.commit()
flash(f"Checked in to {event.name} successfully")
return redirect(url_for("dashboard.home"))
else:
flash(f"You've already checked in to {event.name}")
return redirect(url_for("dashboard.home")) return redirect(url_for("dashboard.home"))

View file

@ -1,9 +0,0 @@
from flask_wtf import FlaskForm
from wtforms.fields import PasswordField
from wtforms.validators import DataRequired
class PasswordForm(FlaskForm):
current_password = PasswordField('Current Password')
new_password = PasswordField('New Password', validators=[DataRequired()])
password_confirm = PasswordField('Confirm New Password',
validators=[DataRequired()])

View file

@ -80,6 +80,12 @@ class Event(db.Model):
"end_time": self.end_time.isoformat(), "end_time": self.end_time.isoformat(),
} }
class EventCheckin(db.Model):
__tablename__ = "acm_checkins"
id = Column(String, primary_key=True)
user = Column(String, ForeignKey("acm_users.id"), nullable=False)
event = Column(String, ForeignKey("acm_events.id"), nullable=False)
class Link(db.Model): class Link(db.Model):
__tablename__ = "acm_links" __tablename__ = "acm_links"
id = Column(String, primary_key=True) id = Column(String, primary_key=True)

View file

@ -74,12 +74,12 @@ very friendly. You can find a list of our upcoming events and meetings <a href="
</div> </div>
<div class="col"> <div class="col">
<div class="card" id="events-coordinator"> <div class="card" id="events-coordinator">
<img class="card-img-top" id="Events Coordinator-img" src="{{ url_for('static', <img class="card-img-top" id="Event Coordinator-img" src="{{ url_for('static',
filename='img/officers/placeholder.png') filename='img/officers/placeholder.png')
}}" alt="Events Coordinator"> }}" alt="Event Coordinator">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">Events Coordinator</h5> <h5 class="card-title">Events Coordinator</h5>
<p class="card-text" id="Events Coordinator-name">Unavailable</p> <p class="card-text" id="Event Coordinator-name">Unavailable</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,35 @@
{% extends "admin/admin-layout.html" %}
{% import "bootstrap5/form.html" as wtf %}
{% block app_content %}
<h1>Checkins for `{{ e.name }}`</h1>
{% for c in checkins %}
{{ c.__dict__ }}
{% endfor %}
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody>
{% for c in checkins %}
<tr>
<td>{{ c.name }}</td>
<td>{{ c.email }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h3>Copy-pastable list for CampusGroups</h3>
{% for c in checkins %}
{{ c.email }},
{% endfor %}
{% endblock %}

View file

@ -40,6 +40,11 @@
<li class="dropdown-item"> <li class="dropdown-item">
<a href="#deleteModal" <a href="#deleteModal"
data-bs-toggle="modal" data-id="{{ e.id }}">Delete Event</a> data-bs-toggle="modal" data-id="{{ e.id }}">Delete Event</a>
</li>
<li class="dropdown-item">
<a href="{{ url_for('admin.event_checkins',
id=e.id) }}">Event Checkins</a>
</li>
</ul> </ul>
</div> </div>
</td> </td>

View file

@ -4,6 +4,19 @@
<h1>Welcome back, {{ current_user.first_name }}!</h1> <h1>Welcome back, {{ current_user.first_name }}!</h1>
{% if events %}
<p>The following events are available for check-in:</p>
{% for e in events %}
<h5>{{ e.name }} <a class="btn btn-primary" href="{{ url_for('dashboard.checkin', event_id=e.id)
}}">Check in</a></h5>
{% endfor %}
{% else %}
<p>There are no events available for check-in.</p>
{% endif %}
<hr/>
<p>For a list of upcoming events, take a look at our <a href="{{ <p>For a list of upcoming events, take a look at our <a href="{{
url_for('main.events') url_for('main.events')
}}">events }}">events
@ -13,46 +26,4 @@
unless you're an unless you're an
officer!</p> officer!</p>
<button type="button" class="btn btn-secondary" data-bs-toggle="modal"
data-bs-target="#passwordModal">Change or Set
Local Password</button>
<!-- Modals -->
<div class="modal" id="passwordModal" tabindex="-1" aria-labelledby="passwordModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="passwordModalLabel">Change Password</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="form" id="edit-form" action="/dashboard/change_password" role="form" method="post">
<div class="modal-body">
{{ form.csrf_token }}
<div class="form-floating mb-3 required">
{{ form.current_password(class="form-control") }}
{{ form.current_password.label() }}
</div>
<div class="row">
<div class="col">
<div class="form-floating mb-3 required">
{{ form.new_password(class="form-control") }}
{{ form.new_password.label() }}
</div>
</div>
<div class="col">
<div class="form-floating mb-3 required">
{{ form.password_confirm(class="form-control") }}
{{ form.password_confirm.label() }}
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-primary" id="edit-save">Save changes</button>
</div>
</form>
</div>
</div>
</div>
{% endblock app_content %} {% endblock app_content %}

View file

@ -40,7 +40,7 @@
{% endif %} {% endif %}
{{ render_nav_item('auth.logout', 'Logout') }} {{ render_nav_item('auth.logout', 'Logout') }}
{% else %} {% else %}
{{ render_nav_item('auth.login', 'Login') }} {{ render_nav_item('auth.login', 'Login with WPI') }}
{% endif %} {% endif %}
</ul> </ul>
</div> </div>

View file

@ -1,12 +0,0 @@
{% extends "layout.html" %}
{% block app_content %}
<h1>Login Methods</h1>
<div>
<a class="btn btn-primary" href="{{ url_for('auth.oauth_redirect') }}">Login with WPI</a>
</div>
<div class="mt-1 mb-3">
<a class="btn btn-secondary" href="">Login with Local Account</a>
</div>
{% endblock app_content %}

View file

@ -0,0 +1,35 @@
"""add event checkins
Revision ID: 53a76e988b5a
Revises: 300f24071c14
Create Date: 2024-08-25 15:18:22.451548
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '53a76e988b5a'
down_revision = '300f24071c14'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('acm_checkins',
sa.Column('id', sa.String(), nullable=False),
sa.Column('user', sa.String(), nullable=False),
sa.Column('event', sa.String(), nullable=False),
sa.ForeignKeyConstraint(['event'], ['acm_events.id'], ),
sa.ForeignKeyConstraint(['user'], ['acm_users.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('acm_checkins')
# ### end Alembic commands ###