Creating A Simple Blog With Flask

Creating A Simple Blog With Flask

In this article, I'll be discussing my Flask blog, a web application that I built using the Flask micro-framework. I decided to use Flask to create my blog because I wanted to learn more about web development and have a platform to share my thoughts and ideas with a wider audience.

Flask is a micro web framework written in Python that makes it easy to create web applications quickly. In this article, I'll explain how I used Flask to create my blog.

First, let's talk about what Flask is and what it is used for. Flask is a lightweight and modular framework that allows you to handle HTTP requests and responses, and use Jinja templates to generate HTML pages. It's easy to get started with Flask, as it has great documentation and a large community of users.

This tutorial will walk you through the process of creating a simple blog using Flask. We will be using Flask-SQLAlchemy, a Flask extension that provides support for SQLAlchemy, which is a powerful Python library for interacting with databases.

Prerequisites:

  • Python 3 installed on your system

  • knowledge of Python

  • Basic knowledge of HTML and CSS

First, we need to set up a Flask project. Open up a terminal and create a new directory for your project:

mkdir FAST-blog
cd FAST-blog

Next, set up a virtual environment and activate it:

What is a virtual environment?

In a Flask application, a virtual environment is a Python environment that is isolated from the system-level packages. It allows you to install and manage packages for your application without affecting the system-level packages.

Using a virtual environment has several benefits:

  • It keeps your project's dependencies separate from your system-level packages.

  • It makes it easier to switch between different versions of packages for different projects.

  • It allows you to test your code with different versions of packages, without affecting your system-level packages.

To create a virtual environment in a Flask application, you can use the venv module that comes with Python. You can then install packages using pip, which is the package manager for Python.

To create a virtual environment we use this command:


# Create the virtual environment
python -m venv blog

The next step is to activate our virtual environment with this command:

source blog/Scripts/activate

Once the virtual environment is activated, any packages that you install using pip will be installed in the virtual environment, rather than on the system level. To deactivate the virtual environment, you can use the deactivate command.

Installation

Pip is the package installer for Python. That is what will be used to install the libraries used in this tutorial.

pip install flask flask-sqlalchemy

Next, we install:

pip install Flask-Login

Now create a file called app.py in your project directory where we would add our imports, database and routes.

Imports

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
from flask_login import LoginManager,UserMixin,login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash 
from flask import  render_template, redirect, url_for, request, flash
from sqlalchemy import func

We start by importing modules to use in creating our blog. Flask is a web framework for Python, and SQLAlchemy is a library for interacting with databases. The flask_sqlalchemy module is a Flask extension that simplifies the use of SQLAlchemy in Flask. The flask_login module provides user session management for Flask. The werkzeug library is a utility library for Flask that provides various functions such as generating password hashes and checking password hashes.

Database Preparation

base_dir = os.path.dirname(os.path.realpath(__file__))

app = Flask(__name__)

app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + os.path.join(base_dir, 'blog.db')
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = '5a60845ca52c8ee42a2bb2d0'

db = SQLAlchemy(app)
db.init_app(app)

This code is used to set up a Flask web application and configure a database using SQLAlchemy.

The base_dir variable is set to the directory where the current Python file is located.

The app object is created as an instance of the Flask class, with the name of the current Python module passed as the argument. This is used to set the Flask application instance, which is later used to run the app.

The app.config dictionary is then populated with several key-value pairs that configure the Flask app. The SQLALCHEMY_DATABASE_URI value is set to a string that specifies the database to be used by SQLAlchemy. It appears to be a SQLite database located at 'blog.db' in the same directory as the current file. The SQLALCHEMY_TRACK_MODIFICATIONS flag is set to False, which disables SQLAlchemy's modification tracking feature. The SECRET_KEY is set to a string value that is used to encrypt cookies and sign sessions.

The db object is created as an instance of the SQLAlchemy class, and is then initialized with the app object using the init_app method. This is used to set up the database connection for the Flask app.

User Authentication

login_manager = LoginManager(app)
@login_manager.user_loader
def user_loader(id):
    return User.query.get(int(id))

The login_manager object is created as an instance of the LoginManager class, and is initialized with the app object. This is used to handle user authentication for the Flask app. This code is to set up a login_manager object as an instance of the LoginManager class, and initialize it with the app object. It then defines a function decorated with @login_manager.user_loader, which sets the function as a user loader function for the login_manager.

The user loader function is used to retrieve a user from the database based on their unique identifier, which is passed as the id argument to the function. In this case, the function is to use the User.query.get method to retrieve the user with the specified id from the database, and return it.

The user_loader function is called by Flask-Login during the login process to retrieve a user object from the database based on the unique identifier stored in the session. This allows the application to keep track of the logged-in user across requests.

Database Table Creation

class User(db.Model, UserMixin):
    id = db.Column(db.Integer(), primary_key=True)
    first_name = db.Column(db.String(255), nullable=False)
    last_name = db.Column(db.String(255), nullable=False)
    username = db.Column(db.String(255), nullable=False, unique=True)
    email = db.Column(db.String(255), nullable=False, unique=True)
    password = db.Column(db.Text(), nullable=False)
    date_created = db.Column(db.DateTime(timezone=True), default=func.now())
    articles = db.relationship('Article', backref='user', passive_deletes=True)
    def __repr__(self):
        return f'User: <{self.username}>'


class Article(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    text = db.Column(db.Text(), nullable=False)
    date_created = db.Column(db.DateTime(timezone=True), default=func.now())
    author = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)

class Message(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    email = db.Column(db.String(80), nullable=False)
    subject = db.Column(db.String(80), nullable=False)
    message = db.Column(db.Text(), nullable=False)

    def __repr__(self):
        return f"Message: <{self.subject}>"

This code defines three classes: User, Article, and Message. Each class corresponds to a database table and defines a set of fields as class attributes.

The User class is a subclass of db.Model and UserMixin, and represents a user of the application. It has several fields, including id, first_name, last_name, username, email, password, and date_created. The id field is an integer primary key field, and the other fields are strings of various lengths or a date-time value. The articles field is a relationship field that defines a one-to-many relationship between users and articles. The __repr__ method returns a string representation of the user object.

The Article class is a subclass of db.Model and represents an article written by a user. It has fields for the article id, text, date_created, and author. The id field is an integer primary key field, and the other fields are strings or a date-time value. The author field is a foreign key field that references the id field of the User table. The ondelete option is set to 'CASCADE', which means that if a user is deleted, all of their articles will also be deleted.

The Message class is a subclass of db.Model and represents a message sent by a user. It has fields for the message id, email, subject, and message. The id field is an integer primary key field, and the other fields are strings of various lengths. The __repr__ method returns a string representation of the message object.

Routes

The next line of action is to create routes to enable our flask blog to work properly. What are routes? a route is a mapping between a URL and the function that should be executed when the URL is accessed. When a user navigates to a URL in the web application, Flask looks for a matching route and runs the associated function. The function can then generate a response for the user, such as rendering a template or returning a JSON object.

Home

@app.route("/")
@app.route("/home")
def home():
    articles = Article.query.all()
    return render_template("home.html", user=current_user, articles=artic

The routes are the URL paths that users can visit in their web browsers. The first route, "/", is the root URL of the application, and the second route, "/home", is a subpath of the root URL.

When either of these routes is accessed, the function home() is executed. The function retrieves all of the articles from the database and then renders a template called home.html, passing in the articles as well as a user object. The render_template() function is a Flask function that is used to render HTML templates. The user object is a global variable provided by Flask that represents the currently logged-in user, and the articles variable is a list of all of the articles in the database.

Create-Post

@app.route("/create-posts", methods = ['GET', 'POST'])
@login_required
def create_post():
    if request.method == 'POST':
        text = request.form.get('text')


        if not text:
            flash('field can not be empty', category='error')
        else:
            article = Article(text=text, author=current_user.id)
            db.session.add(article)
            db.session.commit()
            flash('Article created successfully', category='success')
            return redirect(url_for('home'))
    return render_template('create_posts.html', user=current_user)

This code defines a route for creating a new article. The route is "/create-posts", and it can handle both GET and POST requests.

The @login_required decorator is a Flask extension that requires the user to be logged in before they can access the route. If the user is not logged in, they will be redirected to the login page.

The function first checks the HTTP method of the request. If it is a POST request, it means that the user has submitted the form to create a new article. The function retrieves the text of the article from the form data and checks if it is empty. If it is empty, it displays a flash message telling the user that the field cannot be empty. If the field is not empty, it creates a new Article object and adds it to the database using the db.session.add() and db.session.commit() functions. Finally, it displays a success message and redirects the user back to the home page.

If the request is a GET request, it means that the user has accessed the route for the first time and is trying to view the form for creating a new article. In this case, the function simply renders the create_posts.html template and passes in the user object.

Login

@app.route("/login", methods = ["GET", "POST"])
def login():
    if request.method == "POST":
        email=  request.form.get("email")
        password =  request.form.get("password1")


        user = User.query.filter_by(email=email).first()
        if user:
            if check_password_hash(user.password, password):
                flash("Logged in!", category='success')
                login_user(user, remember=True)
                return redirect(url_for('home'))
            else:
                flash('Password is incorrect.', category='error')
        else:
            flash('Email does not exist.', category='error')
    return render_template("login.html", user=current_user)

This code defines a route for logging in to the blog. The route is "/login", and it can handle both GET and POST requests.

If the request method is POST, it means that the user has submitted the login form. The function retrieves the email and password from the form data and checks if the email exists in the database. If the email does not exist, it displays a flash message telling the user that the email does not exist. If the email does exist, it checks if the password is correct by using the check_password_hash() function. If the password is incorrect, it displays a flash message telling the user that the password is incorrect. If the password is correct, it logs the user in using the login_user() function provided by Flask-Login and redirects the user to the home page.

If the request method is GET, it means that the user has accessed the route for the first time and is trying to view the login form. In this case, the function simply renders the login.html template and passes in the user object.

Sign- Up

@app.route("/sign-up", methods = ["GET", "POST"])
def sign_up():
    if request.method == "POST":
        username =  request.form.get("username")
        first_name = request.form.get('first_name')
        last_name = request.form.get('last_name')
        email=  request.form.get("email")
        password1 =  request.form.get("password1")
        password2 =  request.form.get("passsword2")

        user = User.query.filter_by(username=username).first()
        if user:
            flash("This username already exists.")
            return redirect(url_for('sign_up'))

        email_exists =User.query.filter_by(email=email).first()
        if email_exists:
            flash("This email is already registered.")
            return redirect(url_for('sign_up'))

        password = generate_password_hash(password1)
        new_user = User(username=username, first_name=first_name, last_name=last_name, email=email, password=password)
        db.session.add(new_user)
        db.session.commit()
        login_user(new_user, remember=True)
        flash('User created')
        return redirect(url_for('home'))

    return render_template('signup.html', user=current_user)

This code defines a route for signing up for the blog. The route is "/sign-up", and it can handle both GET and POST requests.

If the request method is POST, it means that the user has submitted the sign-up form. The function retrieves the user's information from the form data and checks if the username or email are already in use. If either the username or email are already in use, it displays a flash message telling the user that the username or email is already in use and redirects the user back to the sign-up form. If both the username and email are available, it creates a new user object, adds it to the database using the db.session.add() and db.session.commit() functions, logs the user in using the login_user() function, and redirects the user to the home page.

If the request method is GET, it means that the user has accessed the route for the first time and is trying to view the sign-up form. In this case, the function simply renders the signup.html template and passes in the user object.

Logout

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for('home'))

This code defines a route for logging out of the web blog. The route is "/logout", and it can only handle GET requests. The @login_required decorator is a Flask extension that requires the user to be logged in before they can access the route. If the user is not logged in, they will be redirected to the login page.

When the route is accessed, the function logout() is executed. The function logs the user out using the logout_user() function provided by Flask-Login and then redirects the user to the home page.

Delete Post

@app.route("/delete-post/<id>", methods=['GET'])
@login_required
def delete_post(id):
    article = Article.query.filter_by(id=id).first()

    if not article:
        flash("Post does not exist.", category='error')
    elif current_user.id != article.author:
        flash('You do not have permission to delete this post.', category='error')
    else:
        db.session.delete(article)
        db.session.commit()
        flash('Article deleted.', category='success')

    return redirect(url_for('home')

This code defines a route for deleting an article from the blog. The route is "/delete-post/<id>", and it can only handle GET requests. The <id> part of the route is a dynamic component that represents the ID of the article that the user wants to delete. The @login_required decorator is a Flask extension that requires the user to be logged in before they can access the route. If the user is not logged in, they will be redirected to the login page.

When the route is accessed, the function delete_post() is executed. The function retrieves the article from the database using the Article.query.filter_by() function and the id parameter. If the article does not exist, it displays a flash message telling the user that the post does not exist. If the article exists but the logged-in user is not the author of the article, it displays a flash message telling the user that they do not have permission to delete the post. If the article exists and the logged-in user is the author of the article, it deletes the article from the database using the db.session.delete() and db.session.commit() functions and displays a flash message telling the user that the article was deleted. Finally, it redirects the user back to the home page.

Post

@app.route("/posts/<username>")
@login_required
def posts(username):
    user = User.query.filter_by(username=username).first()

    if not user:
        flash('No user with that username exists.', category='error')
        return redirect(url_for('home'))

    articles = Article.query.filter_by(author=user.id).all()
    return render_template("posts.html", user=current_user, articles=articles, username=username)

This code defines a route for viewing a user's articles on the blog. The route is "/posts/<username>", and it can only handle GET requests. The <username> part of the route is a dynamic component that represents the username of the user whose articles the user wants to view. The @login_required decorator is a Flask extension that requires the user to be logged in before they can access the route. If the user is not logged in, they will be redirected to the login page.

When the route is accessed, the function posts() is executed. The function retrieves the user from the database using the User.query.filter_by() function and the username parameter. If the user does not exist, it displays a flash message telling the user that no user with that username exists and redirects the user back to the home page. If the user exists, it retrieves all of the articles written by that user from the database using the Article.query.filter_by() function and the author parameter. Finally, it renders the posts.html template, passing in the user object, the articles object, and the username parameter.

About

@app.route('/about')
def about():
    return render_template('about.html')

This code defines a route for the About page on the blog. The route is "/about", and it can only handle GET requests.

When the route is accessed, the function about() is executed. The function simply renders the about.html template.

Contact

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        email = request.form.get('email')
        subject = request.form.get('subject')
        message = request.form.get('message')

        new_message = Message( email=email,subject=subject,message=message)
        db.session.add(new_message)
        db.session.commit()

        flash("Message sent.")
        return redirect(url_for('home'))
    return render_template('contact.html')

This code defines a route for the Contact page on the blog. The route is "/contact", and it can handle both GET and POST requests.

If the request method is POST, it means that the user has submitted the contact form. The function retrieves the email, subject, and message from the form data and creates a new Message object with this information. It then adds the message to the database using the db.session.add() and db.session.commit() functions and displays a flash message telling the user that the message was sent. Finally, it redirects the user back to the home page.

If the request method is GET, it means that the user has accessed the route for the first time and is trying to view the contact form. In this case, the function simply renders the contact.html template.

Edit

@app.route('/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def edit(id):
    article_edit = Article.query.get_or_404(id)

    if request.method == 'POST':
        article_edit.text = request.form.get('text')
        db.session.commit()
        flash("Your changes have been saved.")
        return redirect(url_for('home'))


    return render_template('edit.html',article=article_edit)

This code defines the route for editing an article on the blog. The route is "/edit/<id>", and it can handle both GET and POST requests. The <id> part of the route is a dynamic component that represents the ID of the article that the user wants to edit. The @login_required decorator is a Flask extension that requires the user to be logged in before they can access the route. If the user is not logged in, they will be redirected to the login page.

If the request method is POST, it means that the user has submitted the form to edit the article. The function retrieves the new text of the article from the form data and updates the article in the database using the db.session.commit() function. It then displays a flash message telling the user that their changes have been saved and redirects the user back to the home page.

If the request method is GET, it means that the user has accessed the route for the first time and is trying to view the form for editing the article. In this case, the function retrieves the article from the database using the Article.query.get_or_404() function and the id parameter. If the article does not exist, it returns a 404 error to the user. If the article exists, it renders the edit.html template and passes in the article object.

Finally, we add the "if __name__ == '__main__': block".It is a standard Python idiom that is used to ensure that the code inside the block is only executed when the script is run directly, and not when it is imported by another script.

if __name__ == '__main__':
    app.run(debug=True)

The app.run() function starts the web server and runs the application. The debug parameter is set to True, which enables the debug mode of the application. In debug mode, the application will automatically reload itself when the source code is changed, and it will display more detailed error messages in the browser if an error occurs. It is recommended to set the debug parameter to False when deploying the application in production.

By the time you are done writing the code, it should look like this:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os
from flask_login import LoginManager,UserMixin,login_user, logout_user, login_required, current_user
from werkzeug.security import generate_password_hash, check_password_hash 
from flask import  render_template, redirect, url_for, request, flash
from sqlalchemy import func


base_dir = os.path.dirname(os.path.realpath(__file__))

app = Flask(__name__)

app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + os.path.join(base_dir, 'blog.db')
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = '5a60845ca52c8ee42a2bb2d0'

db = SQLAlchemy(app)
db.init_app(app)
login_manager = LoginManager(app)

class User(db.Model, UserMixin):
    id = db.Column(db.Integer(), primary_key=True)
    first_name = db.Column(db.String(255), nullable=False)
    last_name = db.Column(db.String(255), nullable=False)
    username = db.Column(db.String(255), nullable=False, unique=True)
    email = db.Column(db.String(255), nullable=False, unique=True)
    password = db.Column(db.Text(), nullable=False)
    date_created = db.Column(db.DateTime(timezone=True), default=func.now())
    articles = db.relationship('Article', backref='user', passive_deletes=True)
    def __repr__(self):
        return f'User: <{self.username}>'


class Article(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    text = db.Column(db.Text(), nullable=False)
    date_created = db.Column(db.DateTime(timezone=True), default=func.now())
    author = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False)

class Message(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    email = db.Column(db.String(80), nullable=False)
    subject = db.Column(db.String(80), nullable=False)
    message = db.Column(db.Text(), nullable=False)

    def __repr__(self):
        return f"Message: <{self.subject}>"



@login_manager.user_loader
def user_loader(id):
    return User.query.get(int(id))


@app.route("/")
@app.route("/home")
def home():
    articles = Article.query.all()
    return render_template("home.html", user=current_user, articles=articles)



@app.route("/create-posts", methods = ['GET', 'POST'])
@login_required
def create_post():
    if request.method == 'POST':
        text = request.form.get('text')


        if not text:
            flash('field can not be empty', category='error')
        else:
            article = Article(text=text, author=current_user.id)
            db.session.add(article)
            db.session.commit()
            flash('Article created successfully', category='success')
            return redirect(url_for('home'))
    return render_template('create_posts.html', user=current_user)



@app.route("/login", methods = ["GET", "POST"])
def login():
    if request.method == "POST":
        email=  request.form.get("email")
        password =  request.form.get("password1")


        user = User.query.filter_by(email=email).first()
        if user:
            if check_password_hash(user.password, password):
                flash("Logged in!", category='success')
                login_user(user, remember=True)
                return redirect(url_for('home'))
            else:
                flash('Password is incorrect.', category='error')
        else:
            flash('Email does not exist.', category='error')
    return render_template("login.html", user=current_user)

@app.route("/sign-up", methods = ["GET", "POST"])
def sign_up():
    if request.method == "POST":
        username =  request.form.get("username")
        first_name = request.form.get('first_name')
        last_name = request.form.get('last_name')
        email=  request.form.get("email")
        password1 =  request.form.get("password1")
        password2 =  request.form.get("passsword2")

        user = User.query.filter_by(username=username).first()
        if user:
            flash("This username already exists.")
            return redirect(url_for('sign_up'))

        email_exists = User.query.filter_by(email=email).first()
        if email_exists:
            flash("This email is already registered.")
            return redirect(url_for('sign_up'))

        password = generate_password_hash(password1)
        new_user = User(username=username, first_name=first_name, last_name=last_name, email=email, password=password)
        db.session.add(new_user)
        db.session.commit()
        login_user(new_user, remember=True)
        flash('User created')
        return redirect(url_for('home'))

    return render_template('signup.html', user=current_user)


@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for('home'))


@app.route("/delete-post/<id>", methods=['GET'])
@login_required
def delete_post(id):
    article = Article.query.filter_by(id=id).first()

    if not article:
        flash("Post does not exist.", category='error')
    elif current_user.id != article.author:
        flash('You do not have permission to delete this post.', category='error')
    else:
        db.session.delete(article)
        db.session.commit()
        flash('Article deleted.', category='success')

    return redirect(url_for('home'))

@app.route("/posts/<username>")
@login_required
def posts(username):
    user = User.query.filter_by(username=username).first()

    if not user:
        flash('No user with that username exists.', category='error')
        return redirect(url_for('home'))

    articles = Article.query.filter_by(author=user.id).all()
    return render_template("posts.html", user=current_user, articles=articles, username=username)


@app.route('/about')
def about():
    return render_template('about.html')

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        email = request.form.get('email')
        subject = request.form.get('subject')
        message = request.form.get('message')

        new_message = Message( email=email,subject=subject,message=message)
        db.session.add(new_message)
        db.session.commit()

        flash("Message sent.")
        return redirect(url_for('home'))
    return render_template('contact.html')




@app.route('/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def edit(id):
    article_edit = Article.query.get_or_404(id)

    if request.method == 'POST':
        article_edit.text = request.form.get('text')
        db.session.commit()
        flash("Your changes have been saved.")
        return redirect(url_for('home'))


    return render_template('edit.html',article=article_edit)

if __name__ == '__main__':
    app.run(debug=True)

The routes have been explained as the backend and backbone of the project, the routes in this blog form the backend of the project, handling requests from the user and interacting with the database to create, read, update, and delete articles. They also form the backbone of the project, providing the structure and organization that holds the application together. Without the routes, the web application would not be able to function.

Front-end

In a Flask web application, the front end is typically implemented using HTML, CSS, and JavaScript. HTML is used to structure the content of the web page, CSS is used to style the page, and JavaScript is used to add interactivity to the page. These technologies work together to create the visual layout and appearance of the web application.

One way to organize the front end of a Flask application is to use templates. Templates are HTML files that contain placeholders for dynamic content. When a route is accessed, the backend can render the template and fill in the placeholders with the appropriate content. This allows the front end to be separated from the back end and makes it easier to maintain and update the application.

Templating

Templates allow you to separate the presentation of your web application from the Python code that generates the content. This makes it easier to maintain and update the application, as you can change the appearance of the application without modifying the underlying code.

Flask uses the Jinja2 templating engine to parse and render templates. Jinja2 is a powerful and flexible templating engine that allows you to use placeholders, loops, and conditionals in your templates.

To use templates in Flask, you first need to create the templates themselves. Template files are typically stored in a templates directory within your Flask project. The templates are written in HTML, but they can also include placeholders and control structures from Jinja2.

For this project, we create a base.html file in our templates directory which is the parent template containing the styling and main codes to be inherited by the child template.The code below:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" 
    integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" 
    crossorigin="anonymous"
    />
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <nav class="navbar bg-light">
            <div class="container-fluid">
              <a class="navbar-brand" href="#">
                <img src="https://upload.wikimedia.org/wikipedia/commons/thumb/b/b7/Flash_and_circle.svg/40px-Flash_and_circle.svg.png">
                FAST Blog
              </a>
            </div>
        </nav>
        <div class="container-fluid">
            <button class="navbar-toggler" type="button" 
            data-bs-toggle="collapse" 
            data-bs-target="#navbar">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbar">
                <div class="navbar-nav">
                    <a class="nav-item nav-link" href="/home">Home</a>
                    {% if current_user.is_authenticated %}
                    <a class="nav-item nav-link" href="/logout">Logout</a>
                    {% else %}
                    <a class="nav-item nav-link" href="/login">Login</a>
                    <a class="nav-item nav-link" href="/sign-up">Sign Up</a>
                    {% endif %}
                    <a class="nav-item nav-link" href="/contact">Contact</a>
                    <a class="nav-item nav-link" href="/about">About</a>
                </div>    
            </div>
        </div>

    </nav>
    {% with messages = get_flashed_messages(with_categories=True) %}
      {% if messages %}
        {% for category, message in messages %}
        {% if category == "error" %}
        <div class="alert alert-danger alter-dismissible fade show" role="alert">
          {{ message }}
          <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        </div>
        {% else %}
        <div class="alert alert-success alter-dismissible fade show" role="alert">
          {{ message }}
          <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
        </div>
        {% endif %}

        {% endfor %}
      {% endif %}
    {% endwith %}
    <div class="container">{% block content %}{% endblock %}</div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" 
    crossorigin="anonymous"
    ></script>
    <br>
    <footer>
        <p class="footer" align="center">
            &copy; FAST blog <script>
                document.write(new Date().getFullYear())
            </script>
        </p>
    </footer>
</body>
</html>

This is HTML template file for the blog. It includes a head section with links to Bootstrap CSS stylesheets and a title block, a body section with a navigation bar, flash message alerts, and a content block. In the head section, the {% block title %}{% endblock %} directive indicates a block that can be replaced by a child template.

This allows the title of the page to be set dynamically by the child template. The body section includes a navigation bar with links to various pages, such as the home page, login page, and contact page. It also includes a block for displaying flash messages to the user, which are displayed using Bootstrap alert components. The {% with %}, {% if %}, and {% for %} directives are used to iterate over the flash messages and display them in the appropriate alert component. Finally, the body section includes a content block, denoted by the {% block content %}{% endblock %} directive, which can be replaced by a child template.

This allows the main content of the page to be set dynamically by the child template. Overall, this is the base template that provides the common structure and styling for a web application, with specific content being provided by child templates.

The child templates can be found here.

Conclusion

I used Flask to create my blog, and I found it to be a great choice for building a web app quickly and easily. Whether you're a beginner or an experienced developer, Flask is a powerful tool that can help you create amazing web applications. I hope this article has inspired you to try out Flask and create your web app.

This is what it looks like:

This is the link to the code on GitHub.If you feel you have learned a lot from this tutorial please don't hesitate to share it with your friends.

Thank, you.