PROJECT LINK
Issues:
- BY WILLIAM CHENG
Backend issues
Integration with frontend and backend problems
- JPG base64 image displayment issue - Crucial for exemplifying how URI’s (Unique Resource Identifier) and can call upon the data points
- Movement of the target on DOM events, flag logic - Added a flag to check bsaed on DOM events and only moved upon once it finished
Backend - creating an API endpoint
Main.py and functionality for the API here
- Functionality of how everything works underneath the main.py:
- Essentially what the main.py does and it’s relevance is it calls upon the model and the API, the model is where the initial data is initialized and the API is how it handles requests.
Specific code within the main.py
- Main.py commit
- Importing modules is crucial, as seen in the Python Libraries lesson we can import modules and libraries from other code. One way that the backend does this is through the main.py file in the flask server where it calls upon the model file where we can initialize the code
- We can also incorporate the use of the setting up an API and registering it as URi through importing the main function within the API.locations
import threading
# import "packages" from flask
from flask import render_template # import render_template from "public" flask libraries
# import "packages" from "this" project
from __init__ import app,db # Definitions initialization
from model.users import initLeaderboard
from model.locations import init_locations
# setup APIs\
from api.player import player_api
from api.locations import location_api
# setup App pages
from projects.projects import app_projects # Blueprint directory import projects definition
# Initialize the SQLAlchemy object to work with the Flask app instance
db.init_app(app)
# register URIs
app.register_blueprint(app_projects) # register app pages
app.register_blueprint(location_api)
@app.errorhandler(404) # catch for URL not found
def page_not_found(e):
# note that we set the 404 status explicitly
return render_template('404.html'), 404
@app.route('/') # connects default URL to index() function
def index():
return render_template("index.html")
@app.route('/table/') # connects /stub/ URL to stub() function
def table():
return render_template("table.html")
@app.before_first_request
def activate_job():
db.drop_all() # Clear the database
db.create_all() # Recreate the database schema
initPlayers()
init_locations()
# this runs the application on the development server
if __name__ == "__main__":
# change name for testing
from flask_cors import CORS
cors = CORS(app)
app.run(debug=True, host="0.0.0.0", port="8242")
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 4
1 import threading
3 # import "packages" from flask
----> 4 from flask import render_template # import render_template from "public" flask libraries
6 # import "packages" from "this" project
7 from __init__ import app,db # Definitions initialization
ModuleNotFoundError: No module named 'flask'
the api/locations.py file:
- The API locations.py is how it handles the requests including put, get, delete, and post requests. For our project, we really only need to have get requests since we don’t really need to make any other requests to post any new locations, but we added functionality for it in case we wanted to make it easier to add things to the database.
api/users.py
- For just two fields where it’s the user and the points assigned to the name of the user and points, however it also handles POST requests
Learning
- Learning how to create blueprints, endpoints, and handling the GET requests similar to how it was done in previuso example codes. I also incorporate POST requests to the different users and whatnot
from flask import Blueprint, jsonify
from flask_restful import Api, Resource, reqparse
from __init__ import db
from model.locations import Location
# Create a Blueprint for the location API
location_api = Blueprint('location_api', __name__, url_prefix='/api/locations')
# Create the API instance
api = Api(location_api)
class LocationAPI(Resource):
def get(self):
# Retrieve all locations from the database
locations = Location.query.all()
# Prepare the data in JSON format
json_ready = [location.to_dict() for location in locations]
# Return the JSON response
return jsonify(json_ready)
def post(self):
parser = reqparse.RequestParser()
parser.add_argument("location_name", required=True, type=str)
parser.add_argument("image", required=True, type=str)
args = parser.parse_args()
location = Location(args["location_name"], args["image"])
try:
db.session.add(location)
db.session.commit()
return location.to_dict(), 201
except Exception as exception:
db.session.rollback()
return {"message": f"Error: {exception}"}, 500
def put(self):
parser = reqparse.RequestParser()
parser.add_argument("id", required=True, type=int)
parser.add_argument("location_name", type=str)
parser.add_argument("image", type=str)
args = parser.parse_args()
try:
location = db.session.query(Location).get(args["id"])
if location:
if args["location_name"] is not None:
location.location_name = args["location_name"]
if args["image"] is not None:
location.image = args["image"]
db.session.commit()
return location.to_dict(), 200
else:
return {"message": "Location not found"}, 404
except Exception as exception:
db.session.rollback()
return {"message": f"Error: {exception}"}, 500
def delete(self):
parser = reqparse.RequestParser()
parser.add_argument("id", required=True, type=int)
args = parser.parse_args()
try:
location = db.session.query(Location).get(args["id"])
if location:
db.session.delete(location)
db.session.commit()
return location.to_dict()
else:
return {"message": "Location not found"}, 404
except Exception as exception:
db.session.rollback()
return {"message": f"Error: {exception}"}, 500
# Add the LocationAPI resource to the /api/locations endpoint
api.add_resource(LocationAPI, "/")
class LocationListAPI(Resource):
def get(self):
# Retrieve all locations from the database
locations = Location.query.all()
# Prepare the data in JSON format
json_ready = [location.to_dict() for location in locations]
# Return the JSON response
return jsonify(json_ready)
# Add the LocationListAPI resource to the /api/locationsList endpoint
api.add_resource(LocationListAPI, "/api/locations")
import json
from flask import Blueprint, request, jsonify
from flask_restful import Api, Resource # used for REST API building
from model.users import User
user_api = Blueprint('user_api', __name__,
url_prefix='/api/leaderboard')
# API docs https://flask-restful.readthedocs.io/en/latest/api.html
api = Api(user_api)
class LeaderboardAPI:
class _CRUD(Resource):
def post(self):
'''
Create a new user entry in the leaderboard table.
Read data from the JSON body.
'''
body = request.get_json()
# Validate username
username = body.get('username')
if username is None or len(username) < 2:
return {'message': f'Username is missing or is less than 2 characters'}, 400
# Validate points (should be a non-negative integer)
points = body.get('points')
if not isinstance(points, int) or points < 0:
return {'message': 'Points must be a non-negative integer'}, 400
# Create a new leaderboard entry
leaderboard_entry = User(username=username, points=points)
leaderboard_entry.create()
# Return the newly created leaderboard entry
return jsonify({'username': leaderboard_entry.username, 'points': leaderboard_entry.points})
def get(self):
'''
Retrieve the leaderboard data.
Read all leaderboard entries and prepare the output in JSON format.
'''
leaderboard_entries = User.query.all()
leaderboard_data = [{'username': entry.username, 'points': entry.points} for entry in leaderboard_entries]
return jsonify(leaderboard_data)
def put(self):
''' Update user points '''
body = request.get_json()
username = body.get('username')
new_points = body.get('points')
user = User.query.filter_by(username=username).first()
if user is None:
return {'message': 'User not found'}, 404
if new_points is not None:
user.update_points(new_points)
return {'message': f'Updated points for user {user.username}'}
# Building REST API endpoints
api.add_resource(_CRUD, '/') # Create and Read operations
model/locations.py and model/users.py
- Locations.py in the model is where the initial data is loaded and we can add some instances for it to work
- Here base64 is encoded for the images, and we also assign specific things to the database
from __init__ import db
import base64
class Location(db.Model):
__tablename__ = "locations"
id = db.Column(db.Integer, primary_key=True)
location_name = db.Column(db.String, nullable=False)
image = db.Column(db.String, nullable=False)
def __init__(self, location_name, image):
self.location_name = location_name
self.image = image
def to_dict(self):
return {"id": self.id, "location_name": self.location_name, "image": self.image}
def image_to_base64(image_path):
with open(image_path, "rb") as image_file:
image_binary = image_file.read()
base64_encoded = base64.b64encode(image_binary)
base64_string = base64_encoded.decode("utf-8")
return base64_string
# Example usage
image_path = "static/assets/IMG_0902.jpg" # Relative path to the image
base64_data = image_to_base64(image_path)
image_path2 = "static/assets/IMG_0908.jpg" # Another relative path to a different image
base64_data2 = image_to_base64(image_path2)
# locationposition=(20, 45)
# AND locationposition=(20, 45)
def init_locations():
location1 = Location(location_name="20, 45", image=base64_data)
location2 = Location(location_name="35, 40", image=base64_data2)
# location3 = Location(location_name="Location 3", image="image3.jpg") # Placeholder for the third image
db.session.add(location1)
db.session.add(location2)
# db.session.add(location3)
db.session.commit()
if __name__ == '__main__':
init_locations()
from datetime import date
import os
import base64
import json
from sqlalchemy.exc import IntegrityError
from werkzeug.security import generate_password_hash, check_password_hash
from __init__ import app, db
# Define the User class for the leaderboard table
class User(db.Model):
__tablename__ = 'leaderboard'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), unique=True, nullable=False)
_points = db.Column(db.Integer, default=0) # Use _points as a private attribute
def __init__(self, username, points=0):
self.username = username
self._points = points # Initialize _points attribute
@property
def points(self):
return self._points
@points.setter
def points(self, points):
if points >= 0:
self._points = points
def create(self):
"""Create a new user entry in the leaderboard table."""
try:
db.session.add(self)
db.session.commit()
return self
except IntegrityError:
db.session.remove()
return None
def read(self):
"""Retrieve user information from the leaderboard table."""
return {
"id": self.id,
"username": self.username,
"points": self.points # Use the property, not the private attribute
}
def update_points(self, points):
"""Update the points of a user in the leaderboard."""
if points >= 0:
self.points = points # Use the property, not the private attribute
db.session.commit()
return self
def delete(self):
"""Delete a user from the leaderboard."""
db.session.delete(self)
db.session.commit()
return None
# Function to initialize the leaderboard table with sample data
def initLeaderboard():
with app.app_context():
# create the database and the leaderboard table
db.create_all()
users_data = [
{"username": "user1", "points": 100},
{"username": "user2", "points": 150},
{"username": "user3", "points": 50},
# Add some more users later for now it's just testing
]
for data in users_data:
user = User(username=data["username"], points=data["points"])
user.create()
# Run the initialization function to create and populate the leaderboard
if __name__ == "__main__":
initLeaderboard()
Frontend
Getting pull requests through javascript in our HTML map_game file HTML
- We can call upon our API endpoint and make a get response through the use of the fetch function in the HTTP get request. Afterwards once we make a request to the API url which is the /api/locations we can store the response data as JSON data which we can then access and store as a dictionary with callable objects to acess our data. Below is an example of how we can store the initial data as just call from our endpoint and format it in JSON and then eventually store it as a dictionary.
- Fetch Commit
- Front End Issues
Dictionaries
- Learning dictionaries for our program was really useful and i
let coordinatesData = {}; // Store the coordinates and images
let dictCoordinates = {}; // Global variable to store the organized data just fo rnow
let currentIndex = 0; // Initialize to 0
let dotClicked = false; // Flag to check if the dot has been clicked
function fetchAndDisplayImages() {
// Defining our API endpoint
const apiUrl = 'https://teaminfluencerinnovator.stu.nighthawkcodingsociety.com/api/locations/';
// Make an HTTP GET request to the API
fetch(apiUrl)
.then(response => response.json()) // Parse the JSON response
.then(data => {
// coordinatesData = data; // Store the data as it is
dictCoordinates = data;
// Call moveTarget immediately to position the dot at the first location
moveTarget();
})
.catch(error => {
console.error("Error fetching images:", error);
});
}
fetchAndDisplayImages(); // Call fetchAndDisplayImages to initiate the process
- Now what this will look like is it will look like is
Looping through the data and then incorporating it into the game
- Once we have all that data we need to know how to incorporate it into the game, we need to implement indexing to acess those specific datapoints. Afterwards, already implemented in the moveTarget data, we can supply the coordinates for the target to move. Again, the target only moves based on DOM click event listeners highlighted in an earlier blog so the only time we want to move onto the next datapoint in the dictionary’s ID is after a click has been performed.
- Below is an example of when the target is moved after a click is performed and displaying the image at that specific coordinate and adding it to a container as base64 jpg file
- Specific commits: Coordinates Commit + Image Commit
Use of DOM for event listener objects
- DOM for objects utilized on event listener clicks, showing growth and learning from the Web Programming Basics test and lessons on DOM
HTMl elements
- Image tags and element use with the imager container
Example on click on the WEB programming test
%%html
<!-- the ID must be specified on the elements -->
<button id="buttonID">Click here!</button>
<div id="divContainerIDbutton">
<h1 id="h1ElementIDbutton">My Title</h1>
</div>
<!-- our javascript goe here -->
<script>
// define a function => takes parameter text, returns a new p tab
function createPTag(text) {
// creates a new element
var pElement = document.createElement("p")
// using the parameter like a variable
pElement.innerHTML = text
// outputs p tag after it has been created
console.log("Example #7.1, add p tag using a function")
console.log(pElement)
return pElement;
}
// create a function that sets specific text and adds to div
function addPTagOnButton() {
// using our new function
var pTag = createPTag("Starting a paragraph with text created on button press.")
// place the p element in the webpage
var div = document.getElementById("divContainerIDbutton")
// add p tag to the div
div.appendChild(pTag)
// outputs p tag after it has been created
console.log("Example #7.2, update container adding a 'p' tag")
console.log(div)
}
// add the P tag when our button is clicked
var myButton = document.getElementById("buttonID")
myButton.onclick = addPTagOnButton
</script>
On click DOM, HTML element in our code
function moveTarget() {
const keys = Object.keys(dictCoordinates);
if (currentIndex >= 0 && currentIndex < keys.length) {
const key = keys[currentIndex];
const value = dictCoordinates[key];
const location_name = value.location_name;
const coordinatesParsed = location_name.split(",");
const x = parseInt(coordinatesParsed[0], 10);
const y = parseInt(coordinatesParsed[1], 10);
const imageSrc = `data:image/jpg;base64, ${value.image}`;
const maxX = gameContainer.clientWidth - target.clientWidth;
const maxY = gameContainer.clientHeight - target.clientHeight;
target.style.left = Math.min(x, maxX) + "px";
target.style.top = Math.min(y, maxY) + "px";
// Get the image container by ID
const imageContainer = document.getElementById("image-container");
// Create a new image element
const image = document.createElement("img");
image.src = imageSrc;
// Clear the existing content of the image container
imageContainer.innerHTML = '';
// Append the new image to the image container
imageContainer.appendChild(image);
} else {
// If there are no more locations, stop moving the target
target.style.display = 'none';
}
}
gameContainer.addEventListener("click", () => {
if (!dotClicked) {
currentIndex++; // Move to the next location when there's no click
moveTarget();
}
dotClicked = false;
});
target.addEventListener("click", (event) => {
const earnedPoints = 1; // You always get 1 point for clicking on the dot
score += earnedPoints;
scoreDisplay.textContent = `Score: ${score}`;
alert(`You got ${earnedPoints} point!`); // Display a pop-up message
currentIndex++; // Move to the next location when the dot is clicked
moveTarget();
dotClicked = true; // Set the flag to true
});
#image-container img {
position: fixed;
top: 0;
right: 0;
padding: 20px;
max-width: 30%;
}
<div id="image-container" class="w3-container" style="display: flex; flex-wrap: wrap;"></div>
const imageSrc = `data:image/jpg;base64, ${value.image}`;
// Get the image container by ID
const imageContainer = document.getElementById("image-container");
// Create a new image element
const image = document.createElement("img");
image.src = imageSrc;