Introduction
Our team’s project is a button designer/sharing website where users can use the button creator interface to make buttons. Users must first signup (add a new account to the database), then they can access all features of the website. The button designs can be customized with width, height, color, and even button animations. The designs can then be saved as 2 types: public (everyone can access) or private (only the user who made them can access).
The search page on the website catalogues all the designs and users may enter keywords and select whether they wish to view all users’ public designs or their own private designs. Once the designs have been fetched, they will be displayed in a table with rows containing all the relevant data for that design.
My portion of the project was the search function. I worked on the HTML and JS for the frontend portion, as well as helping with the CRUD code on the backend. The CRUD code makes use of Object-Oriented Programming, and the backend is deployed on a Flask server using port 8086.
Python Model code and SQLite Database
unique collection/table in database, rows and columns in the table of the SQLite database.
As hinted at in the introduction, the design database contains relevant information in storing all the designs (both public and private). The details include:
- design name
- type (public/private)
- specific button content (the button design HTML)
- the user who made the design
- the numbers of likes and dislikes
- description that users may enter when creating the design
unique code that was created to initialize table and create test data
The following code creates test/default users for the user database.
This includes
- user’s name
- uid
- password, which will be hashed/encrypted in the database
- user type (admin/normal)
The following code initializes the designs SQLite database.
Lists and Dictionaries
List as extracted from database as Python objects, shown in VSCode Debugger
The JS sends a GET request to the backend and collects all public designs into a list design_return, with each design as a python object (shown below).
Dictionaries, show Keys/Values using debugger
In each button object, the dictionary contains keys for “Name”, “Content”, “Description”, and owner id, along with number of likes and dislikes and the type (public/private). The search page makes a GET request and the backend returns all the public designs and sending them off to the frontend to process there based on the search term the user enters. If “private” designs is selected, the page makes a PUT request instead and the backend will return all the private designs.
Python API code, Postman/JSON
In VSCode, show Python API code definition for request and response using GET, POST, UPDATE methods. Discuss algorithmic condition used to direct request to appropriate Python method based on request method.
Above are the GET and UPDATE/PUT methods for the SEARCH CRUD class. Either one is selected based on “if” the user selects the Public or Private designs button on the website page, shown below.
Below is the frontend JS code to handle user requests with if statements:
This is the POST method for the USER CRUD class.
The POST method takes the JSON data from the user and checks of any of the fields/parameters are invalid (<2 characters) using if statements. It then creates the User object for a new user and inserts the data into the SQLite database.
The UPDATE method is embedded within the PUT method for the same class. Requests are handled similarly to the above, and @token_required only allows requests to be made if the user is authenticated and a token is present.
This line of code creates the path to the CRUD methods for search
api.add_resource(_SearchCRUD, ‘/search’)
In VSCode, show algorithmic conditions used to validate data on a POST condition.
Also shown above in the POST method code. Checks if each field is at least 2 characters, and if not it returns a corresponding message and exits the POST method function.
In Postman, show URL request and Body requirements for GET, POST, and UPDATE methods.
POST: (create user)
Requires uid, password, name
GET: (search)
No body is required for the GET in the search.
UPDATE: (aka PUT, for search)
In Postman, show the JSON response data for 200 success conditions on GET, POST, and UPDATE methods.
POST success:
UPDATE/PUT success:
GET success:
In Postman, show the JSON response for error for 400 when missing body on a POST request.
Above, the only data provided was {}, leading to a missing body error.
- In Postman, show the JSON response for error for 404 when providing an unknown user ID to a UPDATE request.
Our user API code automatically retrieves user ID from the JWT token from login by decoding it, meaning that no uid needs to be provided with the UPDATE/PUT request. This ensures that users can only update their own user profile, and also means that there is no possibility for a 404 error response (no uid given with the request means there is no possibility of an unknown uid).
Extra: 401 error when searching for private designs
@token_required takes effect here because the user is not authenticated and thus no jwt token is present, so there is an unauthorized user error.
Frontend
Blog JavaScript API fetch code and formatting code to display JSON.
Below are both search functions (getPublic and getPrivate), which are GET and PUT requests respectively. If the data is returned successfully, then the last function displayDataInTable is called with the argument containing the designs. displayDataInTable then creates the HTML table to display all relevant search results in the page. It loops through all the returned designs and checks if they contain the user’s search term within the name or the content, adding new rows accordingly.
function getPublic() { // splitting up public and private cases
fetch('http://127.0.0.1:8086/api/users/search')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data); // Handle the data returned from the server
displayDataInTable(data.Designs);
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
}
function getPrivate() {
// Making the PUT request (private)
const authOptions = {
mode: 'cors', // no-cors, *cors, same-origin
credentials: 'include', // include, same-origin, omit
headers: {
'Content-Type': 'application/json',
},
method: 'PUT', // Override the method property
};
fetch('http://127.0.0.1:8086/api/users/search', authOptions)
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data); // Handle the data returned from the server
displayDataInTable(data.Designs);
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
}
function displayDataInTable(data) {
var tableContainer = document.getElementById('tableContainer');
var tableHTML = '<table id="table">'; // initialize the table
const searchTerm = getSearchTerm(); // item in search field
tableHTML += '<tr><th>Name</th><th>Content</th><th>Description</th><th>Likes</th><th>Dislikes</th><th>Type</th></tr>';
data.forEach(function(item) {
// Check if searchTerm is a substring of Name or Content
if (searchTerm && (item.Name.includes(searchTerm) || (item.Content && item.Content.includes(searchTerm)))) {
tableHTML += '<tr>';
tableHTML += '<td class="nr">' + item.Name + '</td>';
tableHTML += '<td>' + (item.Content || '') + '</td>';
tableHTML += '<td>' + item.Description + '</td>';
tableHTML += '<td><button onclick="toggleLike(this, this.closest(`tr`).querySelector(`.nr`).textContent)" data-item-id="' + item.id + '">Like</button><span class="likesCount">' + item.Likes + '</span></td>';
tableHTML += '<td><button onclick="toggleDislike(this, this.closest(`tr`).querySelector(`.nr`).textContent)" data-item-id="' + item.id + '">Dislike</button><span class="dislikesCount">' + item.Dislikes + '</span></td>';
tableHTML += '<td>' + item.Type + '</td>';
tableHTML += '</tr>';
}
});
tableHTML += '</table>';
tableContainer.innerHTML = tableHTML;
}
In Chrome inspect, show response of JSON objects from fetch of GET, POST, and UPDATE methods.
GET:
When the page makes a GET request to the backend, it returns a list containing all the public designs and logs it in the console.
POST and UPDATE/PUT (for users) do not return JSON objects. They redirect the browser window to the homepage of the site if successful.
In the Chrome browser, show a demo (GET) of obtaining an Array of JSON objects that are formatted into the browsers screen.
This GET method obtains the list of public designs when the user presses the search button. It returns the array of JSON objects and formats it into the HTML table.
In JavaScript code, describe fetch and method that obtained the Array of JSON objects.
fetch('http://127.0.0.1:8086/api/users/search')
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log(data); // Handle the data returned from the server
displayDataInTable(data.Designs);
})
.catch(error => {
console.error('There was a problem with the fetch operation:', error);
});
The code fetches to the url on the first line (http://127.0.0.1:8086/api/users/search), with a GET method (which is default and not specified within the code). The code handles the backend return by verifying the response first and then displays the code in the table (displayDataInTable is called). The code to display the data in the table is below.
In JavaScript code, show code that performs iteration and formatting of data into HTML.
function displayDataInTable(data) {
var tableContainer = document.getElementById('tableContainer');
var tableHTML = '<table id="table">'; // initialize the table
const searchTerm = getSearchTerm(); // item in search field
tableHTML += '<tr><th>Name</th><th>Content</th><th>Description</th><th>Likes</th><th>Dislikes</th><th>Type</th></tr>';
data.forEach(function(item) {
// Check if searchTerm is a substring of Name or Content
if (searchTerm && (item.Name.includes(searchTerm) || (item.Content && item.Content.includes(searchTerm)))) {
tableHTML += '<tr>';
tableHTML += '<td class="nr">' + item.Name + '</td>';
tableHTML += '<td>' + (item.Content || '') + '</td>';
tableHTML += '<td>' + item.Description + '</td>';
tableHTML += '<td><button onclick="toggleLike(this, this.closest(`tr`).querySelector(`.nr`).textContent)" data-item-id="' + item.id + '">Like</button><span class="likesCount">' + item.Likes + '</span></td>';
tableHTML += '<td><button onclick="toggleDislike(this, this.closest(`tr`).querySelector(`.nr`).textContent)" data-item-id="' + item.id + '">Dislike</button><span class="dislikesCount">' + item.Dislikes + '</span></td>';
tableHTML += '<td>' + item.Type + '</td>';
tableHTML += '</tr>';
} // build new table row with cells containing the details of the design
});
tableHTML += '</table>';
tableContainer.innerHTML = tableHTML;
}
The code gets the container id of the table and initializes the HTML string with a header row. Then it iterates through each element of the designs data, and checks if the content or the name contains the search term from the user. If the design is desired (what the user searched for), then it appends a new row containing that design to the growing HTML string for the table.
In the Chrome browser, show a demo (POST or UPDATE) gathering and sending input and receiving a response that show update. Repeat this demo showing both success and failure.
UPDATE/PUT request success:
This is a screenshot from a user pressing the save button, and the button design saves successfully.
POST request failure:
This is a user trying to create a new design without entering a design name.
In JavaScript code, show and describe code that handles failure. Describe how the code shows failure to the user in the Chrome Browser screen.
function closePopup() {
const name1 = document.getElementById("nameInput").value;
const publicInput1 = document.getElementById("publicInput").value;
const description = document.getElementById("description").value;
const body = {
name: name1,
content: "",
type: publicInput1,
description: description
}
console.log(name1)
console.log(description)
if (name1.trim() !== "") {
const authOptions = {
mode: 'cors', // no-cors, *cors, same-origin
credentials: 'include', // include, same-origin, omit
headers: {
'Content-Type': 'application/json',
},
method: 'POST', // Override the method property
cache: 'no-cache', // Set the cache property
body: JSON.stringify(body)
};
console.log(body)
fetch(url, authOptions)
.then(response => {
// handle error response from Web API
if (!response.ok) {
window.location.href = "http://127.0.0.1:4100/CPT/login";
}
document.getElementById("popup").style.display = "none";
})
// catch fetch errors (ie ACCESS to server blocked)
.catch(err => {
console.log(body);
console.error(err);
});
} else {
alert("Please enter a design name!");
}
}
If the request is successfully made but the response is not ok, it redirects the user to the login page (window.location.href). Otherwise, if the user has not inputted a design name, an alert pops up and the design is not created (no fetch is made).
In JavaScript code, show and describe code that handles success. Describe how code shows success to the user in the Chrome Browser screen.
function saveButton(name) {
const url ='http://127.0.0.1:8086/api/users/design';
const body = {
name: name,
content: buttonCode.value,
type: "",
description: description
}
const authOptions = {
mode: 'cors', // no-cors, *cors, same-origin
credentials: 'include', // include, same-origin, omit
headers: {
'Content-Type': 'application/json',
},
method: 'PUT', // Override the method property
cache: 'no-cache', // Set the cache property
body: JSON.stringify(body)
};
fetch(url, authOptions)
.then(response => {
// handle error response from Web API
if (!response.ok) {
const errorMsg = 'Save error: ' + response.status;
console.log(errorMsg);
return;
} else {
alert("Design Saved")
}
})
// catch fetch errors (ie ACCESS to server blocked)
.catch(err => {
console.log(body);
console.error(err);
});
}
The code checks if save (the PUT/UPDATE request to the url) is successful, and alerts the user with a popup based on the result. “Design Saved” if it is ok, but if !response.ok, then it logs a save error in the console.
/model/concussion.py - ML Linear Regression
This is my personal model for the concussions recovery time predict machine learning model. I used linear regression on generated data in a CSV file to predict the “healtime” variable, which is then displayed on the page
# import all necessary packages for machine learning
# numpy to handle the returned result and convert into usable list format
import numpy as np
# need to be able to split up data into training and testing sets
from sklearn.model_selection import train_test_split
# training with linear regression model as data is continuous, not discrete
from sklearn.linear_model import LinearRegression
# python pandas package
# need pandas for dataframe and easy data manipulation
import pandas as pd
# Define the concussion_regression global variable
concussion_regression = None
# define the ConcussionRegression class
# we are using OOP here
class ConcussionRegression:
# below are the separate functions to handle various events in the prediction
# initConcussion will take the generated sample data from the CSV file
# then it will clean data by removing all negative values
def initConcussion(self):
# load in the csv data
concussion_data = pd.read_csv('concussion_recovery_data.csv')
'''
Categories in the CSV file: sex,age,ht,wt,smoke,alcohol,sleephrs,exercisehrs,hitbox,healtime
sex is boolean, 1=male 0=female
ht = height (cm)
wt = weight (kg)
smoke/alcohol are boolean, 1=yes 0=no
sleephrs are per night
exercisehrs are per week
hitbox = size of object hit, in kg
healtime is in days
'''
global cd
# now variable cd has all the data
cd = concussion_data
categories = ['age', 'ht', 'wt', 'sleephrs', 'exercisehrs', 'hitbox', 'healtime']
# clean up the data
# for non-boolean categories, drop all negative values
for cat in categories:
cd.drop(cd[cd[cat] < 0].index, inplace=True)
# runLinearRegression trains the model according to linear regression
# the model will be trained to predict healtime based on the other categories
def runLinearRegression(self):
# making a linear regression model
X = cd.drop('healtime', axis=1) # all categories except healtime (result)
y = cd['healtime'] # healtime (the result)
# split up data into training and testing sets
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
global lr
# train the model
lr = LinearRegression()
lr.fit(X_train, y_train)
def predict(data):
new_case = data.copy() # copy in the data from frontend
'''
format of the input:
new_case = {
'name': case.get('name'),
'sex': case.get('sex'),
'ht': case.get('ht'),
'wt': case.get('wt'),
'smoke': case.get('smoke'),
'alcohol': case.get('alcohol'),
'sleephrs': case.get('sleephrs'),
'exercisehrs': case.get('exercisehrs'),
'hitbox': case.get('hitbox'),
}
'''
# convert frontend data to usable format
# sex converted to boolean and name doesn't matter
new_case['sex'] = new_case['sex'].apply(lambda x: 1 if x == 'male' else 0)
new_case.drop(['name'], axis=1, inplace=True)
# predict time to heal
# need to convert to list since numpy arrays are not directly serializable to JSON
healtime = np.squeeze(lr.predict(new_case)).tolist() # use numpy and .tolist()
# print(healtime)
return healtime
def initConcussion():
# initiate the concussion regression model
# then run linear regression
global concussion_regression
concussion_regression = ConcussionRegression()
concussion_regression.initConcussion()
concussion_regression.runLinearRegression()
# test the model
if __name__ == "__main__":
# initialize concussion model
initConcussion()
# test data on partner
patient_data = pd.DataFrame({
'name': ['Andrew Kim'],
'sex': ['male'],
'age': [16],
'ht': [180],
'wt': [73],
'smoke': [0],
'alcohol': [1],
'sleephrs': [8.5],
'exercisehrs': [5.5],
'hitbox': [0.28],
})
print(predict(patient_data))
10.526551391730155