Admin dashboard!
Get event management page almost working! Deleting events works, creating new events works, editing events (almost) works! Just need to figure out how to properly set the date and time fields when editing... Also, user management! You can see a list of users and will theoretically be able to promote officers from the web UI
This commit is contained in:
parent
d6302ea673
commit
5bd7d548c1
10 changed files with 411 additions and 1 deletions
|
@ -48,5 +48,8 @@ def create_app():
|
||||||
from .dashboard import bp as dash_bp
|
from .dashboard import bp as dash_bp
|
||||||
app.register_blueprint(dash_bp)
|
app.register_blueprint(dash_bp)
|
||||||
|
|
||||||
|
from .admin import bp as admin_bp
|
||||||
|
app.register_blueprint(admin_bp)
|
||||||
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
101
acmsite/admin/__init__.py
Normal file
101
acmsite/admin/__init__.py
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||||
|
import ulid
|
||||||
|
import datetime
|
||||||
|
from flask_login import current_user, login_required
|
||||||
|
|
||||||
|
from acmsite.models import User, Event
|
||||||
|
|
||||||
|
from .forms import EventForm
|
||||||
|
from acmsite import db
|
||||||
|
|
||||||
|
|
||||||
|
bp = Blueprint("admin", __name__, url_prefix="/admin")
|
||||||
|
|
||||||
|
@bp.route("/")
|
||||||
|
@login_required
|
||||||
|
def home():
|
||||||
|
if not current_user.is_admin:
|
||||||
|
flash("Unauthorized")
|
||||||
|
return redirect(url_for('dashboard.home'))
|
||||||
|
return render_template("admin/index.html")
|
||||||
|
|
||||||
|
@bp.route("/users")
|
||||||
|
@login_required
|
||||||
|
def users():
|
||||||
|
if not current_user.is_admin:
|
||||||
|
flash("Unauthorized")
|
||||||
|
return redirect(url_for('dashboard.home'))
|
||||||
|
|
||||||
|
user_list = User.query.all()
|
||||||
|
|
||||||
|
return render_template("admin/users.html", u_list=user_list)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route("/events", methods=["GET","POST"])
|
||||||
|
@login_required
|
||||||
|
def events():
|
||||||
|
if not current_user.is_admin:
|
||||||
|
flash("Unauthorized")
|
||||||
|
return redirect(url_for('dashboard.home'))
|
||||||
|
|
||||||
|
event_list = Event.query.all()
|
||||||
|
|
||||||
|
form = EventForm(request.form)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
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')
|
||||||
|
print(start_day)
|
||||||
|
print(start_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))
|
||||||
|
|
||||||
|
e = Event(
|
||||||
|
id=ulid.ulid(),
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
location=location,
|
||||||
|
start_time=start,
|
||||||
|
end_time=end)
|
||||||
|
db.session.add(e)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return render_template("admin/events.html", e_list=event_list, form=form)
|
||||||
|
|
||||||
|
@bp.route("/event/<string: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/<string: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"}
|
12
acmsite/admin/forms.py
Normal file
12
acmsite/admin/forms.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import DateTimeField, DateField, StringField, TextAreaField, TimeField
|
||||||
|
from wtforms.validators import DataRequired
|
||||||
|
|
||||||
|
class EventForm(FlaskForm):
|
||||||
|
name = StringField('Name', validators=[DataRequired()])
|
||||||
|
description = TextAreaField('Description')
|
||||||
|
location = StringField('Location', validators=[DataRequired()])
|
||||||
|
start_day = DateField('Start Day', validators=[DataRequired()])
|
||||||
|
start_time = TimeField('Start Time')
|
||||||
|
end_day = DateField('End Day', validators=[DataRequired()])
|
||||||
|
end_time = TimeField('End Time')
|
|
@ -38,3 +38,13 @@ class Event(db.Model):
|
||||||
location = Column(String, nullable=False)
|
location = Column(String, nullable=False)
|
||||||
start_time=Column(DateTime, nullable=False)
|
start_time=Column(DateTime, nullable=False)
|
||||||
end_time=Column(DateTime, nullable=False)
|
end_time=Column(DateTime, nullable=False)
|
||||||
|
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
|
2
acmsite/static/js/jquery-3.6.3.min.js
vendored
Normal file
2
acmsite/static/js/jquery-3.6.3.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
33
acmsite/templates/admin/admin-layout.html
Normal file
33
acmsite/templates/admin/admin-layout.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{% extends 'layout.html' %}
|
||||||
|
|
||||||
|
{% block navbar %}
|
||||||
|
<nav class="navbar mb-4 navbar-expand-lg">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
|
||||||
|
data-bs-target="#navbarSupportedContent"
|
||||||
|
aria-controls="navbarSupportedContent"
|
||||||
|
aria-expanded="true"
|
||||||
|
aria-label="Toggle
|
||||||
|
navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand" href="{{ url_for('admin.home') }}">
|
||||||
|
<img src="{{url_for('static', filename='img/logo.png')}}"
|
||||||
|
alt="Logo" width="35" height="35" class="d-inline-block
|
||||||
|
align-text-middle mx-2">ACM Admin</a>
|
||||||
|
</div>
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="nav navbar-nav me-auto mb-2 mb-lg-0">
|
||||||
|
{{ render_nav_item('admin.users', 'Member List')}}
|
||||||
|
{{ render_nav_item('admin.events', 'Event List')}}
|
||||||
|
{{ render_nav_item('admin.home', 'Bulk Mail Tool')}}
|
||||||
|
</ul>
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
{{ render_nav_item('dashboard.home', '<- Back To Site') }}
|
||||||
|
{{ render_nav_item('auth.logout', 'Logout') }}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
{% endblock %}
|
214
acmsite/templates/admin/events.html
Normal file
214
acmsite/templates/admin/events.html
Normal file
|
@ -0,0 +1,214 @@
|
||||||
|
{% extends "admin/admin-layout.html" %}
|
||||||
|
{% import "bootstrap5/form.html" as wtf %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>Event list</h1>
|
||||||
|
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Start Time</th>
|
||||||
|
<th>End Time</th>
|
||||||
|
<th><button type="button" class="btn btn-primary" data-bs-toggle="modal"
|
||||||
|
data-bs-target="#editModal">New</button></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for e in e_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ e.name }}</td>
|
||||||
|
<td>{{ e.location }}</td>
|
||||||
|
<td>{{ e.start_time }}</td>
|
||||||
|
<td>{{ e.end_time }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="dropdown">
|
||||||
|
<a href="#" class="btn btn-primary dropdown-toggle"
|
||||||
|
data-bs-toggle="dropdown"><span
|
||||||
|
class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<a href="#editModal"
|
||||||
|
data-bs-toggle="modal" data-id="{{ e.id }}">Edit</a>
|
||||||
|
</li>
|
||||||
|
<li class="dropdown-item">
|
||||||
|
<a href="#deleteModal"
|
||||||
|
data-bs-toggle="modal" data-id="{{ e.id }}">Delete Event</a>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Modals -->
|
||||||
|
<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" 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="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-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="deleteModal" tabindex="-1"
|
||||||
|
aria-labelledby="deleteModalLabel"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="deleteModalLabel">Delete
|
||||||
|
Event?</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||||
|
aria-label="Close">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary"
|
||||||
|
data-bs-dismiss="modal">Cancel</button>
|
||||||
|
<button type="button" id="delete" data-bs-dismiss="modal" class=" btn btn-danger">Delete</button>
|
||||||
|
</div>
|
||||||
|
</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 deleteButton = document.getElementById("delete")
|
||||||
|
|
||||||
|
deleteButton.addEventListener("click", (event) => {
|
||||||
|
button = $(event.relatedTarget)
|
||||||
|
id = deleteButton.dataset.id
|
||||||
|
const deleteRequest = new Request(`/admin/event/${id}/delete`)
|
||||||
|
|
||||||
|
fetch(deleteRequest)
|
||||||
|
.then(async (res) => {
|
||||||
|
window.alert(await res.text())
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#deleteModal').on('show.bs.modal', function(event) {
|
||||||
|
var modal = $(this)
|
||||||
|
var button = $(event.relatedTarget)
|
||||||
|
var id = button.data("id")
|
||||||
|
|
||||||
|
// find delete button
|
||||||
|
|
||||||
|
delButton = document.getElementById("delete")
|
||||||
|
delButton.dataset.id = id
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#editModal').on('show.bs.modal', function(event) {
|
||||||
|
var modal = $(this)
|
||||||
|
|
||||||
|
// Zero all fields
|
||||||
|
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')
|
||||||
|
|
||||||
|
if (id) {
|
||||||
|
$.get(`/admin/event/${id}`, (data) => {
|
||||||
|
console.log(data)
|
||||||
|
if (data.status == "error") {
|
||||||
|
is_new = "yes"
|
||||||
|
} else {
|
||||||
|
name = data.name,
|
||||||
|
description = data.description,
|
||||||
|
loc = data.location
|
||||||
|
|
||||||
|
start = new Date(data.start_time)
|
||||||
|
start_day = start.toLocaleDateString()
|
||||||
|
console.log(start_day)
|
||||||
|
start_time = start.toLocaleTimeString()
|
||||||
|
end = new Date(data.end_time)
|
||||||
|
end_day = end.toLocaleDateString()
|
||||||
|
end_time = end.toLocaleTimeString()
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#deleteModal').on('hidden.bs.modal', function(event) {
|
||||||
|
location.reload()
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#editModal').on('hidden.bs.modal', function(event) {
|
||||||
|
location.reload()
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock app_content %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{{super()}}
|
||||||
|
{% endblock %}
|
7
acmsite/templates/admin/index.html
Normal file
7
acmsite/templates/admin/index.html
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends "admin/admin-layout.html" %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>Welcome to the Officer Dashboard!</h1>
|
||||||
|
<p>Use the navbar up top to navigate to a user dashboard, event dashboard, and
|
||||||
|
bulk mail tool (when/if it works...).</p>
|
||||||
|
{% endblock app_content %}
|
28
acmsite/templates/admin/users.html
Normal file
28
acmsite/templates/admin/users.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends "admin/admin-layout.html" %}
|
||||||
|
|
||||||
|
{% block app_content %}
|
||||||
|
<h1>Member List</h1>
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Last Login</th>
|
||||||
|
<th>Officer?</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for u in u_list %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ u.email }}</td>
|
||||||
|
<td>{{ u.first_name }} {{ u.last_name }}</td>
|
||||||
|
<td>{{ u.created }}</td>
|
||||||
|
<td>{{ u.last_login }}</td>
|
||||||
|
<td>{{ u.is_admin }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
{% endblock app_content %}
|
|
@ -36,7 +36,7 @@
|
||||||
{% if current_user.is_authenticated %}
|
{% if current_user.is_authenticated %}
|
||||||
{{ render_nav_item('dashboard.home', 'Dashboard') }}
|
{{ render_nav_item('dashboard.home', 'Dashboard') }}
|
||||||
{% if current_user.is_admin %}
|
{% if current_user.is_admin %}
|
||||||
{{ render_nav_item('dashboard.home', 'Admin Dash') }}
|
{{ render_nav_item('admin.home', 'Admin Dash') }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ render_nav_item('auth.logout', 'Logout') }}
|
{{ render_nav_item('auth.logout', 'Logout') }}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue