Events Page Guide for Review
By Drew Reed, Period 5 APCSP with Mr. Yeung. See the bottom of the Table of Contents for the raw code.
Full Site Link
Please navigate the site linked here to see these achievements first-hand.
Frontend UI
The program contains validations for user inputs in both the frontend and the backend.
Frontend Validation
The validation in the frontend runs based on these conditions:
Description | Image |
---|---|
All fields must be filled in. | ![]() |
Name, email, event name and event details must all be less than 100 characters to prevent spam. | ![]() |
The date inputted must be in the future, but cannot be more than a year in the future. | ![]() |
The start and end time of the event must be during the open hours (listed on the "Hours" page). | ![]() |
The event cannot last longer than 3 hours so that the events room isn't too booked all the time. They must be at least 15 minutes long. | ![]() |
Name, email, event name, and password must not be duplicates. | ![]() |
Additionally, no more than five events can coincide within a time period at a time. There aren't that many events in the database, so producing an example alert would be difficult and essentially necessitate spam.
Create, Read Options
The create (POST) option exists in the form of the input boxes that let you add an event.

The read (GET) option is the table below it

BONUS: Delete Option
Above the table, a set of input boxes to delete an event allow you to do so if you input the event name and password.

Backend Database
This section focuses on the backend code. You can use the table of contents at the top to skip to raw backend code.
Code Quality and Comments
In order to really see this, skip to the "Raw Code -> Backend Python Code". I put at least a few comments on every function and class.
Supporting Create and Read
Below is the model code code.
def create(self):
try:
# creates a person object from User(db.Model) class, passes initializers
db.session.add(self) # add prepares to persist person object to Users table
db.session.commit() # SqlAlchemy "unit of work pattern" requires a manual commit
return self
except IntegrityError:
db.session.remove()
return None
And below this is the read code.
def read(self):
return {
"id": self.id,
"name": self.name,
"email": self.email,
"event_name": self.event_name,
"event_details": self.event_details,
"date": self.date,
"start_time": self.start_time,
"end_time": self.end_time,
"password": self.password
}
class _Create(Resource):
def post(self):
''' Read data for json body '''
body = request.get_json()
''' Avoid garbage in, error checking '''
# validate name
name = body.get('name')
if name is None or len(name) < 2:
return {'message': f'Submitter\'s name is missing, or is less than 2 characters'}, 210
# validate uid
email = body.get('email')
if email is None or len(email) < 2:
return {'message': f'Email is missing, or is less than 2 characters'}, 210
# look for the rest of the data
event_name = body.get('event_name')
event_details = body.get('event_details')
date = body.get('date')
if (date is None) or (len(date) != 10) or (int(date[6:10]) < 2023) or (int(date[6:10]) > 2024):
return {'message': f'Date is missing, formatted incorrectly, or within an invalid time range.'}, 210
start_time = body.get('start_time')
end_time = body.get('end_time')
password = body.get('password')
''' #1: Key code block, setup USER OBJECT '''
eo = Event(name=name,
email=email,
event_name=event_name,
event_details=event_details,
date=date,
start_time=start_time,
end_time=end_time,
password=password)
''' #2: Key Code block to add user to database '''
# create user in database
event = eo.create()
# success returns json of user
if event:
return jsonify(event.read())
# failure returns error
return {'message': f'Processed {event_name}, either a format error or the event "{event_name}" is a duplicate'}, 210
And below this is the GET.
class _Read(Resource):
def get(self):
events = Event.query.all() # read/extract all users from database
json_ready = [event.read() for event in events] # prepare output in json
return jsonify(json_ready) # jsonify creates Flask response object, more specific to APIs than json.dumps
class _Delete(Resource):
def delete(self):
body = request.get_json() #getting the database data for a given event
event_id = body.get('id') #isolating the ID of the given entry
status = deleteID(event_id) #uses the deleteID function from the model
if status:
return {'message': f'Successfully deleted event with id {event_id} '}
else:
return {'message': f'Event with id {event_id} not found'}, 240
let sorted = false;
var pulldata = "";
const read_url = "https://cgato.duckdns.org/api/events";
const read_options = {
method: 'GET', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'default', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'omit', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded',
},
};
const post_url = "https://cgato.duckdns.org/api/events/create";
const del_url = "https://cgato.duckdns.org/api/events/delete";
const table = document.getElementById("evtablecont");
function showEvTable() {
create_Table();
document.getElementById('evlogbtn').style = "display:none";
document.getElementById('logrefbtn').style = "display:block";
document.getElementById('delControls').style = "display:block";
document.getElementById('event_name_del').style = "display:block";
document.getElementById('password_del').style = "display:block";
document.getElementById('deletebtn').style = "display:block";
document.getElementById('evtable').style = "display:block";
document.getElementById('filters').style = "font-size:25px;display:block";
}
function time_Dif(start, end) {
var hourdif = 60 * (Number(end.substring(0, 2)) - Number(start.substring(0, 2)));
var mindif = Number(end.substring(3, 5)) - Number(start.substring(3, 5));
return hourdif + mindif
}
// THIS IS A PLACEHOLDER FUNCTION FOR WHEN THE API IS RUNNING
function submit_Form() {
try {
fetch(read_url, read_options)
// response is a RESTful "promise" on any successful fetch
.then(response => {
// check for response errors
if (response.status !== 200) {
const errorMsg = 'Database response error: ' + response.status;
console.log(errorMsg);
};
// valid response will have json data
response.json().then(data => {
var form_list = [document.getElementById('name').value, document.getElementById('email').value, document.getElementById('event_name').value, document.getElementById('event_details').value, document.getElementById('date').value, document.getElementById('start_time').value, document.getElementById('end_time').value, document.getElementById('password').value];
// for loop to ensure all fields were filled in
for (let i = 0; i < form_list.length; i++) {
if (form_list[i] == '') {
alert("There was an error processing your form. Make sure all fields are filled in.");
return;
};
};
for (let i = 0; i < 4; i++) {
if (form_list[i].length > 100) {
alert("There was an error processing your form. Certain input fields have too many characters. Make sure that your name, email, event name, and details are all no more than 100 characters long. (This is a measure to prevent spam.)")
return;
};
};
// Defining some variables for validation
var tempdate = document.getElementById('date').value;
var tempstime = document.getElementById('start_time').value;
var tempetime = document.getElementById('end_time').value;
var datefix = tempdate.substr(5, 2) + '/' + tempdate.substr(8, 10) + '/' + tempdate.substr(0, 4);
const hourdict = [{"open":10, "close":18}, {"open":8, "close":17}, {"open":8, "close":17}, {"open":8, "close":17}, {"open":8, "close":17}, {"open":8, "close":17}, {"open":10, "close":18}];
form_list[4] = datefix;
var fulldate = datefix + " " + tempstime;
let ev_date = new Date(fulldate);
let cur_date = new Date();
console.log(ev_date, cur_date);
let ev_dow = ev_date.getDay()
// validating date
var datedif = Math.ceil((ev_date - cur_date) / (1000 * 60 * 60 * 24));
if (1 > datedif || 365 < datedif) {
alert("There was an error processing your form. Make sure the date you have inputted is less than a year in the future.");
return;
};
// validating day of the week considering open hours
if (Number(tempstime.substring(0, 2)) < hourdict[ev_dow]["open"] || Number(tempstime.substring(0, 2)) >= hourdict[ev_dow]["close"]) {
alert("There was an error processing your form. It seems that your event starts before opening/after closing on " + datefix + ".");
return;
} else if (Number(tempetime.substring(0, 2)) <= hourdict[ev_dow]["open"] || Number(tempetime.substring(0, 2)) > hourdict[ev_dow]["close"]) {
alert("There was an error processing your form. It seems that your event ends before opening/after closing on " + datefix + ".");
return;
};
// validating event duration (must be at least 15 minutes, less than 3 hours, start must be before end)
var timedif = time_Dif(tempstime, tempetime); //in minutes
if (timedif < 15 || timedif > 180) {
alert("There was an error processing your form. Make sure that your event lasts at least 15 minutes, but no more than 3 hours.")
return;
};
// validating coincidence and email
var coinc = 0;
for (let i = 0; i < data.length; i++) {
temppull = data[i];
if (temppull['date'] == datefix) {
if (Number(tempstime.substring(0, 2)) <= Number(temppull['start_time'].substr(0, 2)) < Number(tempetime.substring(0, 2)) || Number(tempstime.substring(0, 2)) < Number(temppull['end_time'].substr(0, 2)) <= Number(tempetime.substring(0, 2))) {coinc = coinc + 1;};
};
if (temppull['email'] == form_list[1]) {
alert("There was an error processing your form. It seems that an event has already been created by that email. If someone has used your address to create an event without your consent, contact our staff.");
return;
};
};
if (coinc > 5) {
alert("There was an error processing your form. Make sure that your event's timing does not coincide with the timing of more than five other events.");
return;
};
// if all validations successful
const body = {
"name": document.getElementById('name').value,
"email": document.getElementById('email').value,
"event_name": document.getElementById('event_name').value,
"event_details": document.getElementById('event_details').value,
"date": datefix,
"start_time": document.getElementById('start_time').value,
"end_time": document.getElementById('end_time').value,
"password": document.getElementById('password').value
};
const post_options = {
method: 'POST',
body: JSON.stringify(body),
headers: {
'Content-Type':'application/json',
'Authorization': 'Bearer my-token',
},
};
console.log(body);
fetch(post_url, post_options)
.then(response =>
response.json().then(data => {
console.log(data);
})
)
alert("Thank you, " + form_list[0] + ", for submitting an event! Watch your email for a confirmation message.\n\n(Warning: Please do not submit two events at a time! Your events may end up being cancelled as a result.)");
});
});
} catch (err) {
alert("There was an error processing your form. (Failed to send to/pull from the database, or there was an error in the formatting of your form. Make sure you're on unrestricted WiFi.)");
};
};
// prepare HTML result container for new output
function create_Table() {
// fetch the API
fetch(read_url, read_options)
// response is a RESTful "promise" on any successful fetch
.then(response => {
// check for response errors
if (response.status !== 200) {
const errorMsg = 'Database response error: ' + response.status;
console.log(errorMsg);
}
// valid response will have json data
response.json().then(data => {table_Make(data)})
});
};
function table_Make(list) {
table.innerHTML = "";
list.forEach(user => {
// build a row for each user
const tr = document.createElement("tr");
// td's to build out each column of data
const name = document.createElement("td");
const email = document.createElement("td");
const event_name = document.createElement("td");
const event_details = document.createElement("td");
const date = document.createElement("td");
const start_time = document.createElement("td");
const end_time = document.createElement("td");
// filter times
var temp_stime = user.start_time;
var temp_etime = user.end_time;
if (Number(temp_stime.substring(0, 2)) > 12) {
var temp_shr = Number(temp_stime.substring(0, 2)) - 12;
var new_stime = String(temp_shr) + temp_stime.substring(2, 5) + " PM";
} else {
var new_stime = temp_stime + " AM"
}
if (Number(temp_etime.substring(0, 2)) > 12) {
var temp_ehr = Number(temp_etime.substring(0, 2)) - 12;
var new_etime = String(temp_ehr) + temp_etime.substring(2, 5) + " PM";
} else {
var new_etime = temp_etime + " AM"
}
// add content from user data
name.innerHTML = user.name;
email.innerHTML = user.email;
event_name.innerHTML = user.event_name;
event_details.innerHTML = user.event_details;
date.innerHTML = user.date;
start_time.innerHTML = new_stime;
end_time.innerHTML = new_etime;
// add data to row
tr.appendChild(name);
tr.appendChild(email);
tr.appendChild(event_name);
tr.appendChild(event_details);
tr.appendChild(date);
tr.appendChild(start_time);
tr.appendChild(end_time);
// add row to table
table.appendChild(tr);
});
};
var soonval = "placeholder";
var soon_fulldate = "placeholder";
var temp_soondate = "placeholder";
var lateval = "placeholder";
var late_fulldate = "placeholder";
var temp_latedate = "placeholder";
function sort_Events() {
var orderval = document.getElementById("timesort").value;
var monthval = document.getElementById("monthfil").value;
var sorted_List = [];
// fetch the API
fetch(read_url, read_options)
// response is a RESTful "promise" on any successful fetch
.then(response => {
// check for response errors
if (response.status !== 200) {
const errorMsg = 'Database response error: ' + response.status;
console.log(errorMsg);
};
// valid response will have json data
response.json().then(data => {
var testcopy = [...data];
var d_length = testcopy.length;
if (orderval == "time_submitted") {
testcopy.forEach(event => {sorted_List.push(event)});
} else if (orderval == "soonest") {
for (let j = 0; j < d_length; j++) {
let i = 0;
testcopy.forEach(event => {
if (i == 0) {
soon_fulldate = event['date'] + " " + event['start_time'];
temp_soondate = new Date(soon_fulldate);
soonval = event;
} else {
var temp_fulldate = event['date'] + " " + event['start_time'];
var temp_evdate = new Date(temp_fulldate);
if (temp_evdate.getTime() < temp_soondate.getTime()) {
soon_fulldate = event.date + " " + event.start_time;
temp_soondate = new Date(soon_fulldate);
soonval = event;
};
};
i = i + 1;
});
sorted_List.push(soonval);
for (let i = 0; i < testcopy.length; i++) {
if (testcopy[i] == soonval) {
testcopy.splice(i, 1);
};
};
};
} else if (orderval == "latest") {
for (let j = 0; j < d_length; j++) {
let i = 0;
testcopy.forEach(event => {
if (i == 0) {
late_fulldate = event['date'] + " " + event['start_time'];
temp_latedate = new Date(late_fulldate);
lateval = event;
} else {
var temp_fulldate = event['date'] + " " + event['start_time'];
var temp_evdate = new Date(temp_fulldate);
if (temp_evdate.getTime() > temp_latedate.getTime()) {
late_fulldate = event.date + " " + event.start_time;
temp_latedate = new Date(late_fulldate);
lateval = event;
};
};
i = i + 1;
});
sorted_List.push(lateval);
for (let i = 0; i < testcopy.length; i++) {
if (testcopy[i] == lateval) {
testcopy.splice(i, 1);
};
};
};
};
var final_List = [];
for (let k = 0; k < sorted_List.length; k++) {
if (sorted_List[k]['date'].substring(6, 10) == monthval.substring(0, 4)) {
if (sorted_List[k]['date'].substring(0, 2) == monthval.substring(5, 7)) {final_List.push(sorted_List[k])} else {};
} else {};
};
table_Make(final_List);
});
});
};
function delete_Event() {
var del_ename = document.getElementById("event_name_del").value;
var del_password = document.getElementById("password_del").value;
var success = false;
fetch(read_url, read_options)
// response is a RESTful "promise" on any successful fetch
.then(response => {
// check for response errors
if (response.status !== 200) {
const errorMsg = 'Database response error: ' + response.status;
console.log(errorMsg);
};
// valid response will have json data
response.json().then(data => {
data.forEach(event => {
if (event['event_name'] == del_ename && event['password'] == del_password) {
// if all validations successful
const del_ID = event['id'];
const body = {
'id':del_ID
};
const del_options = {
method: 'DELETE',
body: JSON.stringify(body),
headers: {
'Content-Type':'application/json',
'Authorization': 'Bearer my-token',
},
};
console.log(body);
fetch(del_url, del_options)
.then(response =>
response.json().then(data => {
console.log(data);
})
)
alert('You have successfully deleted the event "' + event['event_name'] + '" from the events database.');
success = true;
}
})
if (success == false) {alert("There was an error in one of the two fields you have filled in. Make sure that your event name and password both match the case used when first created. (You can copy-paste the event name from the data below.)")}
})
})
}
""" database dependencies to support sqliteDB examples """
from random import randrange
from datetime import date
import os, base64
import json
from __init__ import app, db
from sqlalchemy.exc import IntegrityError
''' Tutorial: https://www.sqlalchemy.org/library.html#tutorials, try to get into Python shell and follow along '''
# Define the User class to manage actions in the 'users' table
# -- Object Relational Mapping (ORM) is the key concept of SQLAlchemy
# -- a.) db.Model is like an inner layer of the onion in ORM
# -- b.) User represents data we want to store, something that is built on db.Model
# -- c.) SQLAlchemy ORM is layer on top of SQLAlchemy Core, then SQLAlchemy engine, SQL
class Event(db.Model):
__tablename__ = 'events' # table name is plural, class name is singular
# Define the User schema with "vars" from object
id = db.Column(db.Integer, primary_key=True)
_name = db.Column(db.String(255), unique=False, nullable=False)
#_uid = db.Column(db.String(255), unique=True, nullable=False)
_email = db.Column(db.String(255), unique=False, nullable=False)
_event_name = db.Column(db.String(255), unique=False, nullable=False)
_event_details = db.Column(db.String(255), unique=False, nullable=False)
_date = db.Column(db.String(255), unique=False, nullable=False)
_start_time = db.Column(db.String(255), unique=False, nullable=False)
_end_time = db.Column(db.String(255), unique=False, nullable=False)
_password = db.Column(db.String(255), unique=False, nullable=False)
# constructor of a User object, initializes the instance variables within object (self)
def __init__(self, name, email, event_name, event_details, date, start_time, end_time, password):
self._name = name
self._email = email
self._event_name = event_name
self._event_details = event_details
self._date = date
self._start_time = start_time
self._end_time = end_time
self._password = password
#here's the name getter
@property
def name(self):
return self._name
#here's the name setter
@name.setter
def name(self, name):
self._name = name
#here's the email getter
@property
def email(self):
return self._email
#here's the email setter
@email.setter
def email(self, email):
self._email = email
#here's the event_name getter
@property
def event_name(self):
return self._event_name
#here's the event_name setter
@event_name.setter
def event_name(self, event_name):
self._event_name = event_name
#here's the event_details getter
@property
def event_details(self):
return self._event_details
#here's the event_details setter
@event_details.setter
def event_details(self, event_details):
self._event_details = event_details
#here's the date getter
@property
def date(self):
return self._date
#here's the date setter
@date.setter
def date(self, date):
self._date = date
#here's the start_time getter
@property
def start_time(self):
return self._start_time
#here's the start_time setter
@start_time.setter
def start_time(self, start_time):
self._start_time = start_time
#here's the end_time getter
@property
def end_time(self):
return self._end_time
#here's the end_time setter
@end_time.setter
def end_time(self, end_time):
self._end_time = end_time
#here's the password getter
@property
def password(self):
return self._password
#here's the password setter
@password.setter
def password(self, password):
self._password = password
# output content using str(object) in human readable form, uses getter
# output content using json dumps, this is ready for API response
def __str__(self):
return json.dumps(self.read())
# CRUD create/add a new record to the table
# returns self or None on error
def create(self):
try:
# creates a person object from User(db.Model) class, passes initializers
db.session.add(self) # add prepares to persist person object to Users table
db.session.commit() # SqlAlchemy "unit of work pattern" requires a manual commit
return self
except IntegrityError:
db.session.remove()
return None
# CRUD read converts self to dictionary
# returns dictionary
def read(self):
return {
"id": self.id,
"name": self.name,
"email": self.email,
"event_name": self.event_name,
"event_details": self.event_details,
"date": self.date,
"start_time": self.start_time,
"end_time": self.end_time,
"password": self.password
}
# CRUD update: updates user name, password, phone
# returns self
def update(self, name="", email="", event_name="", event_details="", date="", start_time="", end_time="", password=""):
"""only updates values with length"""
if len(name) > 0:
self.name = name
if len(email) > 0:
self.email = email
if len(event_name) > 0:
self.event_name = event_name
if len(event_details) > 0:
self.event_details = event_details
if len(date) > 0:
self.date = date
if len(start_time) > 0:
self.start_time = start_time
if len(end_time) > 0:
self.end_time = end_time
if len(password) > 0:
self.password = password
db.session.commit()
return self
# CRUD delete: remove self
# None
def delete(self):
db.session.delete(self)
db.session.commit()
return None
"""Database Creation and Testing """
# Builds working data for testing
def initEvents():
with app.app_context():
"""Create database and tables"""
db.create_all()
"""Tester data for table
e1 = Event(name="Thomas Edison", email="tedison@lightbulb.edu",
event_name="The Edison Troupe Meet",
event_details="We 10 selected geniuses will meet in the events room for a convergence.",
date="02/23/2023", start_time="13:00", end_time="14:00", password="tedisonrules20")
e2 = Event(name="John Mortensen", email="jmortensen@powayusd.com",
event_name="Extra Credit Code Meetup",
event_details="Come to work on ideation and any confusion with the Full Stack CPT project. No phones.",
date="02/25/2023", start_time="10:00", end_time="12:00", password="codec0decod3bro")
e3 = Event(name="Karl Giant", email="giantrichguy@wallstreet.org",
event_name="Karl and Cats",
event_details="Karl would like to see cats with friends (if he can fit in the building).",
date="02/26/2023", start_time="16:00", end_time="17:00", password="i_am-the-f4th3r")
events = [e1, e2, e3]
#Builds sample events data
for event in events:
try:
event.create()
except IntegrityError:
'''fails with bad or duplicate data'''
db.session.remove()
print(f"Records exist, duplicate data, or error: {event.event_name}")
"""
def deleteID(event_id):
event = Event.query.get(event_id) #getting an event from the database based on ID
#user = Wordle.query.filter_by(name=name).first()
if event != None:
print("Query 1:", event) #prints the one query if it finds one
db.session.delete(event) #deletes the event with the db.session innate function
db.session.commit() #commits the change
return True
else:
print("event "+str(event_id)+" not found") #if the event isn't found, the display is provided
return False
from flask import Flask, Blueprint, request, jsonify
from flask_restful import Api, Resource # used for REST API building
from datetime import *
from flask_cors import CORS
from model.events import Event, deleteID
event_api = Blueprint('event_api', __name__,
url_prefix='/api/events')
# API docs https://flask-restful.readthedocs.io/en/latest/api.html
api = Api(event_api)
class EventAPI:
class _Create(Resource):
def post(self):
''' Read data for json body '''
body = request.get_json()
''' Avoid garbage in, error checking '''
# validate name
name = body.get('name')
if name is None or len(name) < 2:
return {'message': f'Submitter\'s name is missing, or is less than 2 characters'}, 210
# validate uid
email = body.get('email')
if email is None or len(email) < 2:
return {'message': f'Email is missing, or is less than 2 characters'}, 210
# look for the rest of the data
event_name = body.get('event_name')
event_details = body.get('event_details')
date = body.get('date')
if (date is None) or (len(date) != 10) or (int(date[6:10]) < 2023) or (int(date[6:10]) > 2024):
return {'message': f'Date is missing, formatted incorrectly, or within an invalid time range.'}, 210
start_time = body.get('start_time')
end_time = body.get('end_time')
password = body.get('password')
''' #1: Key code block, setup USER OBJECT '''
eo = Event(name=name,
email=email,
event_name=event_name,
event_details=event_details,
date=date,
start_time=start_time,
end_time=end_time,
password=password)
''' #2: Key Code block to add user to database '''
# create user in database
event = eo.create()
# success returns json of user
if event:
return jsonify(event.read())
# failure returns error
return {'message': f'Processed {event_name}, either a format error or the event "{event_name}" is a duplicate'}, 210
class _Read(Resource):
def get(self):
events = Event.query.all() # read/extract all users from database
json_ready = [event.read() for event in events] # prepare output in json
return jsonify(json_ready) # jsonify creates Flask response object, more specific to APIs than json.dumps
class _Delete(Resource):
def delete(self):
body = request.get_json() #getting the database data for a given event
event_id = body.get('id') #isolating the ID of the given entry
status = deleteID(event_id) #uses the deleteID function from the model
if status:
return {'message': f'Successfully deleted event with id {event_id} '}
else:
return {'message': f'Event with id {event_id} not found'}, 240
# building RESTapi endpoint
api.add_resource(_Create, '/create')
api.add_resource(_Read, '/')
api.add_resource(_Delete, '/delete')