Introduction

When building an application that requires users to create accounts or sign in, handling data related to users is crucial. This data can include things like user profiles, preferences, and activity logs, which can be used to personalize the user experience and improve the application's performance.

By learning how to handle data related to users effectively and efficiently, you'll be equipped with the skills and knowledge needed to build helpful applications that meet the needs of your users.

PLEASE DON'T OPEN THE POPCORN HACKS, WE WILL OPEN THEM DURING THE PRESENTATION

Basics of Class and User Data

Establishing Class/User Data and making a new user

Because Python is an object-oriented programming (OOP) language, everything in it represents an object. Lists, dictionaries, functions, etc. are all examples of Python objects. Generally speaking, a Python object is an entity of data items and methods describing the behavior of those items.

A Python class is a template used to create Python objects. We can think of it as a cooking recipe, where all the ingredients and their quantities are listed, and the whole process of cooking is described step-by-step.

In this case, we can compare a cake recipe to a class, and a cake cooked following that recipe to an object (i.e., an instance of that class). Using the same recipe (class), we can create many cakes (objects). This is the essence of creating a class in Python: to define data elements and the rules establishing how these elements can interact and change their state — and then use this framework to build various objects in a set way, instead of creating them from scratch every time.

Let's look at a few examples:

class Bikes:
    name = ""
    gear = 0

# create objects of class
bike1 = Bikes() # objectName = ClassName()
bike2 = Bikes()

Now that we have our class and object defined, we can now run code to modify certain attributes of our objects.

bike1.gear = 11
bike1.name = "Mountain Bike"

bike2.gear = 13
bike2.name = "Road Bike"

print(f"Name: {bike1.name}, Gears: {bike1.gear} ")
Name: Mountain Bike, Gears: 11 
Popcorn Hacks #1: Add another print command to display the attributes of Bike #2. What output will this code segment produce?
print(f"Name: {bike2.name}, Gears: {bike2.gear} ")
Name: Road Bike, Gears: 13 

Example: Defining a User Class

Now we will look into Classes for users of a program or website.

class User:
    def __init__(self, username, email):
        self.username = username
        self.email = email

    def display_info(self):
        print(f"Username: {self.username}, Email: {self.email}")

For this cell, we define a User class with a constructor method "init" that takes username and email as arguments. The display_info method is used to print the user information.

For backend functionality, this class can be used to create, manipulate, and manage user data. For example, when a new user signs up for an account, you could create a new User object with their username and email. This object can then be used to perform various operations, such as validating the user's input, storing the user's data in a database, or processing user-related requests.

Now let's make a new User:

new_user = User("jeffrey", "leejeffreysc@gmail.com")
new_user.display_info()
Username: jeffrey, Email: leejeffreysc@gmail.com

Here's a step-by-step breakdown of how the code relates to backend functionality:

new_user = User("jeffrey", "leejeffreysc@gmail.com"): This line creates a new User object, initializing it with the username "jeffrey" and the email "leejeffreysc@gmail.com". This could represent a user who has just signed up for an account, and the input values would typically come from user input, such as a frontend signup form.

new_user.display_info(): This line calls the display_info method on the new_user object, which prints the user's information (username and email) to the console. In a real-world backend application, you might use this method or a similar one to display user information in logs, send a welcome email, or create an API response with the user's data.

User/Class @ Decorators:

A Python Class allows for the use of @ decorators, these allow access to attribute data without the use of functions.

  • @property decorator (aka getter). This enables developers to "GET" attribute data in a clean method (object.name versus object.get_name())
  • @name.setter decorator (aka setter). This enables developers to "UPDATE"/set attribute data in a clean method (object.name = "Jeffrey" versus object.set_name("Jeffrey"))

The getter and setter methods provide a clean and controlled way to _ and _ the attributes of an object. This can be particularly useful when interacting with databases, APIs, or other parts of a web application that require the management and manipulation of object attributes.

class Student:
    def __init__(self, student, name):
        self._student = student
        self._name = name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, new_name):
        self._name = new_name

In this example, the Student class has a name attribute, which is accessed and modified through the name property getter and setter methods. The _name attribute uses an underscore prefix, which is a convention to indicate it should not be accessed directly.

Usage of the getter and setter methods are below:

student = Student(1001, "Derek Sol")
print(student.name)  # Get the name using the getter method

student.name = "Jagger Klein"  # Set the name using the setter method
print(student.name)
Derek Sol
Jagger Klein
Popcorn Hacks #2: The following code segment for computers does not have getter and setter methods called. Using the missing attributes "model" and "year", add example getter and setters methods for this class. Show us an example output that this code segment might produce.
class Computer:
    def __init__(self, model, year):
        self._model = model
        self._year = year

    @property
    def model(self):
        return self._model

    @model.setter
    def model(self, new_model):
        self._model = new_model

    @property
    def year(self):
        return self._year

    @year.setter
    def year(self, new_year):
        self._year = new_year
computer = Computer("Lenovo", "2019")
print(computer.model)  # Get the name using the getter method
print(computer.year)
Lenovo
2019

Databases and SQLite

A database is a structured collection of data that is organized and stored in a way that allows for efficient retrieval, manipulation, and management of that data. In other words, a database is a software application that allows you to store and manage large amounts of data in an organized manner.

Some Examples of Databases:

  • Storing and managing customer data, such as contact information, order history, and customer preferences.
  • Keeping track of inventory levels and stock movements in a warehouse or retail store.
  • Managing financial transactions, such as payments, invoices, and receipts.

SQLite is a software library that provides a user-friendly database management system. Unlike other databases, such as MySQL or PostgreSQL, SQLite is programmed within an application, which means it does not require a separate server process to operate. This makes SQLite great for web applications or for use in situations where you don't need a full database server.

In this lesson, we will be demonstrating how to set up a SQLite database in Flask, a Python web framework. Flask allows programmers to easily interact with SQLite databases, and we'll walk through the process of setting up a new database, creating tables, and adding data. We'll also cover some basic SQL commands that you can use to interact with your database, including CREATE TABLE, INSERT, SELECT, UPDATE, and DELETE.

Setting up a SQLite database in Flask

from flask import Flask
import sqlite3

# Create a Flask application
app = Flask(__name__)
   
# Connect to the SQLite database using SQLite3
conn = sqlite3.connect('database.db')

# Create a cursor object to execute SQL commands
cursor = conn.cursor()

# Create a table in the database using SQL commands
cursor.execute('''CREATE TABLE students
                 (id INTEGER PRIMARY KEY, name TEXT, grade INTEGER)''')

# Commit the changes to the database
conn.commit()

# Close the connection
conn.close()

Note: This will create a new file 'database.db' in the _notebooks folder if one doesn't exist.

Breaking down this code segment:

  • We imported the necessary libraries and tools
  • We created a Flask application
  • We connected to the SQLite database using SQLite3
  • We created a cursor object to execute SQL commands
  • We created tables in the database using SQL commands

Flask provides several extensions and tools for working with SQLite databases, including Flask-SQLAlchemy, Flask-SQLite3, and Flask-Admin. These tools provide a high-level interface for interacting with the database, making it easy to perform common operations such as adding, updating, and deleting records.

CRUD Commands with SQLite

CRUD stands for Create, Read, Update, and Delete, which are the basic operations you can perform on data. SQL is useful library because it allows programmers to create new tables to organize data, add new data to a table, update data that's already there, or delete data that's no longer needed.

  • C: To create a new record in a database, you must first define the table structure that will store the data. This can be accomplished using SQL commands such as CREATE. Once the table is created, data can be added to it using the INSERT INTO command.

  • R: To retrieve data from the database, you can use the READ command. You can specify which fields you want to retrieve and the conditions you want to apply using the WHERE clause. There are also several functions available to aggregate and manipulate data.

  • U: To modify existing data in the database, you can use the UPDATE command. You will need to specify which table and fields you want to update, and the conditions you want to apply using the WHERE clause.

  • D: To remove data from the database, you can use the DELETE command.

Example of CRUD within an SQLite Database

from flask import Flask
import sqlite3

# Create a Flask application
app = Flask(__name__)
   
# Connect to the SQLite database using SQLite3
conn = sqlite3.connect('database.db')

# Create a cursor object to execute SQL commands
cursor = conn.cursor()

# Create a table in the database using SQL commands
cursor.execute('''CREATE TABLE basketball
                 (_player TEXT PRIMARY KEY, _team TEXT, _position TEXT, _opinion TEXT)''')

# Commit the changes to the database
conn.commit()

# Close the connection
conn.close()

Hypothetically, our programmer wanted to create an application about basketball players, and created a new table in SQL to store and interact with user-submitted data. Using the same method as the previous table, a new table 'basketball' is created by the cursor and added to the 'database.db' file.

Create Function:

def create():
   
   database = 'database.db'
   player = input("Enter the player name")
   team = input("Enter team name")
   position = input("Enter player position")
   opinion = input("Enter your opinion ")

   # Connecting to the database, create cursor to execute the SQL command
   connection = sqlite3.connect(database)
   cursor = connection.cursor()

   try:
       # Execute SQL to insert record in to db
       cursor.execute("INSERT INTO basketball (_player, _team, _position, _opinion) VALUES (?, ?, ?, ?)", (player, team, position, opinion))
       # Commit the changes
       connection.commit()
       print(f"New basketball with {player} is added.")
              
   except sqlite3.Error as error:
       print("Error while inserting record", error)

   # Closing cursor and connection
   cursor.close()
   connection.close()
  
create()
New basketball with smith is added.

The create() feature allows users to input information about a basketball player and store it in a SQLite table 'basketball' inside the database file we already created 'database.db'. The code prompts the user for the player's name, team name, position, and their opinion about the player, then establishes a connection to the SQLite database and creates a cursor object for executing SQL commands.

The cursor.execute() method is called with an SQL INSERT command to insert a new entry into the 'basketball' table. The connection.commit() method saves the changes to the database, and if the record is inserted successfully, a confirmation message is printed. In case of errors, the except block catches the sqlite3.Error exception and prints an error message. Finally, the cursor and the connection to the database are closed, and the create() function is called to execute the code.

Read Function:

def read():

   database = 'database.db'
   # Connecting to the database, create cursor to execute the SQL command
   connection = sqlite3.connect(database)
   cursor = connection.cursor()
  
   # Fetch all the records from basketball table
   results = cursor.execute('SELECT * FROM basketball').fetchall()

   if len(results) != 0:
       for row in results:
           print(row)
   else:
       print("No player found.")

   # Closing cursor and connection
   cursor.close()
   connection.close()

read()
('bob', 'wertrwet', 'wewertwert', 'wertwert')
('smith', 'wertwertewr', 'wertwertew', 'wertwert')

The read() feature retrieves and prints all entries from the 'basketball' table in the specified SQLite database. The script establishes a connection to the SQLite database, creates a cursor object for executing SQL commands, and calls the cursor.execute() method with an SQL SELECT command to fetch all the entires from the 'basketball' table. The fetchall() method retrieves all the rows returned by the SELECT command and stores them in the results variable.

The code then checks if there are any records in the table and, if so, iterates through each row in the results using a 'for loop' and prints the row. If there are no records, the program prints "No player found". Finally, the cursor and the connection to the database are closed, and the read() function is called to execute the code.

Update Function:

import sqlite3

def update(): 
  
   database = 'database.db'
   player = input("Enter a basketball player to update the opinion for:")
   opinion = input("Enter new opinion:")

  # Connecting to the database, create cursor to execute the SQL command
   connection = sqlite3.connect(database)
   cursor = connection.cursor()

   try:
       # Updating review for the basketball
       cursor.execute("UPDATE basketball SET _opinion = ? WHERE _player = ?", (opinion, player))
       if cursor.rowcount != 0:
           print(f"Review for the basketball is updated to {opinion}")
           connection.commit()
       else:
           print(f"Player not found.")
   except sqlite3.Error as error:
       print("Error occurred", error)
      
   # Closing cursor and connection
   cursor.close()
   connection.close()

update()
Review for the basketball is updated to asdhfgasdhbgfkhjsgtkjhqwergt

This update feature updates the opinion for a specific basketball player in an SQLite database. The code defines an update() function, which prompts the user to input the basketball player they want to update and the new opinion for that player. The script connects to the SQLite database, creates a cursor object, and handles any potential errors while executing the SQL command. The cursor.execute() method updates the review in the database for the specified basketball item.

If at least one value is changed, a success message is printed and the changes are committed to the database. Otherwise, a "Player not found." message is displayed. In the except block, if an error occurs, an error message along with specific error details is printed. Finally, the cursor and connection to the database are closed, and the update() function is called to execute the review update process.

Delete Function

import sqlite3

def delete():
  
   database = 'database.db'
   player = input("Enter basketball player to delete")

   # Connecting to the database, create cursor to execute the SQL command
   connection = sqlite3.connect(database)
   cursor = connection.cursor()
  
   try:
       cursor.execute("DELETE FROM basketball WHERE _player = ?", (player,))
       if cursor.rowcount == 0:
           print(f"{player} does not exist")
       else:
           print(f"Successfully deleted basketball player {player}")
       connection.commit()
   except sqlite3.Error as error:
       print("Error occurred: ", error)
      
   # Closing cursor and connection
   cursor.close()
   connection.close()

delete()
Successfully deleted basketball player smith

The delete feature deletes a specific basketball player from the specified SQLite database. The code defines a delete() function that prompts the user to input the basketball player they want to delete. After connecting to the SQLite database and creating a cursor object, the code handles potential errors while executing the SQL command. The cursor.execute() method is used to delete the basketball item with the specified ID.

If no rows are affected, a message is printed indicating that the basketball player does not exist. Otherwise, a success message is displayed, confirming the basketball player's deletion. The changes are then committed to the database. In the except block, any error that occurs triggers an error message along with specific error details. Finally, the cursor and connection to the database are closed, and the delete() function is called to execute the basketball item deletion process. This code showcases a practical way to remove information from an SQLite database based on user input.

Alongside the four CRUD functions, a menu function is designed to manage Create, Read, Update, Delete operations and display the schema. The menu function is responsible for taking in user inputs and making the program call various functions. When running a menu function, users enter their desired operation, whether it be Create, Read, Update, or Delete, and the input is checked by various conditions to determine which function should be called. Many menu functions are called recursively, so the cell will repeat the menu process until the user decides to exit.

Popcorn Hacks #3: The menu function below is missing some of the CRUD functions for the specified basketball players table. Fix the following code by adding the intended CRUD functions, then perform a READ function on the database.
def menu():
   operation = input("Enter: (C)reate (R)ead (U)pdate or (D)elete")
   if operation.lower() == 'c':
       create()
   if operation.lower() == 'r':
       read()
   if operation.lower() == 'u':
       update()
   if operation.lower() == 'd':
       delete()
   elif len(operation)==0: # Escape Key
       return
   else:
       print("Please enter c, r, u, or d")
   menu() # recursion, repeat menu
try:
   menu() # start menu
except:
   print("Perform Jupyter 'Run All' prior to starting menu")
('bob', 'wertrwet', 'wewertwert', 'asdhfgasdhbgfkhjsgtkjhqwergt')
Please enter c, r, u, or d

Image Uploading with SQLite

Using various Python Libraries, we are able to upload and customize images using python code, as well as using images to interact with databases.

One way to store images in a database is to INSERT the image data as a binary blob.

To store an image as a blob in SQLite, you can first read the image file and convert it to a binary format using a programming language like Python. Here's an example of how to do this:

Making the Table to store our Image Data:

from flask import Flask
import sqlite3

# Create a Flask application
app = Flask(__name__)
   
# Connect to the SQLite database using SQLite3
conn = sqlite3.connect('database.db')

# Create a cursor object to execute SQL commands
cursor = conn.cursor()

# Create a table in the database using SQL commands
cursor.execute('''CREATE TABLE images
                 (name TEXT PRIMARY KEY, data BLOB)''')

# Commit the changes to the database
conn.commit()

# Close the connection
conn.close()

We use a new data type "BLOB" to store specific data from our .jpg image.

Adding Image Data to our Table:

You can use this example sunset jpg for our code. Download and place it in the same repository as this notebook. Rename image to 'image.jpg'

https://t4.ftcdn.net/jpg/01/04/78/75/360_F_104787586_63vz1PkylLEfSfZ08dqTnqJqlqdq0eXx.jpg

import sqlite3

# Open a connection to the SQLite database
conn = sqlite3.connect('database.db')

# Read the image file as binary data
with open('./images/image.jpg', 'rb') as jpg:
    image_data = jpg.read()

# Insert the image data into the database
conn.execute("INSERT INTO images (name, data) VALUES (?, ?)", ('image', image_data))

# Commit the transaction
conn.commit()

# Close the connection
conn.close()

In this example, the image data is read from the "image.jpg" file and inserted into the "images" table in the "database.db" SQLite database. The image data is then stored as a binary blob in the "data" column of the table.

Converting BLOB to Image -> Displaying Image

import sqlite3
from PIL import Image
import io

# Open a connection to the SQLite database
conn = sqlite3.connect('database.db')

# Retrieve the image data from the database
result = conn.execute("SELECT data FROM images WHERE name=?", ('image',)).fetchone()

# Convert the binary data to an image object
image_data = io.BytesIO(result[0])
image = Image.open(image_data)

# Display the image
image.show()

# Close the connection
conn.close()

The image data is then retrieved from the "images" table using a SELECT statement with a WHERE clause that correctly matches the image name. The binary image data is then converted to an image object using the PIL library and displayed on the screen.

Popcorn Hacks #4: Using the same table, find a new .jpg image and add its image data to the database. Display the image using the PIL Library and show us the output image.
import sqlite3

# Open a connection to the SQLite database
conn = sqlite3.connect('database.db')

# Read the image file as binary data
with open('./images/lassen-volcano.jpg', 'rb') as jpg:
    image_data = jpg.read()

# Insert the image data into the database
conn.execute("INSERT INTO images (name, data) VALUES (?, ?)", ('lassen-volcano', image_data))

# Commit the transaction
conn.commit()

# Close the connection
conn.close()
import sqlite3
from PIL import Image
import io

# Open a connection to the SQLite database
conn = sqlite3.connect('database.db')

# Retrieve the image data from the database
result = conn.execute("SELECT data FROM images WHERE name=?", ('image',)).fetchone()

# Convert the binary data to an image object
image_data = io.BytesIO(result[0])
image = Image.open(image_data)

# Display the image
image.show()

# Close the connection
conn.close()

HACKS (Due Sunday 11:59 PM)

Make sure to fill in all blank notes and popcorn hacks, and run each of the interactive coding exercises. (0.45)

  • Extra Credit: Winning team for Popcorn Hacks (+0.05)

Create your own SQL database and create an algorithm that can insert, update, and delete data related to your topic of choice. Functionality for adding image data MUST be included. Points will be awarded based on effort and success. (0.45)

  • Extra Credit: Connect your backend database to a visible frontend! Screenshots of displayed data is fine. (+0.05)
# imports dependencies of program
from flask import Blueprint, request
from flask_restful import Api, Resource, reqparse
from .. import db
from ..model.schedules import Schedule

# setting variables used to store future data
schedule_bp = Blueprint("schedule", __name__)
schedule_api = Api(schedule_bp)

# this class is the resource for the flask API, which allows to user to use methods to store data
class ScheduleAPI(Resource):
    # GET method
    def get(self):
        # requested id turned into variable for later use
        id = request.args.get("id")
        # this looks through the database by id and finds the id that you are requesting
        schedule = db.session.query(Schedule).get(id)
        if schedule:
            # data is sent to the frontend
            return schedule.to_dict()
        # error checking
        return {"message": "schedule not found"}, 404

    # POST method
    def post(self):
        # using parsers to gather and data that is to be posted and validate it's true
        parser = reqparse.RequestParser()
        parser.add_argument("period", required=True, type=int)
        parser.add_argument("class1", required=False, type=str)
        parser.add_argument("classNumber", required=False, type=str)
        parser.add_argument("startTime", required=False, type=str)
        parser.add_argument("endTime", required=False, type=str)
        # variable made to store data
        args = parser.parse_args()
        # variable created to format all data into one place and send to database for logging
        schedule = Schedule(args["period"], args["class1"], args["classNumber"], args["startTime"], args["endTime"])
        try:
            # data is sent to the database for storage
            db.session.add(schedule)
            db.session.commit()
            return schedule.to_dict(), 201
        except Exception as e:
            # error checking
            db.session.rollback()
            return {"message": f"server error: {e}"}, 500

    # PUT method
    def put(self):
        # parser made to search through all incoming requests
        parser = reqparse.RequestParser()
        # argument expected is set, in this case the id
        parser.add_argument("id", required=True, type=int)
        args = parser.parse_args()

        try:
            # looks through database, looking for id that needs to be edited
            schedule = db.session.query(Schedule).get(args["id"])
            if schedule:
                # adds in edited data
                db.session.commit()
            else:
                # returns data if id is not found
                return {"message": "schedule not found"}, 404
        except Exception as e:
            # returns data if there's an error in the request
            db.session.rollback()
            return {"message": f"server error: {e}"}, 500

    # DELETE method
    def delete(self):
        # parser made to search through all incoming requests
        parser = reqparse.RequestParser()
        # argument expected is set, in this case the id
        parser.add_argument("id", required=True, type=int)
        args = parser.parse_args()

        try:
            # looks through database, looking for id that needs to be deleted
            schedule = db.session.query(Schedule).get(args["id"])
            if schedule:
                # that row with info is deleted
                db.session.delete(schedule)
                db.session.commit()
                return schedule.to_dict()
            else:
                # checks if theres an error with finding the id
                return {"message": "schedule not found"}, 404
        except Exception as e:
            # checks if there's an error with request
            db.session.rollback()
            return {"message": f"server error: {e}"}, 500

# new class that includes all data, rather than individual, only has 2 methods
class ScheduleListAPI(Resource):
    # GET method
    def get(self):
        # looks through entire database
        schedules = db.session.query(Schedule).all()
        # pulls all data
        return [schedule.to_dict() for schedule in schedules]

    # DELETE method
    def delete(self):
        try:
            # deletes entire database resource 
            db.session.query(Schedule).delete()
            db.session.commit()
            return []
        except Exception as e:
            # checks for errors in with request
            db.session.rollback()
            return {"message": f"server error: {e}"}, 500

# link extensions of each class
schedule_api.add_resource(ScheduleAPI, "/schedule")
schedule_api.add_resource(ScheduleListAPI, "/scheduleList")
# imports dependencies of program
from sqlalchemy import Column, Integer, String
from .. import db

# adds new class that defines the schedule database
class Schedule(db.Model):
    # defining table
    __tablename__ = "schedule"
    # creates new column for each value that will be stored in the database, as well as how it will be stored
    id = Column(Integer, primary_key=True)
    _period = Column(Integer, nullable=False)
    _class1 = Column(String(255), nullable=False)
    _classNum = Column(String(255), nullable=False)
    _startTime = Column(String, nullable=False)
    _endTime = Column(String, nullable=False)

    # initialization of the values that will be stored
    def __init__(self, period, class1, classNum, startTime, endTime):
        # sets variables for instance, making them private to each instance run
        self._period = period
        self._class1 = class1
        self._classNum = classNum
        self._startTime = startTime
        self._endTime = endTime

    # returns string of data object
    def __repr__(self):
        # returns what data, used for logging when debugging
        return "<Timer(id='%s', period='%s', class1='%s', classNum='%s', startTime='%s', endTime='%s')>" % (
            self.id,
            self.period,
            self.class1,
            self.classNum,
            self.startTime,
            self.endTime
        )

    # getter method: gets the instance's period variable
    @property
    def period(self):
        return self._period

    # setter method: sets the instance's period variable
    @period.setter
    def text(self, period):
        self._text = period

    # getter method: gets the instance's class name variable
    @property
    def class1(self):
        return self._class1

    # setter method: sets the instance's period variable
    @class1.setter
    def class1(self, value):
        self._class1 = value

    # getter method: gets the instance's class number variable
    @property
    def classNum(self):
        return self._classNum

    # setter method: sets the instance's period variable
    @classNum.setter
    def classNum(self, value):
        self._classNum = value

    # getter method: gets the instance's start time variable
    @property
    def startTime(self):
        return self._startTime

    # setter method: sets the instance's period variable
    @startTime.setter
    def startTime(self, value):
        self.startTime = value
    
    # getter method: gets the instance's end time variable
    @property
    def endTime(self):
        return self._endTime

    # setter method: sets the instance's period variable
    @endTime.setter
    def endTime(self, value):
        self._endTime = value

    # returns dictionary of data, which is used to be converted to JSON
    def to_dict(self):
        return {"id": self.id, "period": self.period, "class1": self.class1, "classNum": self.classNum, "startTime": self.startTime, "endTime": self.endTime}

# sets some initial values for the database
def init_schedules():
    # sets each initial data row as a variable under Schedule class
    task1 = Schedule(period=1, class1="math", classNum="R402", startTime="10:00", endTime="11:00")
    task2 = Schedule(period=2, class1="physics", classNum="K105", startTime="11:00", endTime="12:00")
    task3 = Schedule(period=3, class1="history", classNum="L117", startTime="12:00", endTime="13:00")
    task4 = Schedule(period=4, class1="csp", classNum="A101", startTime="13:00", endTime="14:00")
    task5 = Schedule(period=5, class1="english", classNum="G115", startTime="14:00", endTime="15:00")
    # adds each data row to database
    db.session.add(task1)
    db.session.add(task2)
    db.session.add(task3)
    db.session.add(task4)
    db.session.add(task5)

    db.session.commit()
<!-- FRONTEND FILE -->

<!-- run on a Github Pages website, using a template from teacher, John Mortensen, on website layout and nothing else -->
<html>
<style>
  /* personal styling on interface to make it look appealing */
  input {
    padding: 10px;
    background-color: #4a4a48;
    border: 0px;
    color: #e7dff7;
    border-radius: 15px;
    height: 37px;
  }
  input:focus, textarea:focus, select:focus{
    outline: none;
  }
  select {
    padding: 10px;
    background-color: #4a4a48;
    border: 0px;
    color: #e7dff7;
    border-radius: 15px;
  }
  button {
    border-radius: 10px;
    background: #4a4a4a;
    font-size: 15px;
    color: #a881f7;
    border-color: #775cad;
    margin: 5px;
    width: 60px;
    height: 40px;
    text-align: center;
  }
  .button {
    position: inherit;
    float: left;
    align-content: center;
    width: 45%;
    margin-top: 15px;
  }
  h3 {
    color: #a881f7;
    font-size: 25px;
    padding-top: 15px;
  }
  .data {
    width: 100%;
    position: inherit;
    float: right;
  }
  .title {
    height: 40px;
    color: #a881f7;
    font-size: 30px;
    text-align: center;
    padding: 10px;
  }
  .table {
    border: #a881f7 solid;
    border-radius: 10px;
    border-collapse:separate;
  }
  .cell {
    border: 1px solid;
    text-align: center;
  }
  input::-webkit-outer-spin-button, input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  .texts1 {
    width: 50%;
    float: left;
  }
  .texts2 {
    width: 50%;
    float: left;
    padding-bottom: 20px;
  }
  .drop {
    border-radius: 10px;
    background: #4a4a4a;
    font-size: 15px;
    color: #a881f7;
    border-color: #775cad;
  }
  canvas {
    border-radius: 10px;
    border: 1px solid #000000;
  }
  select {
    text-align: center;
    margin: 5px;
    width: 27%;
  }
  ::-webkit-scrollbar {
      width: 7px;
  }
  ::-webkit-scrollbar-track {
      -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
      -webkit-border-radius: 10px;
      border-radius: 10px;
  }
  ::-webkit-scrollbar-thumb {
      -webkit-border-radius: 10px;
      border-radius: 10px;
      background: #a881f7;
      -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5);
  }
  ::-webkit-scrollbar-thumb:window-inactive {
      background: #a881f7;
  }
</style>

<div class='container'>
  <!-- visual interface that the user sees -->
  <p style="text-align: center;">Enter your class information below and click enter when you are ready.</p> 
  <div class='texts1'>
  <!-- input cells used for gathering user data -->
    <h3> Period </h3>
        <input autocomplete="off" id='newPeriod' type='number' required>
    <h3> Class </h3>
        <input autocomplete="off" id='newClass' type='text' required>
    <h3> Class Number </h3>
        <select id="dropClass1" required>
          <option value="" selected disabled hidden>---</option>
          <option>A</option>
          <option>B</option>
          <option>D</option>
          <option>E</option>
          <option>G</option>
          <option>J</option>
          <option>K</option>
          <option>L</option>
          <option>M</option>
          <option>N</option>
          <option>P</option>
          <option>R</option>
          <option>S</option>
        </select>
        <select id="dropClass2" required>
          <option value="" selected disabled hidden>---</option>
          <option>101</option>
          <option>102</option>
          <option>103</option>
          <option>104</option>
          <option>105</option>
          <option>106</option>
          <option>107</option>
          <option>108</option>
          <option>110</option>
          <option>111</option>
          <option>112</option>
          <option>113</option>
          <option>114</option>
          <option>115</option>
          <option>116</option>
          <option>117</option>
          <option>118</option>
          <option>121</option>
          <option>122</option>
          <option>123</option>
          <option>124</option>
          <option>125</option>
          <option>126</option>
          <option>128</option>
          <option>136</option>
          <option>138</option>
          <option>144</option>
          <option>148</option>
          <option>150</option>
          <option>151</option>
          <option>300</option>
          <option>301</option>
          <option>302</option>
          <option>303</option>
          <option>401</option>
          <option>402</option>
          <option>501</option>
          <option>502</option>
          <option>Performing Arts Center</option>
          <option>Library</option>
          <option>Administration</option>
          <option>Food Court</option>
          <option>Gym</option>
        </select>
  </div>
  <!-- input cells for start and end times -->
  <div class='texts2'>
    <h3> Start Time (hh:mm) </h3>
        <input autocomplete="off" id='newStart' type='time' required>
    <h3> End Time (hh:mm) </h3>
        <input autocomplete="off" id='newEnd' type='time' required>
  </div>
  <!-- submission and clear button used to alter the table state (add or remove data entered by the user) -->
  <div class="button">
    <button class='button' id='addClassButton' onclick="addSchedule()">Add</button>
    <button class='button' id='remove'>Clear</button>
  </div>
  <!-- data table used to display class information entered by the user -->
  <div class="data">
    <p class="title">Classes</p>
    <table class="table" id="schedule" style="width: 100%; margin-left: auto; margin-right: auto;">
      <tr>
        <th class="cell">Period</th>
        <th class="cell">Class Name</th>
        <th class="cell">Class Number</th>
        <th class="cell">Start Time</th>
        <th class="cell">End Time</th>
      </tr>
    </table>
  </div>
</div>
<!-- hidden image used to create map canvas on which classes are marked -->
<img src="images/school_map.jpg" id="map" alt="map" usemap="#map" hidden>
<!-- canvas on which map and highlighted classes will be displayed -->
<canvas id="canvas" width="652px" height="652px">
</canvas>

<!-- logic of the program -->
<script>

// variables created for the map canvas which is used to project classes for user to see
var map = document.getElementById("canvas");
var mapCanvas = map.getContext("2d");
var imgMap = document.getElementById("map");

// redundancy, making sure map always loads on website
if (imgMap.complete) {
  mapCanvas.drawImage(imgMap, 0, 0, 652, 652); // if map is complete loading, don't try loading map again
} else {
    imgMap.onload = function () {
      mapCanvas.drawImage(imgMap, 0, 0, 652, 652); // if map hasn't loaded, map will attempt to load again, making sure it always shows
    };
}

// help on how to draw a star on HTML canvas was found here: https://stackoverflow.com/questions/25837158/how-to-draw-a-star-by-using-canvas-html5
// code is modified to meet my specific needs
function starMark(cx, cy, spikes, outerRadius, innerRadius) {
  // created variables for star location and orientation
  var rot = Math.PI / 2 * 3;
  var x = cx;
  var y = cy;
  var step = Math.PI / spikes;

  // starting path of where the star is drawn and how it is drawn
  mapCanvas.strokeStyle = "#000";
  mapCanvas.beginPath();
  mapCanvas.moveTo(cx, cy - outerRadius)
  // moves brush to repeat a pattern in order to draw the spikes of the star
  for (i = 0; i < spikes; i++) {
      x = cx + Math.cos(rot) * outerRadius;
      y = cy + Math.sin(rot) * outerRadius;
      mapCanvas.lineTo(x, y)
      rot += step

      x = cx + Math.cos(rot) * innerRadius;
      y = cy + Math.sin(rot) * innerRadius;
      mapCanvas.lineTo(x, y)
      rot += step
  }
  // ending the path of the brush in order to end the star and sets final color, line width, and infill of star
  mapCanvas.lineTo(cx, cy - outerRadius)
  mapCanvas.closePath();
  mapCanvas.lineWidth=5;
  mapCanvas.strokeStyle='red';
  mapCanvas.stroke();
}

// setting formatting of keys used to store room information which is used to index the rooms on the map and their coordinates
function storeRoomData(room, xVal, yVal, dataArray) {
    dataArray.push({room: room, x: xVal, y: yVal});
}

// array of room numbers and coordinates is set and filled with 'storeRoomData()' function
var coords = [];

// data for room numbers, can add or take away data any time
storeRoomData("A101",347,507,coords)
storeRoomData("A102",325,509,coords)
storeRoomData("A107",302,501,coords)
storeRoomData("A116",283,487,coords)
storeRoomData("A124",261,484,coords)
storeRoomData("A125",243,469,coords)
storeRoomData("A126",228,445,coords)
storeRoomData("A136",253,425,coords)
storeRoomData("A138",267,436,coords)
storeRoomData("A144",313,462,coords)
storeRoomData("A148",325,437,coords)
storeRoomData("A150",330,469,coords)
storeRoomData("A151",349,471,coords)
storeRoomData("B111",573,358,coords)
storeRoomData("B113",594,356,coords)
storeRoomData("B115",619,354,coords)
storeRoomData("B121",592,239,coords)
storeRoomData("B123",571,247,coords)
storeRoomData("B125",551,255,coords)
storeRoomData("B128",521,263,coords)
storeRoomData("D101",450,260,coords)
storeRoomData("D102",431,241,coords)
storeRoomData("D103",463,247,coords)
storeRoomData("D104",445,229,coords)
storeRoomData("D111",471,203,coords)
storeRoomData("D112",489,221,coords)
storeRoomData("D113",485,189,coords)
storeRoomData("D114",503,208,coords)
storeRoomData("D115",499,176,coords)
storeRoomData("D116",517,194,coords)
storeRoomData("D117",513,162,coords)
storeRoomData("D118",531,183,coords)
storeRoomData("E101",435,175,coords)
storeRoomData("E102",419,169,coords)
storeRoomData("E103",451,153,coords)
storeRoomData("E104",427,144,coords)
storeRoomData("E105",461,129,coords)
storeRoomData("E106",437,119,coords)
storeRoomData("G101",373,215,coords)
storeRoomData("G102",345,213,coords)
storeRoomData("G103",374,199,coords)
storeRoomData("G104",346,353,coords)
storeRoomData("G111",349,162,coords)
storeRoomData("G112",377,163,coords)
storeRoomData("G113",350,143,coords)
storeRoomData("G114",377,144,coords)
storeRoomData("G115",350,123,coords)
storeRoomData("G116",376,125,coords)
storeRoomData("G117",352,104,coords)
storeRoomData("G118",379,105,coords)
storeRoomData("J101",281,233,coords)
storeRoomData("J102",305,222,coords)
storeRoomData("J103",272,215,coords)
storeRoomData("J104",297,204,coords)
storeRoomData("J110",260,188,coords)
storeRoomData("J111",254,177,coords)
storeRoomData("J112",249,165,coords)
storeRoomData("J113",283,175,coords)
storeRoomData("J114",275,157,coords)
storeRoomData("J115",241,151,coords)
storeRoomData("J116",266,139,coords)
storeRoomData("J117",233,133,coords)
storeRoomData("J118",257,122,coords)
storeRoomData("K101",222,208,coords)
storeRoomData("K102",209,223,coords)
storeRoomData("K103",206,186,coords)
storeRoomData("K104",185,206,coords)
storeRoomData("K105",186,167,coords)
storeRoomData("K106",165,187,coords)
storeRoomData("L101",234,283,coords)
storeRoomData("L102",224,307,coords)
storeRoomData("L103",218,276,coords)
storeRoomData("L104",208,299,coords)
storeRoomData("L110",179,287,coords)
storeRoomData("L111",167,282,coords)
storeRoomData("L112",155,276,coords)
storeRoomData("L113",186,261,coords)
storeRoomData("L114",169,252,coords)
storeRoomData("L115",140,270,coords)
storeRoomData("L116",151,245,coords)
storeRoomData("L117",121,262,coords)
storeRoomData("L118",133,237,coords)
storeRoomData("M101",103,312,coords)
storeRoomData("M116",75,350,coords)
storeRoomData("N113",170,422,coords)
storeRoomData("N122",157,359,coords)
storeRoomData("P101",176,468,coords)
storeRoomData("P104",156,462,coords)
storeRoomData("P107",152,479,coords)
storeRoomData("P108",133,490,coords)
storeRoomData("P111",106,482,coords)
storeRoomData("P116",98,456,coords)
storeRoomData("R300",452,66,coords)
storeRoomData("R301",481,76,coords)
storeRoomData("R302",506,89,coords)
storeRoomData("R303",530,103,coords)
storeRoomData("R401",392,49,coords)
storeRoomData("R402",418,53,coords)
storeRoomData("R501",334,42,coords)
storeRoomData("R502",361,44,coords)
storeRoomData("S101",100,147,coords)
storeRoomData("S102",118,132,coords)
storeRoomData("S103",137,114,coords)
storeRoomData("S104",155,100,coords)
storeRoomData("S105",170,85,coords)
storeRoomData("S106",186,72,coords)
storeRoomData("S107",201,58,coords)
storeRoomData("Performing Arts Center",137,395,coords)
storeRoomData("Library",380,477,coords)
storeRoomData("Administration",423,454,coords)
storeRoomData("Food Court",477,323,coords)
storeRoomData("Gym",571,304,coords)

// initializes the format of the function which will be used to populate array, using parameters and assigning them to initial values 
coords[0].room == "A101"
coords[0].x == 347
coords[0].y == 507

// loop continues organization of data into array based on key, value pairs and indexing with the use of 'i'
for (var i = 0; i < coords.length; i++) {
    // sets up data based on variables into array
    var room = coords[i].room;
    var x = coords[i].x;
    var y = coords[i].y;
}

// setting variables used to gather data from user inputs in above HTML
var periodInput = document.getElementById('newPeriod');
var classInput = document.getElementById('newClass');
var startInput = document.getElementById('newStart');
var endInput = document.getElementById('newEnd');
var addClassButton = document.getElementById('addClassButton');
var clear = document.getElementById('remove')
var schedule = document.getElementById('schedule');

// delay set to reload page after user adds data
const delay = (delayms) => {
      return new Promise(resolve => setTimeout(resolve, delayms))
    }
// rerendering function using above delay to reload page when called, allowing stars to be drawn on map canvas
const reRender = async () => {
  let reloadDelay = await delay(700)
  window.location.reload()
}

//function used to add data to the backend database and frontend table
function addSchedule() {

  // defines the variables that are to be pushed into the backend api and placed in frontend table
  var period = periodInput.value
  var class1 = classInput.value
  var classNum = document.getElementById('dropClass1').value + document.getElementById('dropClass2').value;
  var startTime = startInput.value
  var endTime = endInput.value

  // this checks is all cells are complete before pushing the data to the backend
  // this creates an array with the variables that are used to define the values entered by the user
  var cellIds = ['newPeriod', 'newClass', 'dropClass1', 'dropClass2', 'newStart', 'newEnd']

  // this iterates through the variables used to define the input cells
  for (let i = 0; i < cellIds.length; i++) {
    var getData = document.getElementById(cellIds[i]).value
    // checks if the cells are empty or not and if they are, it prompts the user to fill in the empty cells
    if (getData === ''){
      alert('Please fill out the empty cells.')
      return
    }
  }

  // reloads the page, allowing for the stars to be drawn onto the map and for the data to be loaded into the table
  reRender()
  // calls function to add data to table directly after data is inputted
  addTask(period, class1, classNum, startTime, endTime)
  // runs the function responsible for adding the data to an api where the data is stored, called, and can be deleted from
  addData(period, class1, classNum, startTime, endTime)
}

// this function adds data into the table for the user to see, based on the data that is read from the database in the api
// it also calls the parameters which are defined to match with the values of the input from the user which are pulled from the api
function addTask(period, class1, classNum, startTime, endTime) {
  
  // this part converts default 24-hour time to 12-hour time
  // splits the start time into variables based on the colons
  var start = startTime.split(":")
  // makes the hour into an integer for future calculations
  var hourStart = parseInt(start[0])
  // if the hour is greater than 12 hours, this runs
  if (hourStart > 12) {
    // new variable is set, which is the original time as an integer with 12 hours subtracted
    newStartHour = hourStart - 12
    // makes it back into a string with PM notation, since this is PM
    newStartTime = newStartHour.toString() + ":" + start[1] + " PM"
  // if hour is less than 12 hours, this runs
  } else if (hourStart < 12) {
    // sets time string as it would be normally, without any conversion, but with AM
    newStartTime = hourStart + ":" + start[1] + " AM"
  }

  // same thing as the other code above but with the end time
  var end = endTime.split(":")
  var hourEnd = parseInt(end[0])
  if (hourEnd > 12) {
    newEndHour = hourEnd - 12
    newEndTime = newEndHour.toString() + ":" + end[1] + " PM"
  } else if (hourEnd < 12) {
    newEndTime = hourEnd + ":" + end[1] + " AM"
  }
  // creates an array with the variables defining the user data pulled form the api, allowing for quick iteration through the rows and cells of the table, to display data quickly
  var tableCells = [period, class1, classNum, newStartTime, newEndTime]
  // creates the rows of information
  var row = document.createElement('tr')
  // for each cell, the program pulls the data from the api based on the variables defined in the above array, and displays them as text for the user to see
  for (var i = 0; i < tableCells.length; i++) {
    var tableCell = document.createElement('th')
    // for each variable in array, the content of the cell is set on whatever the variable is defined as
    tableCell.textContent = tableCells[i]
    // class name is set for styling
    tableCell.className = 'cell'
    // creates new row in table
    row.appendChild(tableCell)
  }
  // creates a new row for further information to be placed
  schedule.appendChild(row)
}

// connection to the api, this was worked on with 5 other students and the teacher, however everything is modified to meet the needs of the specific program I entirely made myself

// creates the ability to call the local api, if full stack is run entirely locally
const isLocalhost = Boolean(
	window.location.hostname === "localhost" ||
	window.location.hostname === "[::1]" ||
	window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
)
// checks whether the site is run locally and decides whether or not to use the local api or hosted api
const api = isLocalhost ? "http://localhost:8199" : "https://saakd.nighthawkcodingsociety.com";

// adds data that the user inputs into the api, based on the parameters which are defined per the values of the input boxes in the above logic
function addData(period, class1, classNum, startTime, endTime){

// a dictionary is made, to organize the data before sending it to the backend for storage
let data = {
  "period": period,
  "class1": class1,
  "classNumber": classNum,
  "startTime": startTime,
  "endTime": endTime
}

// this fetches the post link for the api, allowing for the data to be added into the api database
fetch(api + '/schedule', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(data),
})
  // error checking, to make sure everything works and if there are errors, the program stops
  .then((response) => response.json())
  .catch((error) => {
    console.error('Error:', error);
  });
}

// gathers all data from the api backend and send it for the frontend to process through the get link, which contains all the data in the database
const getList = async () => {
	const list = await fetch(api + "/scheduleList").then((r) => r.json());
	scheduleLocal = list
  return list
};

// gathers data and prepares it to be displayed for the user to see
getList().then(list => {
  // for each class in the data, which is defined as list, that is pulled into the frontend, it is run through the 'addTask()' function in order to display the data for the user to see
  list.forEach(cls => {
    addTask(cls.period, cls.class1, cls.classNum, cls.startTime, cls.endTime)
    
    // adds the markings on the map for each class that is in the dataset
    const result = coords.find(({ room }) => room === cls.classNum);
    starMark(result.x, result.y, 5, 20, 10)
  })
})

// allows data to be cleared from the table and the api once the clear button is clicked
clear.addEventListener("click", function(){
  // runs the 'remove()' function
  remove()
});

// this fetches the delete link for the api, allowing for the data to be removed entirely from the api database, preventing program from regenerating stars and text
function remove() {
  fetch(api + '/scheduleList', {
    method: 'DELETE'
  })
    .then((response) => response.json())

  // sets arbitrary for while loop to clear table
  var x = 1
  // this triggers the page to reload in order to entirely clear the table and map of it's contents
  reRender();
  // entirely clears contents of table
  while (x = 1) {
    const element = document.getElementById("class");
    element.remove();
  }
}
</script>

Result