Flask is a WSGI web application framework that is lightweight. It’s built to get started simple and quick, with the flexibility to scale up to more sophisticated projects. It started as a basic wrapper for Werkzeug and Jinja and has since grown into one of the most popular Python web application frameworks.
Flask makes recommendations but does not impose any dependencies or project structure. The developer is free to use whatever tools and libraries they wish. The community has created several extensions that make adding additional functionality simple.
Installing Flask in Ubuntu
Use the below command to install it on Ubuntu.
pip install -U Flask
Flask is excellent for static content online applications, although it can also manage complicated web applications (Reddit and Airbnb). However, it may be more difficult to keep everything structured since there is more freedom regarding the tools and libraries utilized; it is also more difficult to keep everything structured.
Features of Flask
- Microframework with a low weight.
- The Jinja2 format motor is used in Flask.
- Although it lacks a tool to manage administration chores, it does offer a Flask-Admin extension.
- Developers can investigate the application’s core while maintaining control.
- Many libraries and extensions (such as SQLAlchemy, PyMongo, and PonyORM) are available, and extensions (such as Flask-Peewee, Flask-Pony, and Flask-Alembic).
- It gives the developer more freedom over the components he or she wants to use.
- Flask lacks such a tool for handling administrative tasks.
- Flask provides developers with a lot of flexibility in building and adding features to simple apps.
- It’s a lot easier to learn.
- Each project can be a single application with numerous models and views, while the single application can have many models and views.
- If you’re new to web programming, Flask is a good place to start. Many websites are built with Flask and receive a lot of traffic. However, it does not match those developed using Django.
- A simple application can be updated later with Flask to include more features and make it more complicated. It gives you the freedom to swiftly grow your application.
- Developers are free to utilize any plugins or libraries they choose, and they can create functionalities in various ways.
- Flask is a straightforward framework that lets developers select how their applications should look.
- Simple applications are simple to create and do not necessitate a lot of coding.
- Flask is more open-ended, so developers can choose to follow best practices or not.
- For a straightforward operation, a Flask application takes far fewer lines of code.
The framework’s gist is that if a third-party library can do a given function, Flask does not have that function or library pre-installed. As a result, Flask lacks a database abstraction layer, form validation, and other common features. When Flask was first formed, Armin Ronacher was a member of Pocoo, an international network of Python enthusiasts.
Deny.py, the first version of Flask, was created as an April Fool’s joke. It was a single file that didn’t require any installation or configuration. The fact that the project gained traction, on the other hand, demonstrated that a framework with few to no dependencies could be useful. Hence Flask emerged.
What are the benefits of using Flask?
In the Python Developers Survey in 2018, Flask was voted the most popular web framework among all Python frameworks. Then there’s the matter of what makes Flask so excellent compared to Django.
Flask is a lightweight web application framework that allows you to get results quickly while still allowing you to expand the program in the future. Flask ensures that your project’s code only contains what developers put in it, with no extra code responsible for features you don’t use.
This framework is popular among developers because of its simplicity, which results in clear, concise code. Active communities (such as those on Facebook and Reddit) and comprehensive documentation make it easier to obtain relevant information, appropriate extensions, and simple solutions to problems. Others like Flask because it allows them to choose from various solutions or even design their own. For experienced developers who prefer the increased flexibility, not needing to follow a precise convention could speed up the development process.
Easy
Flask is self-evidently simple to comprehend and use because it is written in Python. However, the great part is that it can get even easier. The purpose of Flask is to be simple. As a result, the framework itself makes it simple for developers to explore and create online apps.
Flexible
Because Flask is designed to be extensible, developers have more freedom to create their website or application precisely how they want it. Every aspect of your settings and application can be changed, allowing for complete customization.
Small Flask is quite light. Smaller frameworks with similar features typically run faster and perform better than larger frameworks. Because of its simplistic style, it’s ideal for creating a minimum viable product (MVP).
Test-Driven
Flask was created with testing in mind, according to Ronacher. Flask allows developers to write unit tests for their applications using a pytest fixture named client, based on the precept “anything that is untested is broken.” Flask also includes a quick debugger.
When is it appropriate to use Flask?
When you use Flask’s flexibility and customization features to their full potential, it works best. If you want to postpone some technology decisions, Flask, rather than Django, is a good choice. Flask can also be used to create small Web apps with minimal functionality.
Flask is a back-end web development framework. As a result, Flask should be used to create server-side apps.
The ability to create from the ground up is Flask’s most compelling feature. The modularity that follows facilitates development and testing.
Reduced development time and faster run times are just a few of the advantages that a micro framework like Flask may provide.
What Businesses use Flask?
Knowing what kind of businesses use Flask may make the decision between Django and Flask a little easier.
Netflix
You presumably already know what Netflix is, but in case you don’t, here’s a quick rundown. Netflix is a video-on-demand service that provides customers with access to many TV episodes and films. Netflix relies on Flask APIs for a lot of its more analytical and infrastructure work.
Reddit is a little more difficult to explain. It’s a website that hosts thousands of communities for online strangers to connect and converse in. The concept is basic enough for Flask to be useful while being broad enough for Flask to make sense.
Lyft
Lyft is a ride-sharing app that gets passengers from point A to point B in minutes by connecting them with available drivers. Lyft’s tech stack includes Flask, which is used to implement its microservices architecture.
Microservices is a software architecture that consists of a collection of small services that work together to produce a more complicated, single app.
Flask’s disadvantages
Even though Flask has almost every Django-like security precaution available as a downloadable extension, using third-party extensions carries more risk than monolithic Django. All security features are already present in Django. Every third-party extension in Flask is built by several teams, making updates more difficult and increasing the developer’s upkeep time.
How to build a blog Web App in Flask
We have strategically designed this hands-on application to bring together all the theoretical concepts discussed in this article. By completing this demo, you will have mastered the basics of creating and running a Flask application.
You’ll utilize the Bootstrap toolkit to style your app to make it more aesthetically appealing. Bootstrap makes it easy to include responsive web pages in your online application so that it works effectively on mobile browsers without having to write your HTML, CSS, or JavaScript code. You may concentrate on learning how Flask interacts by using the toolbox.
Flask uses the Jinja template engine to dynamically generate HTML pages using Python concepts like variables, loops, and lists. These templates will be used in this project.
In this article, you’ll learn how to create a tiny web blog in Python 3 using Flask and SQLite. Users of the application can see all of the posts in your database and click on a post’s title to see its contents and add new posts to the database, edit existing posts, and delete them.
Prerequisites
You will require the following items before beginning to follow this guide:
- Python
If you have not setup up Python on your machine, you can do so by referring to this article. Or by running the following commands in Ubuntu.
sudo apt update sudo apt install software-properties-common sudo add-apt-repository ppa:deadsnakes/ppa sudo apt update sudo apt install python3.8
To confirm that Python is successfully installed on your system, run the following command on the terminal.
python ––version
- Virtual environment
A Python virtual environment is a directory tree that contains a Python installation and many additional packages.
Python virtual environments are used to establish an isolated environment for various Python projects. It allows you to install a specific version of a module per project without worrying about interfering with your other Python projects.
Install the virtual environment on your Debian Operating system by running the following command on the terminal.
sudo apt install python3-venv
tuts@codeunderscored:~/Documents/codeunderscored_blog$ sudo pip3 install virtualenv
or
pip3 install virtualenv
We’ll call our project directory codeunderscored_blog in this example. Then, we will create our virtual environment inside the project directory by running the following command.
tuts@codeunderscored:~/Documents/codeunderscored_blog$ sudo virtualenv code_vm
To start using the new virtual environment, we need to activate it. In this case, you can accomplish this by running the following command.
tuts@codeunderscored:~/Documents/codeunderscored_blog$ source code_vm/bin/activate
When you get an image similar to the one above, you are certain that your virtual environment is activated and ready to use.
Data types, conditional statements, for loops, functions, and other Python 3 concepts should all be understood. If you’re new to Python, we suggest you brush on this area first before proceeding with this article. Having sorted that, it is time to get our hands dirty.
Installing Flask
You’ll use the pip package installer to activate your Python environment and install Flask in this step.
If you haven’t already done so, make sure you’re in your project directory (codeunderscored_blog) and run the following command to activate your programming environment:
pip install flask
When that is done, you can go right ahead and confirm that Flask is installed successfully as required by running the following command.
python -c "import flask; print(flask.__version__)"
In the above sequence of commands, to run Python code, use the python command-line interface with the -c option. The flask package is then imported with import flask, and the Flask version is printed using the flask.version variable.
The result will be a version number that looks like this:
Creating the Application
We’ll start utilizing Flask now that We’ve built up the development environment. In this stage, we’ll create a simple web application in a Python file and run it to launch the server, which will display some data in the browser.
Open a file titled app.py in your codeunderscored_blog directory and update it with nano, vim, vi, gedit, or your favorite text editor. This app.py file will show you how to process HTTP requests in Python. You’ll import the Flask object and write a function that returns an HTTP response inside of it.
vim app.py
Inside app.py, write the following code:
from flask import Flask app = Flask(name) @app.route('/') def index(): return 'Welcome, codeunderscored!'
You import the Flask object from the flask package in the preceding code block. Then, with the name app, you construct your Flask application instance. You send the name of the current Python module to the special variable name. It tells the instance where it is necessary because Flask creates some pathways behind the scenes.
You utilize the app instance to handle incoming web requests and respond to the user once it’s been created. @app.route is a decorator that changes an ordinary Python function into a Flask view function, translating the method’s return value into an HTTP response that a web browser can display. @app.route() is given the value ‘/’ to indicate that it will react to web requests for the URL /, which is the main URL.
As a response, the index() view method returns the string ‘Welcome, codeunderscored!’
The file can now be saved and closed.
To execute the web application, use the FLASK APP environment variable to tell Flask where to find it (the app.py file).
export FLASK_APP=app
Then, using the FLASK ENV environment option, execute it in development mode as shown below.
export FLASK_ENV=development
Finally, use the flask run command to launch the application:
flask run
There are various pieces of information in the preceding output, including:
- The name of the program we’re using.
- The context in which the application is being executed. In this case, it is the development environment.
- The Flask debugger is active when debug mode is set to on. When things go wrong, this is important since it gives us precise error messages, making debugging easier.
The program is accessible locally via http://127.0.0.1:5000/, where 127.0.0.1 is your machine’s localhost IP address and:5000 is the port number.
If we open a browser and put in http://127.0.0.1:5000/, the string Hello, codeunderscored! will appear. As a result, this indicates that the application is up and operating.
We’ve created a simple Flask web application. We’ve launched the program and seen the results in a web browser. The next section is an advancement to other things like using HTML files in the application to make it intuitive and help us build robust systems.
Using HTML templates
The application currently just shows a plain message with no HTML. Because online applications primarily employ HTML to present information to visitors, we’ll now focus on embedding HTML files into the app that can subsequently be viewed in a web browser.
The render_template() helper function in Flask allows us to leverage the Jinja template engine. Writing HTML code in .html files and utilizing logic in the HTML code will make handling HTML much easier. These HTML files (templates) will be used to create all of the application pages, including the main page, which will display the blog post page, the most recent blog posts, the page where the user may contribute a new post, etc.
In this stage, we’ll edit the already existing app.py file in the main Flask application.
We’ll make the only change to include the render_template() helper method, which allows us to render HTML template files from the templates folder we’re planning to make. A single view function will be included in the file, responsible for handling requests to the main / route. The updated app.py will now look as follows.
from flask import Flask, render_template app = Flask(__name__) @app.route('/') def index(): return render_template('index.html')
The index() view method returns the result of running render_template() with the input index.html, which instructs render_template() to look in the templates folder for a file named index.html.
Because both the folder and the file do not yet exist, running the application will error. Regardless, we’ll run it to familiarize ourselves with this common exception. Then we’ll repair it by creating the necessary folder and file.
Finally, we will save and close the document.
CTRL+C will stop the development server in the terminal that is running the app program. Subsequently, make sure to correctly supply the value for the FLASK APP environment variable before running the program as follows.
export FLASK_APP=app
flask run
The debugger page indicates that the index.html template was not found when opening the URL http://127.0.0.1:5000/ in the browser. The mainline in the code that caused the problem will be highlighted. It’s the line returning render_template(‘index.html’) in this situation.
The debugger will disclose further code if we click on the error line, giving us more context to help address the problem.
Create a templates directory inside the codeunderscored_blog directory to fix this error. Then, inside it, we create the index.html file and open it for editing. Further, inside index.html, add the proceeding HTML code:
mkdir templates
vim templates/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Codeunderscored Blog</title> </head> <body> <h1>Welcome to Codeunderscored Blog</h1> </body> </html>
Save the file and go back to http://127.0.0.1:5000/ in the browser, or refresh the page. The word “Welcome to Codeunderscored Blog” should be displayed in a <h1> tag at this time.
In addition to the templates folder, Flask web applications usually contain a static folder where the application’s static files, such as CSS files, JavaScript scripts, and pictures, are stored.
To add CSS to the application, generate a main.css style sheet file. To begin, we create a static directory within the main codeunderscored_blog directory:
mkdir static
Then, inside the static directory, establish a new directory named css for .css files. It is usually done to organize static files into specific folders; for example, images in the images (or img) directory, JavaScript files are usually placed in the js directory, and so on. The css directory will be created inside the static directory with the following command:
mkdir static/css
Then edit the cd_main.css file located in the css directory:
vim static/css/cd_main.css
Add the CSS rule below to the cd_main.css (cascading style sheets) as follows.
h1 { border: 3px #eee solid; color: green; text-align: center; padding: 12px; }
To <h1> tags, the CSS code adds a border, changes the color to green, centers the text, and adds a little padding. We then proceed to save and close the file. After that, edit the index.html template file and add the following code.
<head> <meta charset="UTF-8"> <link rel="stylesheet" href="{{ url_for('static', filename= 'css/cd_main.css') }}"> <title>codeunderscored Blog</title> </head>
so that the final look is as follows,
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="{{ url_for('static', filename= 'css/cd_main.css') }}"> <title>codeunderscored Blog</title> </head> <body> <h1>Welcome to Codeunderscored Blog</h1> </body> </html>
After refreshing the application’s index page, we’ll notice that the word ‘Welcome to Codeunderscored Blog’ is now green, centered, and surrounded by a border.
We can style the application and make it more appealing with our design by utilizing the CSS language. If you’re not a web designer or don’t know how to use CSS, you can utilize the Bootstrap toolkit, which includes simple components for styling your application. We’ll be using Bootstrap for this demo.
You might have predicted that creating another HTML template would entail copying and pasting most of the HTML code from the index.html template. With a base template file, which all of the HTML files will inherit from, you may eliminate excessive code repetition. For further details, see Jinja Template Inheritance.
To generate a base template, we create the following file in the templates directory: base.html
tuts@codeunderscored:~/Documents/codeunderscored_blog$ sudo vim nano templates/base.html
After that, we add the following code in the base.html template
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous"> <title>{% block title %} {% endblock %}</title> </head> <body> <nav class="navbar navbar-expand-md navbar-light bg-light"> <a class="navbar-brand" href="{{ url_for('index')}}">codeunderscored Blog</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="{{url_for('create')}}">Create Post</a> </li> </ul> </div> </nav> <div class="container"> {% for message in get_flashed_messages() %} <div class="alert alert-danger">{{ message }}</div> {% endfor %} {% block content %} {% endblock %} </div> <!-- JavaScript Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script> </body> </html>
Once we’ve finished altering the file, we save and close it.
The majority of the code in the preceding block is normal HTML and Bootstrap code. The <meta> tags offer information to the web browser, the <link> tag refers to the Bootstrap CSS files, and the <script> tags are pointers to JavaScript code that enables extra Bootstrap capabilities; for more detail, see the Bootstrap documentation.
The following highlighted parts, on the other hand, are unique to the Jinja template engine:
{% block title %} {% endblock %}:
A block that serves as a placeholder for a title; we’ll use it in other templates to give a custom title to each page in the application without rewriting the complete section each time.
{{ url_for('index')}}:
The above syntax refers to a function that returns the index() view function’s URL. It is different from the previous url_for() call made to connect a static CSS file since it only takes one argument, the name of the view function, and instead of linking to a static file, it connects to the route associated with the function.
{% block content %} {% endblock %}:
This block will be replaced by the content dependent on the child template that will override it (templates that inherit from base.html).
Now that we have a base template, we can use inheritance to benefit from it. We will open the index.html file and replace its contents with the following.
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Welcome to Codeunderscored Blog {% endblock %}</h1> {% endblock %}
To inherit from the base.html template, we use the percent extends tag in this new version of the index.html template. Then we extend it by replacing the content block in the base template with the content block in the code block before it.
This content block contains a <h1> tag inside a title block with the text Welcome to Codeunderscored Blog, which replaces the original title block with the text Welcome to Codeunderscored Blog in the base.html template. This way, we can avoid repeating the same content because it serves as both a page title and a heading that shows below the base template’s navigation bar.
Template inheritance also allows you to reuse HTML code from other templates (in this case, base.html) without having to repeat it each time it is needed.
Save and close the file, then reload the browser’s index page. The page will now have a navigation bar and a customized title.
In Flask, we’ve used HTML templates and static files. We also utilized Bootstrap and a base template to start refining the design of the page and reduce code repetition. The next step is to create a database to hold the application’s data.
Configure the Database
In this phase, we’ll create a database to hold data, such as the application’s blog articles. We’ll also add a few sample entries to the database.
Because the sqlite3 module, which we’ll need to connect with the database, is widely available in the standard Python library, we’ll need an SQLite database file to store the data.
First, because SQLite stores data in tables and columns, and since the data is mostly made up of blog posts, we’ll need to create a table called codeunderscored_posts with the required columns. We’ll make a .sql file with SQL statements for creating the codeunderscored_posts table, which will have a few columns. This file will then be used to generate the database.
Open the following file in the codeunderscored_blog directory: db.sql
vim db.sql
Inside this file, type the following SQL commands:
DROP TABLE IF EXISTS codeunderscored_posts; CREATE TABLE codeunderscored_posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, title TEXT NOT NULL, content TEXT NOT NULL );
The file can then be saved and closed.
The first SQL command is DROP TABLE IF EXISTS codeunderscored_posts;, which deletes any existing tables with the name codeunderscored_posts to avoid confusion. When we apply these SQL commands, they will erase all of the content available in the database, so don’t write any essential content in the web application until the end of this article and try with the outcome. The next step is to use CREATE TABLE codeunderscored_posts to create a codeunderscored_posts table with the following columns:
- id: An integer indicates a primary key; the database will assign this as a unique value for each record.
- cd_created: The date and time when the blog entry was written. The DEFAULT value is the CURRENT TIMESTAMP value, which is when the post was added to the database. NOT NULL indicates that this column should not be empty, and the DEFAULT value is the CURRENT TIMESTAMP value, which is when the post was added to the database. We don’t need to specify a value for this column because it will automatically fill in, much like the id column.
- cd_title: the title of the post and is of type text and does not accept empty values.
- cd_content: The substance of the post is also of the type text and does not accept empty values.
We’ll use the SQL schema in the db.sql file to create the database using a Python script that generates an SQLite.db database file. Using a suitable editor, we open the file initialize_db.py in the flask blog directory:
vim initialize_db.py
and then add the following code
import sqlite3 connection = sqlite3.connect('codeunderscoredDB.db') with open('db.sql') as f: connection.executescript(f.read()) cur = connection.cursor() cur.execute("INSERT INTO codeunderscored_posts (cd_title, cd_content) VALUES (?, ?)", ('Codeunderscored First Post', 'Content for the first post') ) cur.execute("INSERT INTO codeunderscored_posts(cd_title, cd_content) VALUES (?, ?)", ('Codeunderscored Second Post', 'Content for the second post') ) connection.commit() connection.close()
We must first import the sqlite3 module before opening a connection to the codeunderscoredDB.db file, which will be created once the Python file is launched. The db.sql file is then opened with the open() function. Then you run its contents using the executescript() method, which runs many SQL statements at once and creates the codeunderscored_posts table. We also build a Cursor object and use its execute() function to run two INSERT SQL statements to update the codeunderscored_posts table with two new blog entries. Finally, we close the connection and commit the modifications.
Save and close the file before using the python command to run it on the terminal:
python initialize_db.py
When the file is done running, it will create a codeunderscoredDB.db file in your codeunderscored_blog directory. This indicates that the database has been successfully set up.
codeunderscoredDB.db is created in codeunderscored_blog directory
The posts placed into the database will be retrieved and displayed on the application’s homepage in the next phase.
Showing All Posts
Now that the database is set up, we can use the index() view function to display all of the posts in the database.
To make the changes, open the app.py file and make the following changes:
The sqlite3 module will be imported at the top of the file for your first change:
import sqlite3
Then we’ll write a function that establishes and returns a database connection. It should be included after the imports as follows.
def getCodeunderscoredDBConnection(): db_connection = sqlite3.connect('codeunderscoredDB.db') db_connection.row_factory = sqlite3.Row return db_connection
The getCodeunderscoredDBConnection() function establishes a database connection. You can get name-based access to columns by setting the row_factory attribute to sqlite3.Row in the db database file. It indicates that the database connection will produce rows that behave similarly to standard Python dictionaries. Finally, the function returns the db_connection connection object that we’ll use to connect to the database.
We then modify the index() function to look like this after defining the get db connection() method:
. . . @app.route('/') def index(): db_connection = getCodeunderscoredDBConnection() codeunderscored_posts = db_connection.execute('SELECT * FROM codeunderscored_posts ').fetchall() db_connection.close() return render_template('index.html', posts=codeunderscored_posts)
The getCodeunderscoredDBConnection() function, which we wrote earlier, is used to open a database connection in this new version of the index() function. Then we run a SQL query to get a list of all the entries in the codeunderscored_posts table. We use the fetchall() function to get all of the rows from the query result, which returns a list of the articles we entered into the database earlier.
We use the close() method to close the database connection and return the result of rendering the index.html template. The codeunderscored_posts object, which holds the database results, is also passed as a parameter, allowing you to access the blog posts in the index.html template.
Save and close the app.py file after making these changes.
We can use a for loop to display each article on the index page now that we’ve provided the posts we fetched from the database to the index.html template.
We open the index.html file as follows:
vim templates/index.html
And make the following modifications.
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Welcome to Codeunderscored Blog {% endblock %}</h1> {% for post in posts %} <a href="{{ url_for('post', id=post['id']) }}"> <h2>{{ post['cd_title'] }}</h2> </a> <span class="badge badge-primary">{{ post['cd_created'] }}</span> <a href="{{ url_for('edit', id=post['id']) }}"> <span class="badge badge-warning">Edit</span> </a> <hr> {% endfor %} {% endblock %}
The syntax {% for post in posts %} is a Jinja for loop, which is similar to a Python for loop with the exception that it must be terminated afterward using the {% endfor %}syntax. In the line return render_template(‘index.html’, posts=codeunderscored_posts), we use this syntax to loop through each item in the posts list supplied by the index() method. Further, we display the post title in a <h2> header inside a <a> tag inside this for loop (we’ll subsequently use this tag to connect to each post individually).
We use a literal variable delimiter ({{ … }}).to display the title. Because a post is a dictionary-like object, we can retrieve the post’s title with post[‘title’]. Using the same way, we may display the date when a post was created.
Save and close the file once done changing it. Then, on the browser, go to the index page. On the page, we’ll see the two posts you contributed to the database.
After we’ve adjusted the index() view function to display all of the posts in the database on the app’s homepage, we’ll go on to displaying each post on its page and allowing visitors to link to each one.
Showing a Single Post
To display an individual blog post by its ID, we will create a new Flask route with a view function and a new HTML template.
The URL http://127.0.0.1:5000/1 will be a page that displays the first post by the end of this stage (because it has ID 1). If the post with the corresponding ID number exists, the http://127.0.0.1:5000/ID URL will display it.
We’ll write a standalone function named getOnePost() since we’ll need to get a blog post by its ID from the database in several places later in this article. We can call it with an ID and get the blog post connected with that ID, or you can make Flask return a 404 Not Found message if the blog post does not exist.
You must import the abort() method from the Werkzeug library, which was installed along with Flask at the program’s top. The latter handles responses with a 404 page:
import sqlite3 from flask import Flask, render_template from werkzeug.exceptions import abort
Then, just after the getCodeunderscoredDBConnection() function we built in the previous step, add the getOnePost() method to look like this one here.
. . . def getCodeunderscoredDBConnection(): db_connection = sqlite3.connect('codeunderscoredDB.db') db_connection.row_factory = sqlite3.Row return db_connection def getOnePost(id): db_connection = getCodeunderscoredDBConnection() codeunderscored_single_post = db_connection.execute('SELECT * FROM codeunderscored_posts WHERE id = ?', (id,)).fetchone() db_connection.close() if codeunderscored_single_post is None: abort(404) return codeunderscored_single_post . . .
The id input in this new function defines which blog post to return.
The getCodeunderscoredDBConnection() function is used to open a database connection and run a SQL query to retrieve the blog post associated with the specified id value. To receive the result, we use the fetchone() method and save it in the post variable before closing the connection. If the post variable is None, which means no result was discovered in the database, we use the abort() function imported previously to respond with a 404 error code, and the method will complete. On the other hand, if a post was found, it should return the post variable’s value.
After that, at the end of the app.py file, we add the following view function:
. . . @app.route('/<int:id>') def post(id): codeunderscored_single_post = getOnePost(id) return render_template('post.html', post=codeunderscored_single_post)
We add a variable rule to this new view function to specify that the part after the slash (/) is a positive integer that we need to access in our view function (marked with the int converter). Flask understands this and gives the value to our post() view function’s id keyword parameter. The getOnePost() function is then used to retrieve the blog post associated with the supplied ID and save the result in the post variable, which we then provide to a post.html template that we’ll soon create.
Proceeding this, save the app.py file and begin editing the post.html template:
vim templates/post.html
In this new post.html file, type the following code. It will be similar to the index.html file, only that it will only display a single post and the post’s contents as shown below.
{% extends 'base.html' %} {% block content %} <h2>{% block title %} {{ post['cd_title'] }} {% endblock %}</h2> <span class="badge badge-primary">{{ post['cd_created'] }}</span> <p>{{ post['cd_content'] }}</p> {% endblock %}
To make the title of the page mirror the post title displayed in a <h2>
heading simultaneously, we include the title block that we defined in the base.html template.
The file is then saved and closed.
We can now visit the following URLs to see the two posts we have in the database, as well as the page informing the visitor that the requested blog post could not be located (since there is no post with an ID number of 3 yet):
http://127.0.0.1:5000/1 http://127.0.0.1:5000/2 http://127.0.0.1:5000/3
Returning to the index page, we’ll link each post title to its corresponding page. We’ll use the url_for() function to accomplish this. To begin, modify the index.html template as follows:
vim templates/index.html
Then modify the href attribute’s value from # to url for(‘post’, post id=post[‘id’]) to make the for loop appear exactly like this:
{% for post in posts %} <a href="{{ url_for('post', id=post['id']) }}"> <h2>{{ post['title'] }}</h2> </a> <span class="badge badge-primary">{{ post['created'] }}</span> <hr> {% endfor %}
As a first argument, you supply ‘post’ to the url_for() function. It is the name of the post() view function, and you pass it the value post[‘id’] because it accepts an id parameter. Based on the ID of each post, the url_for() function will return the appropriate URL.
The file should be saved and closed.
The links on the index page should now work properly. With this, we’ve completed the application component that displays the blog articles stored in the database. We’ll then give the app the power to create, amend, and delete blog articles.
Updating Posts
After we’ve finished displaying the database’s blog posts on the web application, we’ll need to give users the ability to create new blog posts and add them to the database, update existing ones, and delete superfluous blog posts.
Creating a New Article
We have an application that displays the posts in the database up to this point, but there is no way to add a new post unless you connect directly to the SQLite database and manually add one. We’ll construct a page in this part where We’ll create a post by providing a title and content.
Edit the app.py file as follows:
vim app.py
To begin, we’ll need to import the necessary components from the Flask framework:
To access incoming request data submitted via an HTML form, use the global request object.
To generate URLs, we will use the url_for() function.
When a request is processed, we use the flash() function to display a message.
To redirect the client to a new location, we use the redirect() function.
Then, we include the following imports in your file:
import sqlite3 from flask import Flask, render_template, request, url_for, flash, redirect from werkzeug.exceptions import abort . . .
The flash() function, which requires a secret key, saves flashed messages in the client’s browser session. This secret key is needed to protect sessions, allowing Flask to remember information from one request to the next, such as switching from the new post page to the index page. The user can see the information in the session, but they can’t change it unless they have the secret key; therefore, don’t give out the secret key to anyone. For further details, see sessions on the Flask documentation.
To create a secret key, use the app.config object to add a SECRET_KEY configuration to the application. Before defining the index() view method, add it after the app definition:
. . . app = Flask(__name__) app.config['SECRET_KEY'] = 'codeunderscoredsecretkey'
Keep in mind that the secret key should be a long string of random characters.
You’ll construct a view function after specifying a secret key, which will render a template with a form you can fill out to create a new blog post. At the bottom of the code, add the following new function:
. . . @app.route('/create', methods=('GET', 'POST')) def create(): return render_template('create.html')
The above code creates the /create route that accepts both GET and POST requests. By default, GET queries are accepted. We’ll send a tuple with the accepted types of requests to the @app.route() decorator’s methods parameter to accept POST requests issued by the browser when submitting forms.
The file should be saved and closed.
To make the template, go to the templates folder, create and open a file called create.html. Subsequently, add the following code to the file.
{% extends 'base.html' %} {% block content %} <h1>{% block title %}New Codeunderscored Post {% endblock %}</h1> <form method="post"> <div class="form-group"> <label for="cd_title">Heading</label> <input type="text" name="cd_title" placeholder="Post title" class="form-control" value="{{ request.form['cd_title'] }}"></input> </div> <div class="form-group"> <label for="content">Your Content</label> <textarea name="cd_content" placeholder="Post content" class="form-control">{{ request.form['cd_content'] }}</textarea> </div> <div class="form-group"> <button type="submit" name="submit_codeunderscored_post" class="btn btn-primary">Submit</button> </div> </form> {% endblock %}
The majority of this code is HTML. It will show a text space for the post title, an input box for the post content, and a submit button.
The post title input’s value is request.form[‘title’], and the text area’s value is request.form[‘content’]; this is done to ensure that the data submitted is not lost if something goes wrong. If you create a long post and neglect to give it a title, a notification will appear telling the user that a title is required. The post typed will be saved in the request global object, which is available in the templates.
Now that the development server is up and running, access the newly created page by using the /create route in the browser:
A ‘Create a New Post’ page will appear, with a box for a title and content.
This form sends a POST request to the view method create(). However, because there is no code to handle a POST request yet, nothing occurs after filling out the form and submitting it.
When a form is submitted, we’ll handle the incoming POST request. It will be done within the create() view function. By validating the value of request.method, we can process the POST request individually. When its value is set to ‘POST,’ it implies that the request is a POST request, and you’ll extract, validate, and insert the data into the database.
Edit the app.py file as follows:
Make the following changes to the create() view function:
@app.route('/create', methods=('GET', 'POST')) def create(): if request.method == 'POST': title = request.form['cd_title'] content = request.form['cd_content'] if not title: flash('Title is required!') else: db_connection = getCodeunderscoredDBConnection() db_connection.execute('INSERT INTO codeunderscored_posts (cd_title, cd_content) VALUES (?, ?)', (title, content)) db_connection.commit() db_connection.close() return redirect(url_for('index')) return render_template('create.html')
We ensure that the code following the if statement is only run when the request is a POST request via the comparison request. ‘POST’ is the method.
The submitted title and content are then extracted from the request.
The form object offers us access to the request’s form data. If the title isn’t provided, the condition is met, and a notice advising the user that the title is necessary is displayed. If the title is provided, we use the getCodeunderscoredDBConnection() function to open a connection and enter the title and the content into the posts table.
The modifications are subsequently committed to the database, and the connection is closed. Following the addition of the blog post to the database, we use the redirect() function to send the client to the index page, supplying it the URL obtained by the url_for() function with the value ‘index’ as an argument.
The file should be saved and closed.
Now, using the web browser, we head to the /create route:
http://127.0.0.1:5000/create
We will fill in the box with a title and some content that suites us. After we submit the form, the new post will appear on the index page.
Finally, in the base.html template, we’ll display flashed messages and add a link to the navigation bar for fast access to this new page. To begin, we open the template file:
vim templates/base.html
Following the About link inside the tag, edit the file by adding a new tag. Then, immediately above the content block, add a new for loop to display the flashed messages below the navigation bar. These messages are available through Flask’s specific get_flashed_messages() function:
<nav class="navbar navbar-expand-md navbar-light bg-light"> <a class="navbar-brand" href="{{ url_for('index')}}">FlaskBlog</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="{{url_for('create')}}">New Codeunderscored Post</a> </li> </ul> </div> </nav> <div class="container"> {% for message in get_flashed_messages() %} <div class="alert alert-danger">{{ message }}</div> {% endfor %} {% block content %} {% endblock %} </div>
After making the necessary changes to the file, we save and close. The New Post item in the navigation bar will now link to the /create route.
Making changes to a Post
You must be able to update the existing entries to keep the blog current. This section will demonstrate how to build a new page in the application to make editing posts easier.
We’ll start by adding a new route to the app.py file. The ID of the post that has to be modified will be passed to its view function, and the URL will be in the format /id/edit, with the id variable representing the post’s ID. Edit the app.py file as follows:
vim app.py
After that, at the end of the file, add the edit() view function. This view function will be identical to the create() view method because editing an existing post is equivalent to generating a new one:
. . . @app.route('/<int:id>/edit', methods=('GET', 'POST')) def edit(id): codeunderscored_single_post = getOnePost(id) if request.method == 'POST': title = request.form['cd_title'] content = request.form['cd_content'] if not title: flash('Title is required!') else: db_connection = getCodeunderscoredDBConnection() db_connection.execute('UPDATE codeunderscored_posts SET cd_title = ?, cd_content = ?' ' WHERE id = ?', (title, content, id)) db_connection.commit() db_connection.close() return redirect(url_for('index')) return render_template('edit.html', post=codeunderscored_single_post)
The URL determines which post you modify, and Flask passes the ID number to the edit() function via the id argument. To acquire the post associated with the specified ID from the database, pass this value to the getOnePost() function. The updated data will be sent via POST, which will be handled by the if request.method ==’POST’ condition.
The starting point is by extracting data from the request, just as you would when creating a new post.
If the title has an empty value, the form object will display a message; otherwise, it will open a database connection. The posts table is then updated by adding a new title and content, with the ID of the post in the database matching the ID in the URL.
In the event of a GET request, render an edit.html template with the post variable holding the getOnePost() function’s returned value. On the edit page, we’ll use this to show the existing title and content.
After saving and closing the file, build a new edit.html template as follows:
vim templates/edit.html
Then, append the following code in the file.
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Edit "{{ post['cd_title'] }}" {% endblock %}</h1> <form method="post"> <div class="form-group"> <label for="title">Title</label> <input type="text" name="cd_title" placeholder="Post title" class="form-control" value="{{ request.form['cd_title'] or post['cd_title'] }}"> </input> </div> <div class="form-group"> <label for="content">Content</label> <textarea name="post_content" placeholder="Post content" class="form-control">{{ request.form['cd_content'] or post['cd_content'] }}</textarea> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> <hr> <form action="{{ url_for('delete', id=post['id']) }}" method="POST"> <input type="submit" name="cd_delete_post" value="Delete Post" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to remove this post?')"> </form> {% endblock %}
The file can now be saved and closed.
Except for the request.form[‘title’] or post[‘title’] and request.form[‘content’] or post[‘content’] syntax, this code follows the same structure. If the data stored in the request exists, it is displayed; otherwise, the data from the post variable supplied to the template containing current database data is displayed.
Now, let’s head to the following URL to make changes to the first post:
http://127.0.0.1:5000/1/edit
After editing the post and submitting the form, we have to double-check that it’s been changed.
On the index page, we must now include a link to the edit page for each post. Open the index.html template file with the following command:
sudo vim templates/index.html
We can now save and close the file.
With the Edit link, we add a <a> tag to connect to the edit() view function, passing in the post[‘id’] value to connect to the edit page of each post.
Getting Rid of a Post
A post may no longer be required to be publicly accessible, which is why the ability to delete a post is essential. We will add the delete capability to the application in this phase.
First, we create a new /ID/delete route that accepts POST requests in the same way that the edit() view function does. The post’s ID to be deleted will be passed to the new delete() view method from the URL. After this, we open the app.py file using the following command:
sudo vim app.py
We will then add the following code
# .... @app.route('/<int:id>/delete', methods=('POST',)) def delete(id): codeunderscored_single_post = getOnePost(id) db_connection = getCodeunderscoredDBConnection() db_connection.execute('DELETE FROM codeunderscored_posts WHERE id = ?', (id,)) db_connection.commit() db_connection.close() flash('"{}" was successfully deleted!'.format(codeunderscored_single_post['cd_title'])) return redirect(url_for('index'))
This view function accepts only POST requests. Because web browser requests are GET by default. Thus, accessing the/ID/delete route in the browser will result in an error.
On the other hand, this route can be accessed via a form that makes a POST request with the ID of the post we want to delete as the parameter. The ID value will be passed to the function, utilizing the getOnePost() function to retrieve the post from the database.
Then we can open a database connection and delete the post using the DELETE FROM SQL statement. Then commit the modification to the database and disconnect the connection while flashing a notice to the user and redirecting them to the index page, informing them that the post was successfully deleted.
We don’t need to render a template file because we’ll only be adding a Delete button to the edit page.
We open the edit.html template file with the following command:
vim templates/edit.html
<hr> <form action="{{ url_for('delete', id=post['id']) }}" method="POST"> <input type="submit" value="Delete Post" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to delete this post?')"> </form> {% endblock %}
Before sending the request, we utilize the confirm() function to display a confirmation message.
Return to the edit page of a blog post and attempt deleting it again:
http://127.0.0.1:5000/1/edit
Complete Code for Codeunderscored Blog
This section has the complete code for your reference in case you missed one or more things during the buildup of this project.
initialize_db.py
import sqlite3 connection = sqlite3.connect('codeunderscoredDB.db') with open('db.sql') as f: connection.executescript(f.read()) cur = connection.cursor() cur.execute("INSERT INTO codeunderscored_posts (cd_title, cd_content) VALUES (?, ?)", ('Codeunderscored First Post', 'Content for the first post') ) cur.execute("INSERT INTO codeunderscored_posts(cd_title, cd_content) VALUES (?, ?)", ('Codeunderscored Second Post', 'Content for the second post') ) connection.commit() connection.close()
db.sql
DROP TABLE IF EXISTS codeunderscored_posts; -- cd -codeunderscored CREATE TABLE codeunderscored_posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, cd_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, cd_title TEXT NOT NULL, cd_content TEXT NOT NULL );
app.py
import sqlite3 from flask import Flask, render_template, request, url_for, flash, redirect from werkzeug.exceptions import abort app = Flask(__name__) def getCodeunderscoredDBConnection(): db_connection = sqlite3.connect('codeunderscoredDB.db') db_connection.row_factory = sqlite3.Row return db_connection def getOnePost(id): db_connection = getCodeunderscoredDBConnection() codeunderscored_single_post = db_connection.execute('SELECT * FROM codeunderscored_posts WHERE id = ?', (id,)).fetchone() db_connection.close() if codeunderscored_single_post is None: abort(404) return codeunderscored_single_post @app.route('/') def index(): db_connection = getCodeunderscoredDBConnection() codeunderscored_all_posts = db_connection.execute('SELECT * FROM codeunderscored_posts').fetchall() db_connection.close() return render_template('index.html', posts=codeunderscored_all_posts) @app.route('/<int:id>') def post(id): codeunderscored_single_post = getOnePost(id) return render_template('post.html', post=codeunderscored_single_post) @app.route('/create', methods=('GET', 'POST')) def create(): if request.method == 'POST': title = request.form['cd_title'] content = request.form['cd_content'] if not title: flash('Title is required!') else: db_connection = getCodeunderscoredDBConnection() db_connection.execute('INSERT INTO codeunderscored_posts (cd_title, cd_content) VALUES (?, ?)', (title, content)) db_connection.commit() db_connection.close() return redirect(url_for('index')) return render_template('create.html') @app.route('/<int:id>/edit', methods=('GET', 'POST')) def edit(id): codeunderscored_single_post = getOnePost(id) if request.method == 'POST': title = request.form['cd_title'] content = request.form['cd_content'] if not title: flash('Title is required!') else: db_connection = getCodeunderscoredDBConnection() db_connection.execute('UPDATE codeunderscored_posts SET cd_title = ?, cd_content = ?' ' WHERE id = ?', (title, content, id)) db_connection.commit() db_connection.close() return redirect(url_for('index')) return render_template('edit.html', post=codeunderscored_single_post) @app.route('/<int:id>/delete', methods=('POST',)) def delete(id): codeunderscored_single_post = getOnePost(id) db_connection = getCodeunderscoredDBConnection() db_connection.execute('DELETE FROM codeunderscored_posts WHERE id = ?', (id,)) db_connection.commit() db_connection.close() flash('"{}" was successfully deleted!'.format(codeunderscored_single_post['cd_title'])) return redirect(url_for('index'))
base.html
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <!-- CSS only --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous"> <title>{% block title %} {% endblock %}</title> </head> <body> <nav class="navbar navbar-expand-md navbar-light bg-light"> <a class="navbar-brand" href="{{ url_for('index')}}">codeunderscored Blog</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav"> <li class="nav-item"> <a class="nav-link" href="{{url_for('create')}}">Create Post</a> </li> </ul> </div> </nav> <div class="container"> {% for message in get_flashed_messages() %} <div class="alert alert-danger">{{ message }}</div> {% endfor %} {% block content %} {% endblock %} </div> <!-- JavaScript Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script> </body> </html>
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="{{ url_for('static', filename= 'css/cd_main.css') }}"> <title>codeunderscored Blog</title> </head> <body> {% extends 'base.html' %} {% block content %} <h1>{% block title %} Welcome to Codeunderscored Blog {% endblock %}</h1> {% for post in posts %} <a href="{{ url_for('post', id=post['id']) }}"> <h2>{{ post['cd_title'] }}</h2> </a> <span class="badge badge-primary">{{ post['cd_created'] }}</span> <a href="{{ url_for('edit', id=post['id']) }}"> <span class="badge badge-warning">Edit</span> </a> <hr> {% endfor %} {% endblock %} </body> </html>
create.html
{% extends 'base.html' %} {% block content %} <h1>{% block title %}New Codeunderscored Post {% endblock %}</h1> <form method="post"> <div class="form-group"> <label for="cd_title">Heading</label> <input type="text" name="cd_title" placeholder="Post title" class="form-control" value="{{ request.form['cd_title'] }}"></input> </div> <div class="form-group"> <label for="content">Your Content</label> <textarea name="cd_content" placeholder="Post content" class="form-control">{{ request.form['cd_content'] }}</textarea> </div> <div class="form-group"> <button type="submit" name="submit_codeunderscored_post" class="btn btn-primary">Submit</button> </div> </form> {% endblock %}
edit.html
{% extends 'base.html' %} {% block content %} <h1>{% block title %} Edit "{{ post['cd_title'] }}" {% endblock %}</h1> <form method="post"> <div class="form-group"> <label for="title">Title</label> <input type="text" name="cd_title" placeholder="Post title" class="form-control" value="{{ request.form['cd_title'] or post['cd_title'] }}"> </input> </div> <div class="form-group"> <label for="content">Content</label> <textarea name="post_content" placeholder="Post content" class="form-control">{{ request.form['cd_content'] or post['cd_content'] }}</textarea> </div> <div class="form-group"> <button type="submit" class="btn btn-primary">Submit</button> </div> </form> <hr> <form action="{{ url_for('delete', id=post['id']) }}" method="POST"> <input type="submit" name="cd_delete_post" value="Delete Post" class="btn btn-danger btn-sm" onclick="return confirm('Are you sure you want to remove this post?')"> </form> {% endblock %}
post.html
{% extends 'base.html' %} {% block content %} <h2>{% block title %} {{ post['cd_title'] }} {% endblock %}</h2> <span class="badge badge-primary">{{ post['cd_created'] }}</span> <p>{{ post['cd_content'] }}</p> {% endblock %}
When done, the final view should appear as follows.
Conclusion
The Flask Python framework’s fundamental concepts were covered in this session. We learned how to create a small Codeunderscored blog web application, deployed it to a development server, and allowed users to provide custom data via URL parameters and web forms. We also used the Jinja template engine to reuse HTML files while also incorporating logic into them. By the end of this article, we’ve built a fully functional web blog that uses the Python language and SQL queries to generate, display, modify and delete blog articles from an SQLite database. We have also scratched the surface of SQLite, and there is a lot more to explore if you intend to build the market-standard application. These include aspects like database relationships.
We can improve this app by adding user authentication so that only registered users can write and edit blog entries. We can also add comments and tags to each blog post and file uploads so that users can include images in their postings. For further details, see the Flask documentation.