Compare commits

...
Sign in to create a new pull request.

9 commits

Author SHA1 Message Date
ngolp
7a52071db3 missed that bit in the github issue where Cara wanted it to redirect to login page. quick fix 2024-10-16 15:49:17 -04:00
ngolp
5188cde7ac graceful handling of database integrity violations in registration 2024-10-16 15:44:20 -04:00
warren yun
e89b2ab7b4 modified theme 2024-09-30 22:09:25 -04:00
warren yun
16f08f7750 playing around with styling and items 2024-09-17 20:01:38 -04:00
Cara Salter
d0240d15d8
Merge pull request #33 from wpi-acm/event-date-time-fields
Event Modals & Split date/time fields
2024-06-05 09:21:09 -04:00
Cara Salter
f5eb90b73d
Make unauth handler redirect to login 2024-06-03 18:15:18 -04:00
Cara Salter
a3c89dd478
Support editing event category in modals 2024-06-03 18:12:39 -04:00
Cara Salter
7fc06bde11
Enable editing/creating/deleting events
Wholly through modals, yay!
2024-06-02 12:58:07 -04:00
Cara Salter
e28912b997
Input event modal and create some supporting infra
update form to split date/time as well
2024-06-02 12:30:40 -04:00
12 changed files with 288 additions and 27 deletions

View file

@ -32,7 +32,7 @@ daemon:
@echo "--- STARTING UWSGI DAEMON ---" @echo "--- STARTING UWSGI DAEMON ---"
@echo "" @echo ""
@echo "" @echo ""
source .venv/bin/activate && flask run source .venv/bin/activate && flask --debug run
@echo "" @echo ""
@echo "" @echo ""
@echo "--- STARTING UWSGI DAEMON ---" @echo "--- STARTING UWSGI DAEMON ---"

View file

@ -82,6 +82,10 @@ def create_app():
def index(): def index():
return render_template("home/index.html") return render_template("home/index.html")
@app.route("/index2.html")
def index2():
return render_template("home/index2.html")
# homepage assets # homepage assets
@app.route("/assets/<path:path>") @app.route("/assets/<path:path>")
def assets(path): def assets(path):

View file

@ -1,11 +1,11 @@
import flask import flask
from flask import Response, render_template, redirect, request, url_for, flash from flask import Response, render_template, redirect, request, url_for, flash, current_app
from flask_login import current_user, login_required from flask_login import current_user, login_required
from goathacks.admin import bp, forms from goathacks.admin import bp, forms
from goathacks import db from goathacks import db
from goathacks.models import Event from goathacks.models import Event
import io, qrcode import io, qrcode, datetime
import qrcode.image.pure import qrcode.image.pure
@bp.route("/events") @bp.route("/events")
@ -16,7 +16,86 @@ def list_events():
events = Event.query.all() events = Event.query.all()
return render_template("events/list.html", events=events) form = forms.EventForm()
return render_template("events/list.html", events=events, form=form)
@bp.route("/event/<int:id>/delete")
@login_required
def delete_event(id):
if not current_user.is_admin:
return {"status": "error", "message": "Unauthorized"}
event = Event.query.filter_by(id=id).first()
if event is None:
return {"status": "error", "message": "Invalid event ID"}
db.session.delete(event)
db.session.commit()
return {"status": "success"}
@bp.route("/event/<int:id>")
@login_required
def event(id):
if not current_user.is_admin:
return {"status": "error", "message": "Unauthorized"}
event = Event.query.filter_by(id=id).first()
if event is None:
return {"status": "error", "message": "Invalid event ID"}
return event.create_json()
@bp.route("/event/<int:id>", methods=["POST"])
@login_required
def update_create_event(id):
if not current_user.is_admin:
flash("Unauthorized")
return redirect(url_for("dashboard.home"))
name = request.form.get('name')
description = request.form.get('description')
location = request.form.get('location')
start_day = request.form.get('start_day')
start_time = request.form.get('start_time')
end_day = request.form.get('end_day')
end_time = request.form.get('end_time')
start = datetime.datetime.combine(datetime.date.fromisoformat(start_day),
datetime.time.fromisoformat(start_time))
end = datetime.datetime.combine(datetime.date.fromisoformat(end_day),
datetime.time.fromisoformat(end_time))
category = request.form.get("category")
if id == 0:
# new event
e = Event(
name=name,
description=description,
location=location,
start_time=start,
category=category,
end_time=end)
db.session.add(e)
db.session.commit()
current_app.logger.info(f"{current_user} is creating a new event: {e.name}")
else:
e = Event.query.filter_by(id=id).first()
if e is None:
return {"status": "error", "message": "Invalid event ID"}
e.name = name
e.description = description
e.location = location
e.start_time = start
e.end_time = end
e.category=category
db.session.commit()
current_app.logger.info(f"{current_user} is updating an existing event: {e.name}")
return redirect(url_for("admin.list_events"))
@bp.route("/events/events.json") @bp.route("/events/events.json")
@login_required @login_required

View file

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

View file

@ -21,6 +21,8 @@ class User(db.Model, UserMixin):
phone = Column(String, nullable=True) phone = Column(String, nullable=True)
gender = Column(String, nullable=True) gender = Column(String, nullable=True)
def __str__(self):
return f"{self.first_name} {self.last_name} ({self.email})"
def create_json_output(lis): def create_json_output(lis):
hackers = [] hackers = []
@ -48,7 +50,7 @@ def user_loader(user_id):
@login.unauthorized_handler @login.unauthorized_handler
def unauth(): def unauth():
flash("Please login first") flash("Please login first")
return redirect(url_for("registration.register")) return redirect(url_for("registration.login"))
class PwResetRequest(db.Model): class PwResetRequest(db.Model):
@ -73,18 +75,21 @@ class Event(db.Model):
events = [] events = []
for e in lis: for e in lis:
events.append({ events.append(e.create_json())
'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 return events
def create_json(self):
return {
"id": self.id,
"name": self.name,
"description": self.description,
"location": self.location,
"start_time": self.start_time.isoformat(),
"end_time": self.end_time.isoformat(),
"category": self.category
}
def get_checkins(self): def get_checkins(self):
checkins = EventCheckins.query.filter_by(event_id=self.id).all() checkins = EventCheckins.query.filter_by(event_id=self.id).all()

View file

@ -6,6 +6,7 @@ from goathacks.registration.forms import LoginForm, PwResetForm, RegisterForm, R
from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.security import check_password_hash, generate_password_hash
from flask_mail import Message from flask_mail import Message
import ulid import ulid
from sqlalchemy.exc import IntegrityError
from goathacks import db, mail as app_mail from goathacks import db, mail as app_mail
from goathacks.models import PwResetRequest, User from goathacks.models import PwResetRequest, User
@ -53,8 +54,20 @@ def register():
phone=phone, phone=phone,
gender=gender gender=gender
) )
db.session.add(user)
db.session.commit() #try to add the user to the database, checking for duplicate users
try:
db.session.add(user)
db.session.commit()
except IntegrityError as err:
db.session.rollback()
if "duplicate key value violates unique constraint" in str(err):
flash("User with email " + email + " already exists.")
else:
flash("An unknown error occurred.")
return redirect(url_for("registration.login"))
#user successfully registered, so login
flask_login.login_user(user) flask_login.login_user(user)
if waitlisted: if waitlisted:
@ -122,6 +135,7 @@ def reset():
user_id=user.id, user_id=user.id,
expires=datetime.now() + timedelta(minutes=30) expires=datetime.now() + timedelta(minutes=30)
) )
db.session.add(r) db.session.add(r)
db.session.commit() db.session.commit()

View file

@ -14,5 +14,5 @@
body { body {
min-height: 100vh; min-height: 100vh;
background-color: #003049;
} }

View file

@ -1,5 +1,5 @@
$color-nav-bg: #974355; $color-nav-bg: #974355;
$color-bg: #003049; $color-bg: #000000;
.navbar-dark, .modal-header, .table-header { .navbar-dark, .modal-header, .table-header {
background-color: $color-nav-bg; background-color: $color-nav-bg;

File diff suppressed because one or more lines are too long

View file

@ -19,7 +19,7 @@
<th>Category</th> <th>Category</th>
<th>Checked in</th> <th>Checked in</th>
<th>QR Code</th> <th>QR Code</th>
<th><a href="{{url_for('admin.new_event')}}">New</a></th> <th><a href="#editModal" data-bs-toggle="modal" data-id="0">New</a></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -34,11 +34,166 @@
<td>{{ event.get_checkins()|length }}</td> <td>{{ event.get_checkins()|length }}</td>
<td><a href='{{ url_for("admin.qrcode_event", id=event.id) <td><a href='{{ url_for("admin.qrcode_event", id=event.id)
}}'>QR Code</a></td> }}'>QR Code</a></td>
<td><a href="{{url_for('admin.edit_event', id=event.id)}}">Edit</a></td> <td><a href="#editModal" data-bs-toggle="modal" data-id="{{ event.id}}" >Edit</a></td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<div class="modal" id="editModal" tabindex="-1" aria-labelledby="editModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="editModalLabel">Event</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form class="form" id="edit-form" action="/admin/events/0" role="form" method="post">
<div class="modal-body">
{{ form.csrf_token }}
<div class="form-floating mb-3 required">
{{ form.name(class="form-control") }}
{{ form.name.label() }}
</div>
<div class="form-floating mb-3">
{{ form.description(class="form-control") }}
{{ form.description.label() }}
</div>
<div class="form-floating mb-3 required">
{{ form.location(class="form-control") }}
{{ form.location.label() }}
</div>
<div class="form-floating mb-3">
{{ form.category(class="form-control") }}
{{ form.category.label() }}
</div>
<div class="row">
<div class="col">
<div class="form-floating mb-3 required">
{{ form.start_day(class="form-control") }}
{{ form.start_day.label() }}
</div>
</div>
<div class="col">
<div class="form-floating mb-3 required">
{{ form.start_time(class="form-control") }}
{{ form.start_time.label() }}
</div>
</div>
</div>
<div class="row">
<div class="col">
<div class="form-floating mb-3 required">
{{ form.end_day(class="form-control") }}
{{ form.end_day.label() }}
</div>
</div>
<div class="col">
<div class="form-floating mb-3 required">
{{ form.end_time(class="form-control") }}
{{ form.end_time.label() }}
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-id="0" id="delete">Delete</button>
<button type="submit" class="btn btn-primary" id="edit-save">Save changes</button>
</div>
</form>
</div>
</div>
</div>
<script src="{{ url_for('static', filename='js/jquery-3.6.3.min.js') }}" charset="utf-8"></script>
<script charset="utf-8">
const editButton = document.getElementById("edit-save")
$('#delete').on("click", (event) => {
if (window.confirm("Delete this event?")) {
console.log("Got OK")
deleteButton = document.getElementById("delete")
id = deleteButton.dataset.id
$.get(`/admin/event/${id}/delete`, (data) => {
if (data.status == "error") {
window.alert(`Error: ${data.message}`)
} else {
window.alert("Success")
}
location.reload()
})
}
})
$('#editModal').on('show.bs.modal', function(event) {
var modal = $(this)
modal.find('#name').val('')
modal.find('#location').val('')
modal.find('#description').val('')
modal.find('#start_day').val('')
modal.find('#start_time').val('')
modal.find('#end_day').val('')
modal.find('#end_time').val('')
var button = $(event.relatedTarget)
var name,description,loc,start_time,start_day,end_time,end_day
id = button.data('id')
saveButton = document.getElementById("edit-save")
saveButton.dataset.id = id
deleteButton = document.getElementById("delete")
deleteButton.dataset.id = id
editForm = document.getElementById("edit-form")
editForm.action = "/admin/event/" + id
if (id) {
$.get(`/admin/event/${id}`, (data) => {
if (data.status == "error") {
// This is a new event, do nothing!
} else {
name = data.name,
description = data.description,
loc = data.location,
category = data.category
start = new Date(data.start_time)
var day = ("0" + start.getDate()).slice(-2);
var month = ("0" + (start.getMonth() + 1)).slice(-2);
start_day = start.getFullYear()+"-"+(month)+"-"+(day);
start_time = `${start.getHours()}:${padTwoDigits(start.getMinutes())}`
end = new Date(data.end_time)
var day = ("0" + end.getDate()).slice(-2);
var month = ("0" + (end.getMonth() + 1)).slice(-2);
end_day = end.getFullYear()+"-"+(month)+"-"+(day);
end_time = `${end.getHours()}:${padTwoDigits(end.getMinutes())}`
}
modal.find('#name').val(name)
modal.find('#location').val(loc)
modal.find('#description').val(description)
modal.find('#start_day').val(start_day)
modal.find('#start_time').val(start_time)
modal.find('#end_day').val(end_day)
modal.find('#end_time').val(end_time)
modal.find('#category').val(category)
});
}
})
function padTwoDigits(num) {
return num.toString().padStart(2, '0')
}
</script>
{% endblock %} {% endblock %}

@ -1 +1 @@
Subproject commit 8d8a691aad1cea4037bb9d33ddf7230d8d272597 Subproject commit 5768c790f43b50f52a18356ff83a9259df07beca

View file

@ -9,7 +9,7 @@ Flask-Login==0.6.2
Flask-Migrate==4.0.0 Flask-Migrate==4.0.0
Flask-SQLAlchemy==3.0.2 Flask-SQLAlchemy==3.0.2
Flask-WTF==1.0.1 Flask-WTF==1.0.1
greenlet==2.0.1 greenlet
itsdangerous==2.1.2 itsdangerous==2.1.2
Jinja2==3.1.2 Jinja2==3.1.2
Mako==1.2.4 Mako==1.2.4
@ -19,7 +19,7 @@ psycopg2==2.9.5
pynvim==0.4.3 pynvim==0.4.3
python-dotenv==0.21.0 python-dotenv==0.21.0
SQLAlchemy==1.4.44 SQLAlchemy==1.4.44
uWSGI==2.0.21 uWSGI
Werkzeug==2.2.2 Werkzeug==2.2.2
WTForms==3.0.1 WTForms==3.0.1
ulid ulid