Build Your Own Blog Quickly And Easily With Flask: A Step-By-Step Guide

Photo by Andrew Neel on Unsplash

Build Your Own Blog Quickly And Easily With Flask: A Step-By-Step Guide


1.0 Introduction

Are you interested in starting your own blog but don’t know where to start? Have you heard about Flask, but don’t know how to use it? This article is here to help! We provide a step-by-step tutorial on how to build a blog using Flask so that you can finally get your blog off the ground. Read on for an easy-to-follow guide on how to dive into the world of web development and create your very own blog!

In this article, we will walk you through the steps of creating a blog with flask. We'll cover setting up the web application framework, configuring a database, and integrating other features like authentication. By the end of this tutorial, you'll have your very own blog running live on the web!

Flask is a microframework written in Python that enables developers to quickly and easily create web applications. It is lightweight and can be used for small-scale projects and mostly it is easier to learn. With its simple yet powerful design flask makes building an accessible task for all developers of different skill sets.

Flask is said to be more pythonic because it does not require particular tools or libraries. It has no database abstraction layer, form validation, or any other components where pre-existing third-party libraries provide common functions.

In this tutorial, we will be concerned with writing the backend code and using a free template for the frontend code.

Concepts learned

  • Routing

  • Jinja2template

  • Responsive website using Bootstrap.

  • User authentication.

  • Database with flask SQLaclhemy

Prerequisites

To get the most out of this course you must have an understanding of python 3 or higher and also a little understanding of HTML and CSS you don't need to be an expert because this tutorial will walk you through everything you need to know to build your first blog application.

Before we dive deeper you should have the following installed:

DB Browser for SQLite (DB4S) is a high-quality, visual, open-source tool to create, design, and edit database files compatible with SQLite.

DB4S is for users and developers who want to create, search, and edit databases. DB4S uses a familiar spreadsheet-like interface, and complicated SQL commands do not have to be learned and also helps you to identify if a table was created or not.

Take a deep breath you have come so far!!!

Preparation

Before we begin writing code we need to create our folders and make sure our folders are in the proper workspace environment.

Creating your project folder

Based on personal preference I create my folder using git bash as follows

$ cd desktop
$ mkdir JezzyBlog
$ cd JezzyBlog
$ code .

code . will open the folder JezzyBlog in vs code

Creating virtual environment

A virtual environment is important here because it helps us to manage all dependencies used in the course of creating this blog. These dependencies may include the different packages installed amongst all others.

Virtual environment is important to python users because it helps to isolate different packages used for the project.

The creation of a virtual environment is done by executing the command venv: followed by the name of the environment, In this project, we will be using env as the name of our virtual environment.

On your terminal in vscode type the following command:

> py -m venv env

After running this command, a directory named env will be created. This is the directory that contains all the necessary executables to use and packages that this Python project would need.

Activating virtual environment

To activate the virtual environment we use the following:

> env\Scripts\Activate

After running this command, your vscode terminal should look like this:

(env) PS C:\Users\Jessica Ovabor\Desktop\JezzyBlog>

Also if it is not working you are probably doing something wrong or has misspelt something take a careful look at it and try again.

NOTE: This method of creating a virtual environment is only applicable to window users, it might differ for both Mac/Linux users. To learn more about virtual environments you can visit full stack python to learn more about the virtual environment.

Flask Installation

To install the flask framework we use the pip module which is a package installer for python that enables us to install different packages into our application.

pip install flask
pip freeze > requirement.txt

Congratulations you have successfully installed your flask application.

Tip: pip freeze >requirement.txt will help document all the packages installed in your application.

where requirement.txt is the name of file that document all the packages installed in the application

you can pip freeze after every installation or pip freeze after all your packages has been installed.

Getting started

We have successfully installed our flask application now we need to be able to use it in our application.

In our project directory that is the JezzyBlog create a new file and name it app.py and write the following code inside of it

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello'


if __name__=="__main__":
    app.run(debug=True)
  • You first import the Flask object from the flask package

  • You then create your Flask application instance with the name app and then you pass a special variable __name__ that holds the name of the current Python module. This argument is passed in the Flask class to create its instance, which is then used to run the application.

  • Once you create the app instance, you use it to handle incoming web requests and send responses to the user. @app.route is a decorator that turns a regular Python function into a Flask view function, which converts the function’s return value into an HTTP response to be displayed by an HTTP client, such as a web browser. You pass the value '/' to @app.route() to signify that this function will respond to web requests for the URL /, which is the main URL.

    The hello() view function returns the string 'Hello, World!' as a response.

    Save and close the file

  • app.run(debug=True) is used to run your web application, and in this case, we set debug to True

In your terminal type the following command app.py to run your web application in a Flask environment

(env) PS C:\Users\Jessica Ovabor\Desktop\JezzyBlog>app.py

Once the application is running the output will be something like this:

 * Serving Flask app 'app'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 143-974-277

This code block means that:

  • The name of the application you’re running.

  • The environment in which the application is being run.

  • Debug mode: on signifies that the Flask debugger is running. This is useful when developing because it gives us detailed error messages when things go wrong, which makes troubleshooting easier and it points directly to where things go wrong.

  • The application is running locally on the URL http://127.0.0.1:5000/, 127.0.0.1 is the IP that represents your machine’s localhost and :5000 is the port number.

Open a browser and type in the URL http://127.0.0.1:5000/, you will receive the string Hello as a response, this confirms that your application is successfully running.

Creating HTML files

Our current application only displays a message without any HTML. The next step is to learn how to display and incorporate HTML into our application

Flask provides a render_template() helper function a templating package used to generate output from a template file based on the Jinja template engine that is found in the application's templates folder.

render_template() looks for the templates folder in the application and runs the HTML files inside.

Firstly you create a folder called templates inside the JezzyBlog folder in our application and this is where all our .html files will be and will also import the render_template() helper function that lets you render HTML template files that exist in the templates the folder created.

To see fully how the render_template() the function works we create a index.html file inside our template and input the following code:

<!DOCTYPE html>
<html lang="en">
  <head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <title>JezzBlog</title>
</head>
<body>
   <h1>Welcome to Jezz<span class= "h1-color">B</span>log</h1>
</body>
</html>

Then in your app.py type the following code and delete the hello() since it is of no use anymore.

from flask import Flask,render_template
app = Flask(__name__) 
@app.route('/')
def index()
   render_template("index.html") 

if name=="__main__": 
   app.run(debug=True)

Save the file and use your browser to navigate to http://127.0.0.1:5000/ . This time the browser should display the text Welcome to JezzBlog in an <h1> tag.

In addition to the templates folder, Flask web applications also typically have a static folder for hosting static files, such as CSS files, JavaScript files, and media such as images the application might use.

You can create a style.css style sheet file to add CSS to your application. First, create a directory called static inside your JezzyBlog directory and Then create another directory called css inside the static directory to host .css files. This is typically done to organize static files in dedicated folders, as such, JavaScript files typically live inside a directory called js, images are put in a directory called images (or img), and so on.

Your directory should be like this:

Then open a style.css file inside the css directory and add the following CSS rule to the file

h1 {
    text-align: center;
    padding: 10px;

}
.h1-color{
    color: red;
}

The CSS code will centre the text, and add a little padding of 10px to the <h1> tags.

.h1-color is a class property in CSS it will change the colour of B part of the blog to red.

Save and close the file.

Next, open the index.html template file

You’ll add a link to the style.css file inside the <head> section of the index.html template file to link the CSS file to the HTML file:


<!DOCTYPE html>
<html lang="en">
  <head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>JezzBlog</title>
<link rel="stylesheet" href="{{ url_for('static', filename= 'css/style.css') }}">
</head>
<body>
   <h1>Welcome to Jezz<span class= "h1-color">B</span>log</h1>
</body>
</html>

Here we use the url_for() helper function to generate the location of the file. The first argument specifies that the file is static and the second argument is the path of the file inside the static directory.

Save and close the file.

Upon refreshing the index page of your application, you will notice that the text Welcome to JezzBlog is now in black with the B in red, centred.

You can use the CSS language to style the application and make it more appealing suiting your taste. However, we will be using Bootstrap which provides easy-to-use components for styling your application.

Next to work with the DRY(do not repeat yourself) we are going to create a base.html file which is a base template file, where all other .html files inherits from. You can also avoid unnecessary code repetition. See Template Inheritance in Jinja for more information.

To make a base template, first create a file called base.html inside your templates directory:

<!DOCTYPE html>
<html lang="en">
  <head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
     <!-- Bootstrap CSS -->
    <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">
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> 


    <title> {%block title %} blog Home{% endblock %}</title>
  </head>
  <body>
    <!-- Responsive navbar-->
   <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
   <a class="navbar-brand" href="#!">Jezz<span class= "h1-color">B</span>log</a>
   <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span></button>
 <div class="collapse navbar-collapse" id="navbarSupportedContent">
 <ul class="navbar-nav ms-auto mb-2 mb-lg-0">
 <li class="nav-item"><a class="nav-link href="#top">Home</a></li>
 <li class="nav-item"><a class="nav-link" href="/blog">Blog</a></li>
 <li class="nav-item"><a class="nav-link" href="/create">Create</a></li>
 <li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
 <li class="nav-item"><a class="nav-link" href="/about">About</a></li>
 <li class="nav-item"><a class="nav-link active" aria-current="page" id= "nav-item2"href="/contact">Contact</a></li>
                </ul>
            </div>
        </div>
    </nav>
    <!-- Page header with logo and tagline-->
    <header class="py-5 bg-light border-bottom mb-4">
    <div class="container">
    <div class="text-center my-5">
    <h1 class="fw-bolder">Welcome to Jezz<span class= "h1-color">B</span>log</h1>
   <p class="lead mb-0">Home of quality information, latest tech news, fashion and definitely gossips.</p>

            </div>
        </div>
    </header>

<!-- Footer-->
    <footer class="py-5 bg-dark">
        <div class="container"><p class="m-0 text-center text-white">Copyright &copy; JezzBlog 2022</p></div>
    </footer>
      <div class="other-container">
        {% block content %} {% endblock %}
    </div>
    <!-- Bootstrap core JS-->
    <script src="https://cd
</body>n.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
    <!-- Core theme JS-->
    <script src="{{ url_for('static', filename='index.js') }}"> </script>
</html>

Save and close the file once you’re done editing it.

Most of the code is standard HTML and code required for Bootstrap. The <meta> tags provide information for the web browser, the <link> tag links the Bootstrap CSS files, and the <script> tags are links to JavaScript code that allows some additional Bootstrap features, check out the Bootstrap documentation for more.

  • {% block title %} {% endblock %}: A block that serves as a placeholder for a title, you’ll later use it in other templates to give a custom title for each page in your application without rewriting the entire <head> section each time.

  • {% block content %} {% endblock %}: Another block that will be replaced by content depending on the child template (templates that inherit from base.html) that will override it.

Now that you have a base template, you open the index.html file and inherit from the base.html as follows:

{% extends 'base.html' %}

{% block content %}
    <h1>{% block title %} Welcome to JezzBlog {% endblock %}</h1>
<!--responsive navbar after a user logout-->
    <li class="nav-item"><a class="nav-link" href="#">Home</a></li>
                        <li class="nav-item"><a class="nav-link" href="/signup">Signup</a></li>
                        <li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
                        <li class="nav-item"><a class="nav-link active" aria-current="page" id= "nav-item2"href="/contact">Contact</a></li>
{% endblock %}

In this new version of the index.html template, we use the {% extends %} tag to inherit from the base.html template. You then extend it by replacing the content block in the base template with what is inside the content block in the code block.

This content block contains an <h1> tag with the text Welcome to JezzBlog inside a title block, which in turn replaces the original title block in the base.html template with the text Welcome to JezzyBlog. This way, you can avoid repeating the same text twice, as it works both as a title for the page and a heading that appears below the navigation bar inherited from the base template.

Template inheritance also gives you the ability to reuse the HTML code you have in other templates (base.html in this case) without having to repeat it each time it is needed.

Save and close the file and refresh the index page on your browser. You’ll see your page with a navigation bar and a styled title.

Setting up the Database

In this step, you’ll set up a database to store data: the blog posts for your application, logout user, signup user, and sign-in user.

You’ll use a flask SQLAlchemy file to store your data and use it to interact with the database, which is readily available in the standard Python library.

There are a lot of database engine such as PostgreSQL or MySQL, you’ll need to use the proper URI as they may differ for the sake of this tutorial we wll be using the flask sqlalchemy.

Open your terminal and install flask sqlalchemy

pip install Flask-SQLAlchemy 
pip freeze > requirement.txt

Open your app.py inside your JezzyBlog directory:

from flask import Flask,render_template
from flask_sqlalchemy import SQLAlchemy
import os
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"] = '78f81b3cb11851782d6ead66'
db = SQLAlchemy(app)
@app.route('/')
def index()
   render_template("index.html") 

if name=="__main__": 
   app.run(debug=True)

Here we did the following:

  • You first import the SQLAlchemy module and then open a connection to a database file named blog.db, which will be created once you run the Python file.

  • you import the os module, which gives you access to miscellaneous operating system interfaces

  • To get a 12-digit (any number of choice) secret key, run this in the terminal: python import secrets.token_hex(12) exit() Copy the token from the terminal and paste it as the secret key in app. config above

  • db = SQLAlchemy(app) makes an instance of the app in the database.

  • SQLALCHEMY_DATABASE_URI: The database URI to specify the database you want to establish a connection with. In this case, the URI follows the format sqlite:///path/to/database.db. You use the os.path.join() function to intelligently join the base directory you constructed and stored in the basedir variable, and the database.db file name. This will connect to a database.db database file in your flask_app directory. The file will be created once you initiate the database.

  • SQLALCHEMY_TRACK_MODIFICATIONS: A configuration to enable or disable tracking modifications of objects. You set it to False to disable tracking and use less memory. For more, see the configuration page in the Flask-SQLAlchemy documentation.

To learn more about Flask sqlalchemy you can visit this tutorial

  1. Creating tables

With the database connection established and the database object created, you’ll use the database object to create a database table for User , Post, Message which is represented by a model.

To define aUsertable as a model, we first create a table of users using the UserMixin which represents each post done by a particular user

To use the UserMixin will have to install the Flask-login. Go to your and type the following:

pip install Flask-Login

Then import the UserMixin from Flask-Login. add the following code in your app.py file:

from flask_login import UserMixin

Now as guessed we can now build the User model

#every signed in user must have the following details
class User(db.Model, UserMixin):
    __tablename__ = 'users'

    id = db.Column(db.Integer(), primary_key=True)
    firstname = db.Column(db.String(255), nullable=False, unique=True)
    lastname = db.Column(db.String(255), nullable=False, unique=True)
    username = db.Column(db.String(255), nullable=False, unique=True)
    email = db.Column(db.String(255), nullable=False, unique=True)
    password_hash = db.Column(db.String(10), nullable=False)
    def __repr__(self):
        return f"User <{self.username}>"
  • id: The user ID. You define it as an integer with db.Integer. primary_key=True defines this column as a primary key, which will assign it a unique value by the database for each entry (that is a user).

  • firstname: The user's first name. A string with a maximum length of 255 characters. nullable=False signifies that this column should not be empty. unique=True signifies each firstname should be unique.

  • lastname: The user's last name. A string with a maximum length of 255 characters. nullable=False signifies that this column should not be empty.unique=True signifies each lastname should be unique.

  • username: The user name. A string with a maximum length of 255 characters. nullable=False signifies that this column should not be empty.unique=True signifies each lastname should be unique.

  • email: The user email. A string with a maximum length of 255 characters. unique=True signifies that each email should be unique for each student. nullable=False signifies that this column should not be empty.

  • password_hash: The user password. A string with a maximum length of 10 characters. unique=True signifies that each email should be unique for each student.

See the SQLAlchemy documentation for more information

NOTE: The UserMixin returns as the username as its represation for the user table

Post Model

The Post model create a table that consists of post created by a user and the user or author who created it. The UserMixin returns as the title of the post as its representation for the post table.

Before we create the model we need to get the date and time each post was created to do that we import an inbuilt module that comes with python called the datetime

from datetime import datetime

The post table is given below:

class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False, unique=True)
    slug = db.Column(db.String(100), nullable=False, unique=True)
    content = db.Column(db.Text, nullable=False)
    posted_by = db.Column(db.String(20), nullable=False, default='N/A')
    posted_on = db.Column(db.DateTime, nullable=False,
                            default=datetime.utcnow())
    user_id=db.Column(db.Integer, db.ForeignKey('user.id'))


    def __repr__(self):
        return f"Posts:<self.title>"#initilising mydatabase

Message model

The message model indicates the message sent from our site visitor. You can also view it as a way of keeping track of every visitor that fills that contact. It returns text as its representation.

The message table is given below:

class Message(db.Model):
    __tablename__ = 'message'

    id = db.Column(db.Integer(), primary_key=True)
    usernames= db.Column(db.String(255), nullable=False)
    email = db.Column(db.String(255), nullable=False)
    needs= db.Column(db.Text(255), nullable=False)
    text = db.Column(db.String(255))
    def __repr__(self):
        return f"Message <{self.text}>"

A quick tip: your tables can have more fields depending on what you want.

  1. Database Initialisation
@app.before_first_request
def create_tables():
    db.create_all()

The above code ensures that a database file with our given name is created for the first time and only if the file does not exist will it be created.

when we run the terminal a blog.db file will appear in our project directory.

After we run we can then use DB browser for SQlite what we previously installed to check what is happening in our database as vscode doesn't show us what is happening in our database.

Routing

Routing means mapping the URLs to a specific function that will handle the logic for that URL. Routing makes navigation easier.

Routing is done using the @app.route() function in which a desired URL is passed as an argument.

  • Home page

    for our home page, we will be using the route we created earlier to represent a static page in which a user is yet to sign in

@app.route('/')
def index()
   render_template("index.html")
  • About page

    This page tells us about the developer and what the whole application is all about

@app.route('/')
def about()
   render_template("about.html")
  • Contact page

    The contact page allows us to receive constructive feedback from users, on how the site can be improved or if they have any questions.

Before we can write the code for our contact page we need to import the following into our app.py:

from flask import Flask,render_template,url_for,redirect,flash,request

Here is a brief explanation of the above code

  • redirect as the name applies helps us to easily navigate back to a certain page after an action has been applied.

  • request indicate we are performing an HTTP request.

  • flash indicate that a message will be displayed after an action has been completed.

    The code for the contact page.

      @app.route("/contact", methods=['GET', 'POST'])
      def contact():
          if request.method == 'POST':
              user_name = request.form.get('username')
              user_email = request.form.get('email')
              user_need = request.form.get('need')
              user_message = request.form.get('message')
              new_contact_message = Message(email=user_email,
                              usernames=user_name,needs=user_need,text=user_message)
              db.session.add(new_contact_message)
              db.session.commit()
              flash("Your message has been succesfully sent")
              return redirect('index')
          else:
              return render_template('contact.html')
    

    This indicates it collects contact information from our contact.html and then stores the information collected to your message table in our database it flashes a message using flash() to display the message that was passed to it and after that, it redirects the user to the home page.

  • GET the method gets data from the database to display it to users and it is a default argument.

  • POST the method allows the user to make a request to the database.

  • request.form.get(): The parameter passed into it is an argument from the name value in the HTML

To learn more about the HTTP method visit Mozilla docs.

NOTE: To view these messages we make use of our DB Browser.

Frontend code for each page

This section of the article gives a detailed explanation of how we use templates and static and how we can easily manipulate them.

Recall from earlier that we have created our base.html file from which other .html pages can inherit. In this section, we will be creating the about.html, create.html,edit.html,blog.html ,signin.html ,signup.html .

  • About.html: it gives us information about the developer and what the site is all about.

create a new html file about.html inside the templates folder and input the following code:

{% extends 'base.html' %}

{% block content %}
{%block head%}
<title>Contact</title>
{%endblock%}
{%block body%}




<div class="container">
    <div class=" text-center mt-5 ">

        <h1 >Contact Us</h1>


    </div>


<div class="row ">
  <div class="col-lg-7 mx-auto">
    <div class="card mt-2 mx-auto p-4 bg-light">
        <div class="card-body bg-light">
       <!--form-->
        <div class = "container">
                         <form action="/contact" method="POST">



        <div class="controls">

            <div class="row">
                <div class="col-md-6">
                    <div class="form-group">
                        <label for="form_name">Firstname *</label>
                        <input id="form_name" type="text" name="fname" class="form-control" placeholder="Please enter your firstname *" required="required" data-error="Firstname is required.">

                    </div>
                </div>
                <div class="col-md-6">
                    <div class="form-group">
                        <label for="form_lastname">Lastname *</label>
                        <input id="form_lastname" type="text" name="lname" class="form-control" placeholder="Please enter your lastname *" required="required" data-error="Lastname is required.">
                                                        </div>
                </div>
            </div>
            <div class="row">
                <div class="col-md-6">
                    <div class="form-group">
                        <label for="form_email">Email *</label>
                        <input id="form_email" type="email" name="email" class="form-control" placeholder="Please enter your email *" required="required" data-error="Valid email is required.">

                    </div>
                </div>
                <div class="col-md-6">
                    <div class="form-group">
                        <label for="form_need">Please specify your need *</label>
                        <select id="form_need" name="need" class="form-control" required="required" data-error="Please specify your need.">
                            <option value="" selected disabled>--Select Your Issue--</option>
                            <option >Unable to create new post</option>
                            <option >Improved features</option>
                            <optio>Others</option>
                            <option >Other</option>
                        </select>

                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-md-12">
                    <div class="form-group">
                        <label for="form_message">Message *</label>
                        <textarea id="form_message" name="message" class="form-control" placeholder="Write your message here." rows="4" required="required" data-error="Please, leave us a message."></textarea
                            >
                        </div>

                    </div>


                <div class="col-md-12">

                    <input type="submit" class="btn btn-success btn-send  pt-2 btn-block
                       mt-3 " value="Send Message" >

            </div>

            </div>


    </div>
     </form>
    </div>
        </div>


</div>
    <!-- /.8 -->

</div>
<!-- /.row-->

</div>
</div>

{%endblock%}
  • Create.html: The create.html file allows the user to create a blog post.

create a new html file create.html inside the templates folder and input the following code:

{% extends 'base.html' %}

{% block content %}
{% block head %}
<title>New Post</title>

{% endblock%}

{% block body %}
<h1>New Post</h1>
<hr>

<form action= "/create"method="POST">
    <label for="title">Title:</label>
    <input class="form-control" type="text" name="title" id="title" placeholder="Enter Title" aria-label="Enter Title">
    <br>
    <label for="slug">Slug:</label>
    <input class="form-control" type="text" name="slug" id="author" placeholder="Enter Subtitle"
        aria-label=" Enter Author">
        <br>
    <label for="author">Author:</label>
    <input class="form-control" type="text" name="author" id="slug" placeholder="Enter Author"
        aria-label=" Enter Author">
    <br>
    <label for="content">Content</label>
    <textarea class="form-control" id="content" name="content" placeholder="Enter Content" aria-label=" Enter Content"
        rows="3"></textarea>
    <br>

    <input type="submit" class="btn btn-success" value="Post">
</form>
<hr>
{% endblock%}
  • Edit.html: The edit.html file allows the user to edit a blog post.

create a new html file edit.html inside the templates folder and input the following code:

{% extends 'base.html' %}

{% block content %}

<h1>Edit Post </h1>
    <hr>

    <form action="/edit/{{blog.id}}" method="POST">
        <label for="content">Title:</label>
        <input class="form-control" type="text" name="title" id="title" value="{{blog.title}}">
        <br>
        <label for="content">Slug:</label>
        <input class="form-control" type="text" name="slug" id="slug" value="{{blog.slug}}">

        <br>
        <label for="post">Content:</label>
        <textarea class="form-control" id="post" name="content" rows="3">{{blog.content}}</textarea>
        <br>
        <input type="submit" class ="btn btn-success" value="Save">

    </form>
    <hr>

{%endblock%}
  • blog.html: The blog.html allows the user to see a list of blog posts created by the user and all other users and also proving them with options to delete and edit a blog post.

create a new html file blog.html inside the templates folder and input the following code:

{% extends 'base.html' %}

{% block content %}
{% block body %}
<div>
      <h1 style="display: inline">Create New Posts:</h1>
        <a style="display: inline" class="btn btn-success float-right " href="/create">+ New Post</a>
    </div>
    <br>
    <hr>



<!--displaying blog post in blog.html-->
    {% for blogs in blog%}
    <div class="card mb-4 w-75">

      <div class="card-body">
    <h2>{{blogs.title}}</h2>
    <h3 style="color:gray;">{{blogs.slug}}</h3>

    {% if blogs.author %}
    <small>Written By {{blogs.posted_by}} on {{blogs.posted_on}}</small>
    {% else%}
    <small>Authored by {{blogs.posted_by}}  on {{blogs.posted_on}}
    </small>
    {% endif %}
    <p style="white-space: pre-wrap">{{blogs.content}}   

  </p>
  </p>
    <!--edit a blog post-->
    <a  class="btn btn-success btn-send pb-2 pt-2 btn-block
    mt-3" href="/edit/{{blogs.id}}">Edit →</a>
        <!--delete a blog post-->
    <a  class="btn btn-danger btn-send pb-2 pt-2 btn-block
    mt-3" href="/delete/{{blogs.id}}">Delete →</a>

    <br>

  </div>
</div>
    {% endfor %}

{%endblock%}
  • signup.html: The signup.html allows users to sign up to our application.

create a new html file signup.html inside the templates folder and input the following code:

{% extends 'base.html' %}

{% block content %}
{% block body %}

<div class="container">
    <div class="row">
        <div class="col-lg-3 col-md-2"></div>
        <div class="col-lg-6 col-md-8 login-box">
            <div class="col-lg-12 login-key">
                <i class="fa fa-key" aria-hidden="true"></i>
            </div>
            <div class="col-lg-12 login-title">
              <p> New user welcome to Jezz<span class= "h1-color">B</span>log</p>
            </div>

            <div class="col-lg-12 login-form">
                <div class="col-lg-12 login-form">




                    <form "{{url_for('signup')}}" method = "POST">

                        <div class="form-group">
                            <label class="form-control-label">FIRSTNAME</label>
                            <input type="text" class="form-control" name='first_name'>
                        </div>
                        <div class="form-group">
                            <label class="form-control-label">LASTNAME</label>
                            <input type="text" class="form-control" name='last_name' i>
                        </div>
                        <div class="form-group">
                            <label class="form-control-label">USERNAME</label>
                            <input type="text" class="form-control" name='username'>
                        </div>
                        <div class="form-group">
                            <label class="form-control-label">EMAIL</label>
                            <input type="text" class="form-control" name='email' i>
                        </div>
                        <div class="form-group">
                            <label class="form-control-label">PASSWORD</label>
                            <input type="password" class="form-control" name='password'>
                        </div>
                        <div class="form-group">
                            <label class="form-control-label">CONFIRM PASSWORD</label>
                            <input type="password" class="form-control" name='confirm_password' i>
                        </div>

                        <div class="col-lg-12 loginbttm">
                            <div class="col-lg-6 login-btm login-text">
                                <!-- Error Message -->
                            </div>
                            <div class="col-lg-6 login-btm login-button">
                                <button type="submit" class="btn btn-outline-primary">signup</button>

                            </div>
                            <p> Already have an account? <a class="a-link" href="{{url_for('login')}}">Login</a> </p>
                        </div>


                    </form>



                </div>
            </div>
            <div class="col-lg-3 col-md-2"></div>
        </div>
    </div>
{%endblock%}
  • signin.html: The signin.html allows users to sign in to our application.

create a new html file signin.html inside the templates folder and input the following code:

{% extends 'base.html' %}

{% block content %}
{% block body %}

<div class="col-lg-12 login-title">
              <p> Welcome back to Jezz<span class= "h1-color">B</span>log</p>
            </div>
       <div class="col-lg-12 login-form">
                <div class="col-lg-12 login-form">
                    <form method = "POST">
                        <div class="form-group">
                            <label class="form-control-label">USERNAME</label>
                            <input type="text" class="form-control" name=username>
                        </div>
                        <div class="form-group">
                            <label class="form-control-label">PASSWORD</label>
                            <input type="password" class="form-control" name=password i>
                        </div>

                        <div class="col-lg-12 loginbttm">
                            <div class="col-lg-6 login-btm login-text">
                                <!-- Error Message -->
                            </div>
                            <div class="col-lg-6 login-btm login-button">
                                <button type="submit" class="btn btn-outline-primary">LOGIN</button>
                            </div>
                        </div>
                    </div>
                    <p> Dont have an account? <a class="a-link" href="{{url_for('signup')}}">Signup</a> </p>
                </div>
                    </form>
                </div>
            </div>
            <div class="col-lg-3 col-md-2"></div>
        </div>

{%endblock%}

Note: WE DO NOT NEED a new html file delete.html and signout.html inside the templates folder because there is nothing to display when a user deletes or signout from our application. whenever a user deletes or a signout from our page we will redirect them to the blog and home page respectively.

User Activity and code for each page

In this section of the article, we are going to write the code block for each page and import different modules to help with our application and also show the different activity that can be performed by each user on our application.

Before we do that we need to import this number of modules in addition to the ones we have installed before.

open your app.py and input the following:

from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import login_user, logout_user, login_required, LoginManager

After this import, your app.py the file should look like this in the beginning:

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

Here is a brief explanation of the above code block:

werkzeug.security is an inbuilt module that comes with Flask. It provides a library for hashing passwords giving us the opportunity to store users' password hashed rather than as it was given using the generate_password_hash() and then use then check_password_hash_() to decode the generated password whenever a login is attempted. This help to improve the security

  1. User Registration

User registration means creating a new user account, which is a record in the database describing how you will prove your identity.

Before you can even create a post you must have an account that clearly indicates your identity so you can use the full functionality.

  • User signup()

open your app.py and input the following code :

# new user signup
@app.route("/signup" ,methods=['GET', 'POST'])
def signup():
     if request.method == 'POST':
      first_name=request.form.get('first_name')
        last_name= request.form.get('last_name')

        username = request.form.get('username')
        email = request.form.get('email')
        password = request.form.get('password')
        confirm_password1 = request.form.get('confirm_password')

        username_exists = User.query.filter_by(username=username).first()
        if username_exists:
            return redirect(url_for('signup'))

        email_exists = User.query.filter_by(email=email).first()
        if email_exists:
            return redirect(url_for('signup'))

        password_hash = generate_password_hash(password)

        new_user = User(username=username, email=email,lastname=last_name,firstname=first_name,password_hash=password_hash)
        db.session.add(new_user)
        db.session.commit()


        return redirect(url_for('blog'))

     return render_template('signup.html')

Here we created asignup() the function which collects user data from the signup.html templates, if the username_exists and email_exists already, it will reload the signup page for the user to make another attempt of the username and email otherwise it will add the new users account to the "user table" with a password configured as hashed using the User model we created earlier and then redirect them back to the blog.html page.

request.form.get() takes an argument from the value passed to the name variable in our HTML form

db.session.add(): add new user to our database anytime an account is created.

db.session.commit(): commits the current action.

  1. User Authentication:

It verifies the identity of a user attempting to gain access to our blog(signin) and deactivates a user's current session when it is done(sign out)

  • User signin()

    The signin() function collects user data from the signin.html form template. it checks if a username_exists and email_exists if yes it will reload the signin.html page if otherwise, it redirects the user to the login

    For us to properly make use of the Flask-Login we need to pass our app as an instance of the LoginManager . This tells which Flask-Login file it is currently listening to.

login_manager = LoginManager(app)
#login manager loader
@login_manager.user_loader
def user_loader(id):
    return Users.query.get(int(id))

@login_manager.user_loader indicate that protected routes will only be rendered to user who are logged in.

The code is given below:

#signin
@app.route("/login" ,methods=['GET', 'POST'])
def signin():
    username = request.form.get('username')
    password = request.form.get('password')

    user = User.query.filter_by(username=username).first()

    if user and check_password_hash(user.password_hash, password):

        login_user(user)

        return redirect(url_for('blog'))




    return render_template('signin.html')

Here the signin() function checks the user details as entered in the signin.html form template with the record(username and password) in the user table if it matches, if yes, then it redirects the user to the blog.html page otherwise it prompts the user to re-enter the correct details.

  • User signout()

    Flask-Login module provides us with an inbuilt logout_user() function, which helps us deny users access to certain functionality when they click the logout button.

    This logout does not require an .html file because we have nothing to display whenever a user logout we should redirect them to the index.html page and no route is required

    The code is given below:

#logout
@app.route('/logout')
def signout():
    logout_user()
    return redirect(url_for('index'))
  1. User's Operation

In this section we will give a detailed explaination of what can kind of operation a user will be able to perform.This the CRUD operation which signifies a user will be able to create, read,update and delete a post.

  • Creating a post

    After a user logs in they should be able to create a new post .

#create a blog post 
@app.route("/create",methods=['GET', 'POST'])
def create():
    if request.method == 'POST':
        post_title = request.form.get('title')
        post_slug= request.form.get('slug')
        post_content = request.form.get('content')
        post_author = request.form.get('author')
        new_post = Post(title=post_title,
                        content=post_content, slug = post_slug,posted_by=post_author)
        db.session.add(new_post)
        db.session.commit()
        return redirect(url_for('blog'))
    else:
        all_posts = Post.query.order_by(Post.posted_on).all()
        return render_template('create.html', blog=all_posts)
  • Editing/Updating a post

    Whenever a user creates a post they must be able to make changes to the post.

#edit a blog post from the database
@app.route('/edit/<int:id>', methods=['GET', 'POST'])
def edit(id):

    post_to_edit = Post.query.get_or_404(id)
   if current_user.username =post_to_edit.post_author
    if request.method == 'POST':
        post_to_edit.title = request.form['title']
        post_to_edit.slug = request.form['slug']

        post_to_edit.content = request.form['content']
        db.session.commit()
        return redirect(url_for('blog'))
    else:
        return render_template('edit.html', blog=to_edit)

Here edit(id) function takes in a parameter id which compares the post_author from the database and checks if it matches with the logged in username in the database if yes, the user is allowed to make an edit otherwise he will not be able.

  • Deleting a post

      #delete a blog post from the database
      @app.route('/delete/<int:id>')
      def delete(id):
          post_to_delete = Post.query.get_or_404(id)
          if current_user.username == to_delete.post_author:
            db.session.delete(post_to_delete)
    
            db.session.commit()
          return redirect(url_for('blog'))
    

    Here delete(id) function takes in a parameter id which compares the post_author from the database and checks if it matches with the logged in username in the database if yes, the user is allowed to make an delete after which will be redirected to the blog.html blog page otherwise he will not be able.

    1. Rendering/Displaying the post

      This shows that whenever a user create or edit a new post it will be rendered in the blog.html

      
       @app.route('/blog')
       def blog():
           if request.method == 'POST':
               post_title = request.form['title']
               post_content = request.form['post']
               post_author = request.form['author']
               post_slug = request.form['slug']
               new_post = JezzyBlog(title=post_title,
                               content=post_content,slug=post_slug, posted_by=post_author)
               db.session.add(new_post)
               db.session.commit()
               return redirect('blog')
           else:
               all_posts = Post.query.order_by(Post.posted_on).all()
               return render_template('blog.html', blog=all_posts)
      

      Here we created a skeleton which matches with the different field an author or user has to field to be able to create or edit a post with this skeleton the user will be able to render all post created from create.html and edit.html in the blog.html