Introduction
A tutorial to build simple Todo App with Flask Python and SQLite3 for beginner that inspire from tutorial FreeCodeCamp and Digital Ocean with enhancement features.
File-and-folder-structure
├── app.py
├── init_db.py
├── README.md
├── static
│ └── css
│ └── main.css
├── templates
│ ├── base.html
│ ├── edit.html
│ └── index.html
└── todo.db (after run python script `init_db.py`)
Launch-our-flask-basic-script
Let’s open up app.py
in our root directory.
# app.py
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True, port=3000)
Here’s what each line of the code does:
from flask import Flask
– Import the Flask class from the Flask moduleapp = Flask(__name__)
– Create an instance of the Flask class and name it app@app.route("/")
– Decorator that associates the URL path "/" with the index functiondef index():
– Define a function named index that will handle requests to the "/" pathreturn 'Hello World!'
– Return the string "Hello World!" as the response to requests to the "/" pathif __name__ == '__main__':
– Check if the script is being run as the main programapp.run(debug=True, port=3000)
– Run the app instance as a web application on port 3000 with debugging enabled.
When you run this script, Flask will create a web server that listens for incoming HTTP requests on the specified port (3000 in this case). When a request comes in with a URL path of "/", Flask will call the index function and return the string "Hello World!" as the response.
Next run app.py
in the shell.
$ python app.py
It prompts and notify that Flask is launching your app.
^ refer launch-flask-app-terminal.gif
As you can see the ip address Running on http://127.0.0.1/3000
. Copy the link http://127.0.0.1/3000
and paste into your browser.
^ refer screenshot-helloworld.png
Create-and-setup-templates
I have a minimal html
template index.html
. You can copy the templates as it includes with the css
styling. Put the scripts inside templates
folder.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flask CRUD-App with SQLite3</title>
<link rel="stylesheet" type="text/css" href="style.css">
<style>
h2, h3 {
text-align: center;
}
h4 {
text-decoration: line-through red;
}
table {
margin-left: auto;
margin-right: auto;
}
.container {
padding: 20px;
width: 800px;
background-color: #eee;
margin-left: auto;
margin-right: auto;
}
</style>
</head>
<body>
<!-- your content here... -->
<h2>This is index page</h2>
<div class="container">
</div>
<script src="js/scripts.js"></script>
</body>
</html>
To let index.html
function we need to import render_template
. Let’s add few lines of code in app.py
from flask import Flask, render_template
@app.route("/")
def index():
# return 'Hello World!'
return render_template('index.html')
Here is the code is doing:
- after import class
Flask
we add function which isrender_template
to render an HTML file as a response to a web request. - Then we comment out
# return 'Hello World~'
return render_template('index.html')
– use the render_template function to render an HTML file namedindex.html
and return it as the response to requests to the "/" path.
In this case, the index function does not return the "Hello World!" string like in the previous example. Instead, it calls the render_template function with the argument "index.html". This tells Flask to look for an HTML file named "index.html" in a directory called "templates" located in the same directory as the app.py script. Flask will then render this HTML file and return it as the response to requests to the "/" path.
Using render_template allows you to separate your web application logic from your HTML templates, making your code more maintainable and easier to understand. You can create as many HTML templates as you need and use them to generate dynamic content for your web pages.
As you load or refresh the page browser it prints "This is index page"
^ refer screenshot-return-render-template.png
@app.route("/")
def index():
# return 'Hello World!'
greet = "Welcome to Flask-Todo app"
return render_template('index.html', greet=greet)
The render_template function takes the name of the HTML file and any variables that need to be passed to the template. In this case, it passes a variable called "greet" with the value "Welcome to Flask-Todo app".
So, when the user visits the root URL of this web application, Flask will render the "index.html" template and pass it the "greet" variable, which will be displayed in the template.
Now let’s add a Python script which is Jinja into index.html
<body>
<!-- your content here... -->
<h2>This is index page</h2>
<h2>{{ greet }}</h2>
<div class="container">
The double curly braces "{{ greet }}" inside an HTML file is a Jinja template expression. Jinja is a templating language that is commonly used with the Flask web framework.
The expression "{{ greet }}" is a placeholder that will be replaced by the value of the "greet" variable that is passed to the HTML template using the Flask’s render_template() function.
In the Python code snippet you provided, the "greet" variable is assigned the value "Welcome to Flask-Todo app" and is passed to the "index.html" template using the render_template() function.
So, when the HTML page is rendered in the user’s browser, the Jinja template engine will replace the "{{ greet }}" expression with the value of the "greet" variable, which is "Welcome to Flask-Todo app". This will result in the string "Welcome to Flask-Todo app" being displayed in the HTML page.
^ refer Screenshot-load-jinja-render-template.png
Initialize-database
# database.py
import sqlite3 as lite
from datetime import date
def db_connection():
conn = lite.connect('todo.db')
conn.row_factory = lite.Row
return conn
con = db_connection()
def table_todo():
con.execute('''
CREATE TABLE IF NOT EXISTS Todo
(id INTEGER PRIMARY KEY,
task TEXT NOT NULL,
complete INTEGER NOT NULL DEFAULT 0,
date_created at DATE);
''')
print("Todo table created")
def dummy_data(my_data):
insert_query = "INSERT INTO Todo VALUES (?, ?, ?, ?)"
cur = con.executemany(insert_query, my_data)
con.commit()
print("Completed insert all dummy data...")
today = date.today()
my_data = [
(1, 'Read comic', 0, today),
(2, 'Cook dinner', 0, today),
(3, 'Go to laundry', 0, today),
(4, 'Watch movie', 0, today)]
def main():
db_connection()
table_todo()
dummy_data(my_data)
if __name__ == '__main__':
main()
init_db.py
Above script init_db.py
is a Python code that creates a SQLite database and a table named Todo. The Todo table has four columns: id, task, complete, and date_created. The code also inserts some dummy data into the Todo table.
The code imports the sqlite3 and datetime modules. The sqlite3 module provides the functionality to connect to and interact with SQLite databases. The datetime module provides classes to work with dates and times.
The db_connection() function creates a connection object to the SQLite database named ‘todo.db’. The function also sets the row_factory attribute to lite.Row to retrieve rows as dictionaries instead of tuples. The function returns the connection object.
The table_todo() function creates the Todo table with four columns. The id column is an integer primary key. The task column is a non-null text field. The complete column is a non-null integer field with a default value of 0. The date_created column is a date field. If the table does not exist, the function creates the table. The function prints a message to indicate that the Todo table is created.
The dummy_data() function inserts the dummy data into the Todo table using the connection object. The function takes a list of tuples as an argument. Each tuple represents a row to be inserted into the Todo table. The function uses the executemany() method to insert all the rows in a single transaction. The function prints a message to indicate that the dummy data is inserted.
The code defines a list named my_data that contains four tuples. Each tuple represents a row to be inserted into the Todo table. The first value of each tuple is the id, the second value is the task, the third value is the complete status, and the fourth value is the date_created.
The main() function calls the db_connection(), table_todo(), and dummy_data() functions to create the Todo table and insert the dummy data. The if name == ‘main‘ statement calls the main() function when the script is executed directly.
In our root folder you can see a file named as todo.db
. If run this using the SQLite3 terminal can be seen a table and it has 4 line of rows stored inside table todo
Display-database-in-browser-flask
from flask import Flask, render_template
from init_db import db_connection
app = Flask(__name__)
@app.route('/')
def index():
greet = "Welcome to Flask-Todo app"
con = db_connection()
c = con.cursor()
tasks = c.execute("SELECT * FROM Todo").fetchall()
c.close()
return render_template("index.html", greet=greet, tasks=tasks)
- "from init_db import db_connection" will import module that created named init_db.py.
This code defines a Flask route for the root URL / and maps it to the index() function.
Inside the function, there are a few things happening:
A string variable greet is defined and set to "Welcome to Flask-Todo app".
The db_connection() function is called to create a connection to a database. Presumably, this function is defined elsewhere in the codebase.
A cursor object c is created from the database connection, and a SQL query is executed on the Todo table to fetch all records. The fetchall() method returns all the rows as a list of tuples.
The cursor is closed to free up any resources being used by the database connection.
Finally, the render_template() function is called with the name of the HTML template file as the first argument, and two keyword arguments welcome and tasks. The welcome variable contains the string "Welcome to Flask-Todo app", and tasks variable contains the list of tuples returned by the SQL query. The template file will be rendered with these variables available for use in the HTML.
In summary, this code fetches all records from a Todo table in a database, passes the records to an HTML template, and renders the template with the records displayed on the webpage.
<table>
<thead>
<tr>
<th>No.</th>
<th>Task</th>
<th>Edit</th>
<th>Delete</th>
<th>Status</th>
</tr>
</thead>
{% for task in tasks %}
<tbody>
<tr>
<td> {{ loop.index }} </td>
<td> {{ task['task'] }} </td>
<td> <a href="#">Edit</a> </td>
<td> <a href="#">Delete</a> </td>
<td> {{ task['complete'] }} </td>
</tbody>
{% endfor %}
</table>
This is an HTML code that generates a table with five columns: No., Task, Edit, Delete, and Status. It also includes a loop that iterates through the tasks variable (presumably passed to the template from a Flask view), which is a list of dictionaries representing tasks.
Within the loop, each task is displayed as a new row in the table. The loop.index variable is used to display the index of the current iteration (starting from 1). The task[‘task’] and task[‘complete’] are used to display the name and status of the task, respectively. Finally, the Edit and Delete links are provided to allow the user to perform those actions on each task.
Overall, this code generates an HTML table with dynamic data populated by a list of tasks, allowing the user to view and interact with the tasks as needed.
Create-insert-input-database-in-flask
Back to app.py
to add insert or create new input or new task.
@app.route("/add", methods=("POST", "GET"))
def add():
if request.method == "POST":
task = request.form["task"]
today = date.today()
data = (task, today)
con = db_connection()
c = con.cursor()
sql = "INSERT INTO Todo (task, date_created) VALUES (?, ?)"
c.execute(sql, data)
con.commit()
c.close()
return redirect(url_for('index'))
Above code defines a route /add in a Flask application, which can handle both GET and POST requests.
When a user submits a form via a POST request to the /add route, the add() function is executed.
The function first checks if the request method is POST using the if request.method == "POST" statement. If it is, the function extracts the value of the task field from the submitted form using request.form["task"].
It then creates a tuple called data containing the extracted task value and the current date obtained using the date.today() method.
The code then establishes a connection to a SQLite database using the db_connection() function (which is not shown in this code snippet), creates a cursor object using the con.cursor() method, and executes an SQL statement to insert the task and date_created values into a table called Todo. The SQL statement uses parameterized queries to avoid SQL injection attacks.
Finally, the code commits the changes to the database using con.commit(), closes the cursor using c.close(), and redirects the user back to the index page using return redirect(url_for(‘index’)).
In summary, this code receives a POST request with a task value, saves it to a database table with a timestamp, and then redirects to another page.
Next let’s create form to enable input index.html
with submit button.
<form action="/add" method="POST" autocomplete="off">
<table>
<tr>
<td>Task: </td>
<td>
<input type="text" name="task">
</td>
<td>
<input type="submit" value="Submit">
</td>
</tr>
</table>
</form>
This is an HTML code for a form that allows a user to submit a task. Here is a breakdown of the code:
The form tag defines a form that is used to collect data from the user. The action attribute is set to "/add" which specifies the URL to which the data will be submitted when the user submits the form. The method attribute is set to "POST" which means that the form data will be sent to the server using the HTTP POST method.
The autocomplete attribute is set to "off" which disables autocomplete for the form.
Inside the form tag, there is a table tag that contains a single row with three columns.
The first column contains the text "Task:" which serves as a label for the input field in the second column.
The second column contains an input field of type "text" with the name attribute set to "task". This is where the user can enter the task they want to submit.
The third column contains an input field of type "submit" with the value "Submit". This is a button that the user can click to submit the form and send the task to the server.
In summary, this code creates a simple form that collects a single piece of data (a task) from the user and sends it to a server for processing.
Let’s review in the browser.
^ refer ‘Screenshot-insert-task-todo.png’
Website-template-inheritance
We have done with two out of four CRUDs operation which is Create and Review. Next we are going to code for Update but before that I would like to do a bit of templating or web template inheritance using Flask.
In index.html all the styling are included inside in one file. What if we need more separate pages to display other function. Do we need to copy all over again and again?
It is going to be a trouble if we copy as this will lead or prone to error.
This is web template inheritance become useful and avoid the repetitive works and this concept is called as Don't-Repeat-Yourself
an accronym as DRY
This will involve 3 different files which is:
- index.html
- base.html
- main.css (in
static/css
folder) - app.py
Styling-css-separation
Let’s cut style tag in head tag and paste to our new created main.css inside main/css subfolder.
<style>
h2, h3 {
text-align: center;
}
h4 {
text-decoration: line-through red;
}
table {
margin-left: auto;
margin-right: auto;
}
.container {
padding: 20px;
width: 800px;
background-color: #eee;
margin-left: auto;
margin-right: auto;
}
</style>
As you can see all the style is in specific or separate file name main.css
. Now a little bit of removing both open and closing style tags.
h2, h3 {
text-align: center;
}
h4 {
text-decoration: line-through red;
}
table {
margin-left: auto;
margin-right: auto;
}
.container {
padding: 20px;
width: 800px;
background-color: #eee;
margin-left: auto;
margin-right: auto;
}
main.css
To enable load style from main.css
need to import url_for
function from Flask
module in app.py
after render_template
function.
from flask import Flask, render_template, url_for
app = Flask(__name__)
# here code omitted...
It hasn’t done yet.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flask CRUD-App with SQLite3</title>
<link rel="stylesheet" type="text/css" href="{{url_for('static', filename='css/main.css')}}">
</head>
index.html
In index.html
add in href
that using the Jinja url_for
function connect to static
and css
to main.css
.
Let see if it is working. Load or refresh browser. Is there an error to debug?
Nope. It works fine.
Now, duplicate index.html
and rename the copy as base.html
. Code edit base.html
as below.
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Flask Todo</title>
<link rel="stylesheet" type="text/css" href="{{url_for('static', filename='css/main.css')}}">
{% block head %}
{% endblock %}
</head>
<body>
<h2> Flask-Todo app with SQLite3(CRUD) </h2>
{% block body %}
{% endblock %}
</body>
</html>
base.html
As you can see there are two blocks of Jinja
code which is head
and body
that wrap by curly braces. Each block has and open and end.
This is for the head
block
{% block head %}
{% endblock %}
while this is body
block
{% block head %}
{% endblock %}
You might be wondering what happened to remain of the code? The remain of the code stay at index.html
between both jinja
block of head
and body
.
This appears to be a Jinja2 template used in a Flask web application.
The first line {% extends ‘base.html’ %} indicates that this template is extending a base HTML template named base.html.
The next two lines {% block head %} and {% endblock %} define an empty block named "head" that can be overridden in the base template.
The next block of code, {% block body %} and {% endblock %}, defines a block named "body" that will contain the main content of the page.
Inside the "body" block, there is an HTML form element that allows the user to submit a new task. The form action is set to "/add" and the method is set to "POST". The autocomplete attribute is set to "off" to prevent the browser from suggesting previously entered values.
The form contains a table element with three columns: "Task", an input field for the task description, and a "Submit" button.
Below the form is another table element that displays a list of existing tasks. The table has five columns: "No." (which indicates the task number), "Task" (which displays the task description), "Edit" and "Delete" (which are links to edit and delete the task), and "Status" (which displays the completion status of the task).
The for loop {% for task in tasks %} is used to iterate over a list of tasks and display each one in a row of the table. The tasks variable is assumed to be passed to the template context from the Flask view. Inside the loop, the loop.index variable is used to display the task number, and task[‘task’] and task[‘complete’] are used to display the task description and completion status, respectively.
Finally, the code closes the "body" block with {% endblock %}.
Update-data-Flask-app
Ok, it is time continue what we have left after Create
and Review
for CRUD
operation. Update
is user can edit or change the task in this app. Proceed to app.py
and add another route to update
function.
@app.route("/edit/<id>", methods=('GET', 'POST'))
def edit(id):
con = db_connection()
c = con.cursor()
c.execute('SELECT * FROM Todo WHERE id=?', (id, ))
data = c.fetchone()
if request.method == 'POST':
id = request.form['id']
task = request.form['task']
c.execute('UPDATE Todo SET task=? WHERE id=?', (task, id))
con.commit()
c.close()
return redirect(url_for('index'))
else:
return render_template("edit.html", data=data)
This code snippet is defining a Flask route using the @app.route decorator. The route is defined as "/edit/
When a client makes a request to this route, the function edit is called. This function first establishes a connection to a database using the db_connection() function, and creates a cursor object to execute SQL queries on the database.
Next, the function executes a SQL query to select a record from the Todo table with the specified id. The query uses a parameterized query with a tuple containing the id parameter, to prevent SQL injection attacks.
If the request method is POST, it means that the user has submitted a form to update the record with the specified id. The function retrieves the id and task parameters from the form using request.form, and executes a SQL query to update the task field of the record with the specified id. The changes are then committed to the database using con.commit(), and the cursor is closed.
Finally, the function redirects the user to the index route using redirect(url_for(‘index’)).
If the request method is not POST, it means that the user is accessing the route to view the form for updating the record. The function retrieves the record data from the database, and passes it to the edit.html template using render_template(). The template can then display the data in a form for the user to edit.
In last line return render_template("edit.html", data=data)
will render template edit.html
and pass the assign task to variable name data
. But before that let’s code on the edit
hyperlink at index.html
to get the id
that will be pass to edit.html
.
<tbody>
<tr>
<td> {{ loop.index }} </td>
<td> {{ task['task'] }} </td>
<td> <a href="/edit/{{ task['id'] }}">Edit</a> </td>
<td> <a href="#">Delete</a> </td>
<td> {{ task['complete'] }} </td>
</tbody>
Above edit
hyperlink link to task id
.
This line of code is written in HTML and uses Flask’s templating language called Jinja. Let’s break it down:
Task need to script that will underline stroke if the button complete
click and it will turn boolean value from default that set to 0
which is pending
and turn to 1
that represents complete
Last part is to change value of the button using Jinja.
Reference-link
Here is the reference link:
Learn Flask for Python – Full Tutorial
How To Use Many-to-Many Database Relationships with Flask and SQLite