Updates for GoatHacks 2025 #34

Merged
Muirrum merged 18 commits from 2025 into master 2024-10-31 13:37:25 -04:00
7 changed files with 264 additions and 21 deletions
Showing only changes of commit d0240d15d8 - Show all commits

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,17 +75,20 @@ 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()

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 e0fb69c0be54c6cce7391ccb86a9bf14b80f432b

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