A Simple Blogging App with Flask and Python

Photo by Chris Ried on Unsplash

A Simple Blogging App with Flask and Python

A simple but comprehensive guide on how to create a blog website from scratch

Introduction

Flask is a web application framework (or, a micro web framework) written in Python.

Bear in mind that Flask is used for the creation of web applications, and not mobile applications.

A project such as the creation of a blogging app requires you to know what a blogging app is, and understand what it entails. For me, this meant;

1) The Frontend part: This covers the landing page (home page) and blog posts. Depending on what you require your blogging app to do, you can have input fields like the registration and login forms, about page etc.

2) The Backend part: This is the functionality of the blogging app. This is where routing, login and authentication, as well as database (for storage and retrieval of data), come in.

3) Researching

Requirements

  • Basic knowledge of HTML, CSS, Python, Flask and Database (CRUD)

  • A code editor (For the context of this article, Visual Studio Code(VSCode) was used)

  • Good command of command line interface (CLI)

  • Python (version 3) should be installed

Setting Up

  • Create a project folder: Having a dedicated folder for each project helps with organisation. All the files and folders for this project would be here.

  • Create and activate a virtual environment: Each python project requires its virtual environment. It is highly recommended as it keeps all the dependencies required by a particular project separate by creating isolated python virtual environments for each project. This is done in the project's root directory.

  • Install Flask

    To install all requirements using the terminal, use pip install <name of requirement>

    Note that all these are done in the terminal using CLI.

Frontend

Here the basic knowledge of HTML and CSS will come in handy as the front end will be created from scratch. In the project directory(folder) create;

A 'templates' folder. All HTML files for the blog will be placed here.

A 'static' folder. All CSS files will be placed here.

In this case, the front end of the blogging app required the about.html, base.html, blog.html, contact.html, edit.html, index.html, post.html, signin.html, signup.html. The base.html file was created to make things easier by extending it to other HTML files. This is a good practice known as template inheritance. It is a method of adding all the elements of an HTML file into another without copy-pasting the entire code. This is done with the use of this element:

{% block content %}

{% endblock content %}

The base.html file will contain this:

<!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 rel="stylesheet" href="">
    <title></title>
</head>
<body>


        {% block content %}

        {% endblock content %}
</body>
</html>

while other HTML files will look like this:

{% extends 'base.html'%}
{% block content %}

{% endblock content %}

Note that this is before you fill in what you want or need in the individual HTML files. This is just the skeleton.

Add a navigations bar in the base.html file using this:

<a href=""</a>

Use the script element to add the year to your blog in the footer as such:

<footer>
        <p>
            &copy;<name of blog app> <script>
                document.write(new Date().getFullYear())
            </script>
        </p>
    </footer>

With a basic knowledge of HTML, we create forms for the contact.html, edit.html, post.html, signin.html and signup.html .

Here is an example of this:

<form action="" method="POST">
        <div>
            <div class="">
                <h3>Create Your Blog Here</h3>
            </div>
            <div>
                <label for="">Title</label><br>
                <input type="" placeholder ="" name="" class="" id="">
            </div><br><br>
            <div>
                <label for="">Author</label><br>
                <input type="" placeholder ="" name="" class="" id="">
            </div><br><br>
            <div>
                <label for="">Blog content</label><br>
                <textarea rows="" placeholder ="" name="" class="" id=""></textarea>
            </div>
            <div>
                <input type="submit" value="Create" class="btn" id="post-btn"> 
            </div>
        </div>  
    </form>

To link our HTML templates to our app.py file, we employ the use of Jinja. Jinja makes the HTML files dynamic by allowing us write code similar to the Python syntax in special placeholders, {{ }}.

<span class="title"><h1>{{ blog.title }}</h1></span>
<span>Posted by {{ blog.author }} on {{ blog.date_created.strftime('%B %d, %Y') }}</span><br><br>
<div class="container">     
    <div class="post">         
        <p>{{ blog.content }}</p>         
        <a href="{{url_for('edit', blog_id=blog.id)}}">edit</a>         
        <a href="{{url_for('delete_blog', blog_id=blog.id)}}">delete</a>                 
    </div>
</div>

Don't forget to link your CSS folder and file to your HTML using this:

<link rel="stylesheet" href="{{url_for('static', filename='css/main.css')}}">

Backend

Create an app.py file in the project directory. This is where the functionality of the app will be done. The codes for the database and routing will be written here.

We already have Flask installed, so, the next step is to import the modules we need.

We would need the Flask, render_template, url_for, request and redirect modules, for a start. Along the line we would import more modules and install the requirements we need.

To execute the code in the app.py file we would be using the if __name__ == '__main__' idiom. This runs the program directly through the Python interpreter. It is also necessary to set debug to be True , so as to easily debug our program as it runs.

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

Note that the if __name__ == '__main__' idiom should be written at the bottom of the app.py file.

Next, install flask_sqlalchemy and import SQLAlchemy from it.

SQLAlchemy is basically referred to as the toolkit of Python SQL that provides developers with the flexibility of using the SQL database. The benefit of using this particular library is to allow Python developers to work with the language’s own objects, and not write separate SQL queries. They can basically use Python to access and work with databases. SQLAlchemy is also an Object Relational Mapper which is a technique used to convert data between databases or OOP languages such as Python.
Flask-SQLAlchemy is an extension for Flask that adds support for SQLAlchemy to your application. It simplifies using SQLAlchemy with Flask by setting up common objects and patterns for using those objects, such as a session tied to each web request, models, and engines.

Next, we import the os module for configuration and interaction with the operating system, then proceed to construct a path for the SQLite database file and store the path of the base directory to a variable. We will also be configuring SQLALCHEMY_DATABASE_URI, SQLALCHEMY_TRACK_MODIFICATIONS and secret key.

NOTE: We set the SQLALCHEMY_TRACK_MODIFICATIONS to False to disable tracking and use less memory and set a secret key for security.

To set a secret key, use the terminal and do this:

import secrets
secret_key = secrets.token_hex(16)
# example output, secret_key = 000d88cd9d90036ebdd237eb6b0db000
app.config['SECRET_KEY'] = secret_key

Lastly, after configuring SQLAlchemy by setting a database URI and disabling tracking, create a database object using the SQLAlchemy class, passing the application instance to connect your Flask application with SQLAlchemy. Store your database object in a variable called db. You’ll use this db object to interact with your database.

Your app.py file should look like this:

from flask import Flask, render_template, url_for, request, redirect
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"] = '000d88cd9d90036ebdd237eb6b0db000'


db = SQLAlchemy(app)

Database

The database is needed for the storage and retrieval of data. To do this, the creation and definition of database models would take place. Database models are like tables with columns. Each model is a class and each column represents a field of data. We would be creating the User model, Blogpost model and Contact model.

Your database model should look like this:

class User(db.Model, UserMixin):
    id = db.Column(db.Integer(), primary_key=True)
    first_name = db.Column(db.String(100), nullable=False)
    last_name = db.Column(db.String(150), nullable=False)
    username = db.Column(db.String(), nullable=False, unique=True)
    gender = db.Column(db.String(), nullable=False)
    email = db.Column(db.String(), nullable=False, unique=True)
    password_hash = db.Column(db.Text(), nullable=False)
    blogpost = db.relationship('Blogpost', backref='user')


    def __repr__(self):
        return f"User <{self.username}>"

We use one-to-many-relationship to link two models together in a database.

We move on to creating the database. Follow these steps to create the database in the terminal:

# Type the following
python
from app import app, db 
with app.app_context():
# Press the tab key then type, db.create_all()
# Press the enter key twice

The blog database file will appear in the project's root directory. Use DB Browser for SQLite to view the database, since VSCode does not read .db files.

Routing

Routing is the primary action in a computer network that transfers data from source to destination. This means that a Unified Resource Locator(URL) is mapped to a specific function to handle its logic. A decorator is used to bind the URL to a function, with the URL being an argument. @app.route() is the decorator we use here.

For the routes to function as they should, we need to install some more requirements to import some modules.

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

The routes here are: Index, Register,Login, About, Contact, Logout, Blog, Addpost, Edit, Delete_blog.

@app.route('/')
def index():
    posts = Blogpost.query.order_by(Blogpost.date_created.desc()).all()

    return render_template('index.html', posts=posts)

For the index route, you need not write anything within the parenthesis, the (/) suffices.

@app.route('/register', methods=['GET','POST'])
def register():
    if request.method == 'POST':
        first_name = request.form.get('firstname')
        last_name = request.form.get('lastname')
        username = request.form.get('username')
        gender = request.form.get('gender')
        email = request.form.get('email')
        password = request.form.get('password') 
        confirm_password = request.form.get('confirmpassword')

        user = User.query.filter_by(username=username).first()
        if user:
            return redirect(url_for('register')) 

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

        password_hash = generate_password_hash(password)

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

        flash('You have been signed up successfully')
        return redirect(url_for('login'))

    return render_template('signup.html')

Note that each route has either a GET method, POST method or both methods. The GET method retrieves data from the database, while the POST method sends data to the database. The GET method is the default routing method so it does not need to be written in as an argument when it is the only method.

Authentication and Authorisation

In order for users of this blog website to be able to register, login or have access to some certain functions, they have to be authenticated and authorised. To achieve this we install the flask_login and make use of the werkzeug.security module from Flask.

From flask_login, we would be needing the LoginManager module to manage the logging in and out of users through the login_user and logout_user modules. Also, the current_user and login_required modules are needed to authenticate users and customise views so that only authorised users have access to specific HTML templates.

From werkzeug.security, we would be importing the generate_password_hash and check_password_hash to hash passwords as well as check passwords for security and authorisation.

With authenticatication and authorisation, we are able to achieve the following;

  • Only authenticated users can view the about and contact pages, as well as logout

  • Only current users can write, edit and delete a blog. No one is allowed to edit or delete a blog that's not theirs

Note that if statements are also used in the HTML files to authenticate and authorise.

For a more comprehensive look at the source code, click here. This project is locally hosted.

Congratulations! You can now create your own simple blog website.

Message flashing was added as an additional feature to make the blog website more responsive. You can add any feature you want to your blog website.

I would greatly appreciate your feedback.