Compare commits
4 commits
local-acco
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a0402feab0 | ||
![]() |
596847ca0a | ||
![]() |
228ff85e6a | ||
![]() |
302fe4289a |
13 changed files with 158 additions and 99 deletions
|
@ -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():
|
||||||
|
|
|
@ -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()])
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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()])
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
35
acmsite/templates/admin/checkins.html
Normal file
35
acmsite/templates/admin/checkins.html
Normal 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 %}
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 %}
|
|
35
migrations/versions/53a76e988b5a_add_event_checkins.py
Normal file
35
migrations/versions/53a76e988b5a_add_event_checkins.py
Normal 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 ###
|
Loading…
Add table
Reference in a new issue