Following along with this tutorial on YouTube, this log file was created to document progress on learning the Flask framework for backend web development in Python. In addition, I find myself referring to the official Flask documentation time and time again. To get started with Flask as soon as possible, the quick start page is as descriptive as it can be for a mere introduction to this micro-framework.
Initialisation
- Create a new directory to house the project.
-
cd
into the project dir. - Type
virtualenv env
into the terminal to create a new virtual environment with the nameenv
. This name can be anything butenv
is the convention. You should now see a new folder with the name you'd specified. - Activate the venv using
env\Scripts\activate.ps1
on powershell orenv\Scripts\activate.bat
on cmd. You'll see the terminal prompt change to indicate you're in a venv. - You can verify whether the venv is active by running
pip -V
and checking the path to the pip in use.
Installing dependencies
- Install dependencies.
pip install flask flask-sqlalchemy
to install flask and it's dependencies in this venv.
Deploying app
- Create a new file named
app.py
and run it usingpy app.py
to deploy the app on your localhost.
Adding routes
- Create folders named
static
andtemplates
. - Create
index.html
insidetemplates
. - Now, instead of returning a string in our app, we'll
return render_template(index.html)
. - Create boilerplate html code in
index.html
and refresh the project.
Using template inheritance
- Create a master html file to inherit from.
- Inherit that file in
index.html
.
Adding css
- Make a css folder.
- Create a
main.css
file with some basic rule sets. - Link
main.css
to the master html file.
Adding database functionality
- Import
sqlalchemy
- Configure sqlite database
- Initialise database
- Create a database model using a class
- Ensure that an
__repr__
string is implemented through the class - Setup database using the py interpreter in the terminal
Build Task Master app
- Make separate
base.html
,index.html
,main.css
andapp.py
files for the project - Write
index.html
- Create a div
- Add header and table
- Add links to the static row
- Add a form at the end after table
- Create a div
- Edit
main.css
to format table - Write more logic in
app.py
- Creation of tasks
- Deletion of tasks
- Updating tasks
- Create a separate dynamic page to edit tasks
Wrapping up
- Complete documentation
Use these notes by reading the bit that corresponds to the step you're on in the checklist.
- All the packages needed for the project are located within this directory which makes the project more portable in a collaborative setting.
- Requirements are installed into the working directory directly.
- Virtual environments are the standard when it comes to collaborative projects due to the easy package management.
- Two folders needed to be created. One named static and the other, templates.
- In the templates folder, we create a file named
index.html
. - By creating this file, we can now return a render of the html file it in our app when defining the route.
- When specifying the name of the file to render, we need not specify the full path since Flask knows to look in the templates folder.
- This is achieved by creating a master html page that is inherited into every other page so as to cut redundant code.
- For this, create a new file in templates named
base.html
. This will be our skeleton. - In this page, use Jinja2 syntax to create a block like so.
{% block head/body %}
{% endblock %}
- Jinja2 is the template engine that Flask uses. We create a block in the code that we'll utilise to insert our code into on all the other pages when we inherit this template.
- The
base.html
file uses one block for the head and another for the body. - Now, to inherit
base.html
, changeindex.html
to be,
<!-- Inherit from base.html -->
{% extends 'base.html' %}
{% block head %}
<!-- Code for the head goes here -->
{% endblock %}
{% block body %}
<!-- Code for the body goes here -->
{% endblock %}
- In the static folder, make a new folder named css and create a file named
main.css
. - Put some basic rule sets in the
main.css
file for example,body { margin: 0; font-family: sans-serif; }
- This stylesheet now needs to be linked in the
base.html
master html file. However, the css file cannot directly be linked as a path. We gotta make use of more Jinja2 syntax. We link the stylesheet by doing the following,- Note:
url_for
no longer needs to be imported from Flask to be able to link themain.css
file. It just becomes redundant. For academic purposes, importing the functionurl_for()
from flask in ourapp.py
looks like,from flask import flask, render_template, url_for
- Link the stylesheet using
<!-- Notice the use of single quotes within the double quotes --> <link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
{{}}
, this makes sure that the return type of the arguments is a string. This is why it's used to slot in the path of a file in string form. - Note:
- The same applies if you were trying to link a JavaScript file. It would be
filename='js/main.js'
instead.
- Import SQLAlchemy into the python app. The import statements now are,
from flask import Flask, render_template, url_for from flask_sqlalchemy import SQLAlchemy
- We configure the app to check for a database that's using sqlite. We reference this using a relative path. This is what the code looks like,
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
- Then, initialising the database,
db = SQLAlchemy(app)
-
Make a class to instantiate a database. Should look something like this,
class Model(db.model): id = db.Column(db.Integer, primary_key=True) # Making sure that the user doesn't leave this field empty content = db.Column(db.String(200), nullable=False) # Make sure to include the datetime library # datetime.utcnow returns the current time date_created = db.Column(db.DateTime, default=datetime.utcnow)
-
Each field of the class is a new attribute in the database.
-
Importing datetime looks like,
from datetime import datetime
-
In addition, define a function to return a string every time a new element is created. Like so,
# Creating a repr function to represent each task (object) as a string def __repr__(self): retString = 'Task #{}'.format(self.id) return retString
-
In Python,
__repr__
is a special method used to represent a class’s objects as a string.__repr__
is called by the repr() built-in function. You can define your own string representation of your class objects using the__repr__
method. -
This blog post is a good resource to learn about the
__repr__
method in Python.
- Head to the terminal. Make sure your env is activated.
- Start an interactive Python shell. Type in
python
orpy
to start up an interpreter prompt. - To create the database, type in,
1. from app import db 2. db.create_all()
- The python script needs to be named
app.py
since we've initialised using the scripts name. - On running the second command, a new
__pycache__
folder will be created along with our database element. - You no longer need the terminal since the database has been setup following the above steps.
- Write a new
index.html
file with a header and a table. - This is the basic structure of the table we're creating.
<div class="content"> <h1>Task Master</h1> <table> <!-- Making columns --> <tr> <th>Task</th> <th>Added</th> <th>Actions</th> </tr> <tr> <!-- Create two empty rows --> <td></td> <td></td> <td> <!-- Create two links --> <a href="">Delete</a> <br> <a href="">Update</a> </td> </tr> </table> </div>
- Edit the
main.css
file to add,To make sure that the table is displayed with a border.table, th, td { border: 1px solid black; border-collapse: collapse; }
- Heading to
app.py
, we need to add another parameter namedmethods
to the@app.route()
. By default, the route is able to'GET'
but unable to'POST'
. We need to set it to perform both operations. The newapp.route()
header becomes,We can now post through this route to send data to our database.@app.route('/', methods=['POST', 'GET'])
- Now, adding a form to our table, the action takes place in this route, i.e.,
'/'
and the method would be'POST'
. That looks something like this,This form takes two inputs, one is the actual content and the second is a submit. The form ends up like this,<form action="/" method="POST"></form>
<form action="/" method="POST"> <input type="text" name="content" id="content"> <input type="submit" value="Add task"> </form>
- For now, the second row of the table isn't updated dynamically. That's going to change soon.
- Editing the
app.py
script, we add the logic to push data to our database. We first importrequests
from Flask. - The import statement changes to,
from flask import Flask, render_template, request
- The
index()
function changes to,@app.route('/', methods=['POST', 'GET']) def index(): if request.method == 'POST': # Does virtually nothing pass else: return render_template('index.html')
- Create a request variable and passing in the id of the input from the form. Then use that request variable to create a new object of the database model.
- This object becomes our task which we will then push to the database.
- Check if the button was pressed on the site, i.e., the method employed is the post method.
if request.method == 'POST':
- Extracting the data from the form in the table. The
content
is in reference to thename
of the input field and notid
,taskContent = request.form['content']
- Creating a new task with the extracted data
newTask = Model(content=taskContent)
- Updating the database
We write
# Pushing the new task created to the database try: # Look familiar? This code pushes data to our database db.session.add(newTask) db.session.commit() # Remember to import redirect back to the index page return redirect('/') except: # This should never fail ideally return 'There was an issue adding your task to the database.'
return redirect('/')
so that the index page is displayed after updating the database. Also, update the import statement to includeredirect
like so,from flask import Flask, render_template, request, redirect
- If the submit button is never pressed, this means that the method employed is the get method. We're just reading the page and nothing else. This method is called by default.
- When we're using the get method, we just sort all the tasks by the most recently created and displaying them.
else: # This looks at all the database contents and returns them in the order of their creation tasks = Model.query.order_by(Model.dateCreated).all() # Passing the tasks variable into the render_template() function return render_template('index.html', tasks=tasks)
- If instead, you want to display the most recent task created, use,
tasks = Model.query.order_by(Model.dateCreated).first()
# If the submit button on the form is pressed
if request.method == 'POST':
# Pass in the id of the input to extract
taskContent = request.form['content']
# Create an object of the table having content as the extracted content above
newTask = Model(content=taskContent)
# Pushing the new task created to the database
try:
# Look familiar? This code pushes data to our database
db.session.add(newTask)
db.session.commit()
# Remember to import redirect back to the index page
return redirect('/')
except:
# This should never fail ideally
return 'There was an issue adding your task to the database.'
else:
# This looks at all the database contents and returns them in the order of their creation
tasks = Model.query.order_by(Model.dateCreated).all()
# tasks = Model.query.order_by(Model.dateCreated).first()
# Passing the tasks variable into the render_template() function
return render_template('index.html', tasks=tasks)
-
Adding Jinja2 syntax to our table in
index.html
to show a row for each task in the database. We use a for loop to iterate through the tasks in the task list. This task list comes from the above code passingtasks
torender_template()
. -
The updated row dynamically becomes,
{% for task in tasks %} <tr> <!-- First column for content of the task --> <td>{{ task.content }}</td> <!-- Second column for the date created --> <!-- date() ensures that it's in the intended format --> <td>{{ task.dateCreated.date() }}</td> <td> <!-- Create a link --> <a href="">Delete</a> <br> <a href="">Update</a> </td> </tr> {% endfor %}
-
It was at this point that the
task.content
field refers to thename
of the input and not theid
.
- We query the database on the
id
field and not the name field like we did before. This is because theid
field is our primary key. - We set the routes for these pages to be dynamic according to the
id
of the task. - The route then becomes,
# By default, the method list consists of 'GET' @app.route('/delete/<int:id>')
- Defining the delete function right after as,
def delete(id): # More on this after taskToDelete = Model.query.get_or_404(id) try: db.session.delete(taskToDelete) db.session.commit() return redirect('/') except: return 'There was an issue deleting that task.'
- Here,
queries the database to find the task with the passed
taskToDelete = Model.query.get_or_404(id)
id
or to return a404
error if not found. The rest of the function's code has been discussed before and an explanation would be redundant.
- The idea is to make a separate page where there's a form to update a certain task. This form would look very similar to the form at the home page.
- Changes to
app.py
are as follows,- Added the route for the page to update the files.
# Functionality to update tasks @app.route('/update/<int:id>', methods=['GET', 'POST'])
- Defining the function to update tasks.
# Passing in id as a parameter def update(id): # Querying for the task to update taskToUpdate = Model.query.get_or_404(id) # If a response was logged from the form if request.method == 'POST': # Extract from the form taskToUpdate.content = request.form['content'] # Edit database try: db.session.commit() return redirect('/') except: return 'There was an issue updating that task.' # Just display the update.html page if there isn't a response else: return render_template('update.html', task=taskToUpdate)
- Added the route for the page to update the files.
- Created a new file named
update.html
which inherits frombase.html
.{% extends 'base.html' %} {% block head %} <title>Update Task</title> {% endblock %} {% block body %} <div class="content"> <h1>Update Task</h1> <!-- Creating a form where the link is dynamically updated according to task.id --> <form action="/update/{{ task.id }}" method="POST"> <input type="text" name="content" id="content" value="{{ task.content }}"> <input type="submit" value="Update"> </form> </div> {% endblock %}
That wraps up the documentation for this extremely short and simple project in flask.