Build a PinFood application with Flask (part 1)
Pin Food is a web application that displays a page containing an interactive map. The user can mark the location of a new “food stall” on the map. Users can enter the date, category, and description of the “food stall”.
Users can also see places for food stalls that have been marked before by displaying an icon on the map and if the user hovers the mouse over an icon, a more complete description of the food stall will appear.
The goal is, users can easily see which areas have food stalls according to category and help Vloggers come to these places to create their own YouTube content.
In this section we will set up a development environment for our project, create a virtual environment and activate it, install Flask
and PyMySQL
, configure Flask for a MySQL Database and try to build our first page.
Preparation
Open a terminal or command line, then enter into any drive (for example, drive D
if you are using windows). Create a folder called pinfood
:
mkdir pinfood
cd pinfood
Create a virtual environment named .venv
and activate it:
python -m venv .venv
.venv\Scripts\activate
Install the PyMySQL
and Flask
packages. PyMySQL
is used to communicate with MySQL
:
pip install flask pymysql
Database Setup
Inside the project root, create a file called dbconf.py
and add the following configuration:
test = True
dbuser = 'root'
dbpassword = ''
Like Django, we use dbconf.py
file to store settings for our project. test = True
, meaning that our project is currently in development mode. dbuser
contains the MySQL username and dbpassword
contains the password to access the MySQL database.
Let’s create a file called dbsetup.py
inside the project root. Then, type the following code:
import pymysql
import dbconf
connection = pymysql.connect(host='localhost',
user=dbconf.dbuser,
passwd=dbconf.dbpassword)
try:
with connection.cursor() as cursor:
sql = "CREATE DATABASE IF NOT EXISTS db_pinfood"
cursor.execute(sql)
sql = """CREATE TABLE IF NOT EXISTS db_pinfood.foods (
id int NOT NULL AUTO_INCREMENT,
latitude FLOAT(10,6),
longitude FLOAT(10,6),
date DATETIME,
category VARCHAR(50),
description VARCHAR(1000),
updated_at TIMESTAMP,
PRIMARY KEY (id)
)"""
cursor.execute(sql)
connection.commit()
finally:
connection.close()
The dbsetup.py
file contains the codes for creating databases and tables. We need this file so that we no longer create databases and tables manually in the MySQL console. I think this will help us, if we really don’t want to open such a boring MySQL console.
Let’s turn on MySQL Server. Then, run dbsetup.py
to create database and table automatically:
python dbsetup.py
Make sure the database is created successfully by viewing it in the MySQL console using SHOW DATABASES
; or you can use PHPMyAdmin
and also, make sure the foods table in the db_pinfood
is created as well:
In your favorite editor (inside the root project folder), create a directory structure and files like:
Then, let’s open the dbhelper.py
file and add the following code:
import pymysql
import dbconf
class DBHelper:
def connect(self, database="db_pinfood"):
return pymysql.connect(host='localhost',
user=dbconf.dbuser,
passwd=dbconf.dbpassword,
db=database)
def get_all_inputs(self):
connection = self.connect()
try:
query = "SELECT description FROM foods;"
with connection.cursor() as cursor:
cursor.execute(query)
return cursor.fetchall()
finally:
connection.close()
def add_input(self, data):
connection = self.connect()
try:
# The following introduces a deliberate security
# flaw. See section on SQL injection below
query = "INSERT INTO foods " \
"(description) VALUES " \
"('{}');".format(data)
with connection.cursor() as cursor:
cursor.execute(query)
connection.commit()
finally:
connection.close()
def clear_all(self):
connection = self.connect()
try:
query = "DELETE FROM foods;"
with connection.cursor() as cursor:
cursor.execute(query)
connection.commit()
finally:
connection.close()
The dbhelper.py
file contains the DBHelper
class with methods to help us get all the data, add data to the database, and delete data from the database. Just Just imagine this is a model. This model is used as an interface for our application to communicate with the database.
Next, open the pinfood.py
file and add the following code:
from dbhelper import DBHelper
from flask import Flask
from flask import render_template
from flask import request
app = Flask(__name__)
DB = DBHelper()
@app.route("/")
def home():
try:
data = DB.get_all_inputs()
except Exception as e:
print(e)
data = None
return render_template("home.html", data=data)
@app.route("/add", methods=["POST"])
def add():
try:
data = request.form.get("description")
DB.add_input(data)
except Exception as e:
print(e)
return home()
@app.route("/clear")
def clear():
try:
DB.clear_all()
except Exception as e:
print(e)
return home()
if __name__ == '__main__':
app.run(port=5000, debug=True)
The pinfood.py
file is the main file for our project. We can run our project by running this file. When this file is executed, Flask will run a development server which we can access on localhost
with port 5000
.
Next, open the templates/home.html
file and add the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Pin Food</title>
</head>
<body>
<div class="container mt-4 mb-3">
<div class="col-md-12">
<h1 class="display-4">Pin Food</h1>
<hr>
</div>
</div>
<div class="container mb-3">
<div class="col-md-4">
<form action="/add" method="POST">
<label>Description</label>
<input type="text" class="form-control"
name="description">
<button class="btn btn-primary" type="submit">
Submit
</button>
<a href="/clear" class="btn btn-outline-danger">
Clear
</a>
</form>
<div class="mt-3">
{% if data %}
{% for description in data %}
<p>{{ description }}</p>
{% endfor %}
{% endif %}
</div>
</div>
</div>
</body>
</html>
The file is a template containing HTML codes. Flask will render the data passed from the home
function in the pinfood.py
file to this template.
action=”/add”
is the url, which points to the add
function inside the pinfood.py
file. If this form is submitted, the data will be processed in the add
function.
Also, href = “/ clear”
is the url, which points to the clear
function inside pinfood.py
. The clear
function will delete existing data when this button is clicked. Be careful.
Then, on the following line:
<div class="mt-3">
{% if data %}
{% for description in data %}
<p>{{ description }}</p>
{% endfor %}
{% endif %}
</div>
The data
passed from the home
function will be checked with the jinja template engine using if
block. If the data is not empty or None
, the next block will be executed. The next block is looping using the for
.
Alright, we will run the project with the following command:
python pinfood.py
Now, open http://localhost:5000/
in a browser and you should see a result like this:
You can try it, by entering any data into the description field and clicking the Submit button. Try it several times! And then, if you check the foods table you will see the data that was successfully saved:
Source Code
We provide source code that you can clone at any time at the following link: