Building a Blog app with Flask

Table of contents

No heading

No headings in the article.

Introduction: Blogs are a popular way for individuals and organizations to share their thoughts, experiences, and knowledge with a wider audience. Building a blog from scratch can be a challenging task, but using a web framework like Flask can make the process much easier. Flask is a lightweight Python web framework that provides useful tools and features for building web applications. In this article, I’ll walk you through the steps of building a simple blog using Flask: Setting up the development environment: Before building, a local development environment must be set up. This will allow for testing and debugging the application on your computer before deploying it to a hosting service or server. To set up a development environment for Flask, you'll need to have Python installed on your system. You can check if you already have Python by running the following command in your terminal:


python --version

If Python is not installed, you can download it from the official Python website (https://www.python.org/). Once Python is installed, install and activate a virtual environment, I will be using Git Bash which allows me to use many of the same shell commands as you would on a Unix-style operating system.

What is a Virtual Environment? A virtual environment is a tool used to isolate specific Python environments on a single machine, allowing you to work on multiple projects with different dependencies and packages without running into conflicts. Here are a few reasons why you might want to use a virtual environment: You can work on multiple projects that have conflicting package dependencies. By using a separate virtual environment for each project, you can avoid conflicts between packages that might be installed globally. You can test out new packages or code changes in a safe and isolated environment, without affecting your global Python environment or any other projects you might be working on. It makes it easier to share your project with others, because all of the required packages are contained within the virtual environment, rather than relying on packages that might be installed globally on the recipient's machine. It can help you to keep your global Python environment clean and organized by allowing you to install packages and libraries in a separate, isolated environment for each project. To install a virtual environment, run the following command in the terminal:

pip install virtualenv

This will install the virtualenv package, which you can use to create and manage virtual environments. To create a virtual environment, navigate to the directory where you want to create the environment and use the following command:

Python -m venv {name of virtual environment}

The next thing to do is to activate the virtual environment with the command:


Source venv/Scripts/activate

To confirm if your virtual environment is activated, you can check the name of the current environment that is shown in the command prompt. When a virtual environment is activated, the name of the environment is shown in parentheses in the command prompt. For example:


(myenv) C:\path\to\project\directory>

Proceed to install Flask using the following command:


pip install flask

This will install Flask and all the necessary dependencies. Creating the Flask application: This guide will be based off of Flask blueprints. In Flask, a blueprint is a way to organize a group of related views and other code. A blueprint is not a standalone application; it is a collection of resources that can be registered on an application. Blueprints are useful for organizing larger applications into logical components, and for creating reusable components that can be shared across multiple applications. They can define views, templates, static files, and other resources, and they can also define their own error pages and custom template filters. To create a blueprint, you can use the Flask class as you would to create a regular Flask application, but instead of creating a global Flask object, you create a blueprint object. For example: from flask import Blueprint

bp = Blueprint('my_blueprint', __name__)

@bp.route('/')
def index():
    return 'Hello from my blueprint!'

Firstly, create a file called "app.py" in the project folder. The init.py file is a special Python file that indicates that the directory it is in should be treated as a Python package. It is usually an empty file, but it can be used to execute initialization code for the package, or to define the package's API. For this project I used an app.py file instead of an init.py because I want python to run the logic of the entire project at once. This makes it easier and less repetitive to create a database

In a Flask application, the init.py file is usually used to create the Flask app object and to set up the application. Type the following code in the app.py file to make the root directory behave like a Python module so that we may import our own locally created code from it:

from Blog import create_app

app = create_app()

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

The code above creates a Flask app by calling the create_app function defined in the Blog package, and then starts the app by calling the run method of the app object.

The create_app function is typically defined in the init.py file of the Blog package, and is responsible for creating and configuring the Flask app object. It might do things like register blueprints, load configuration settings, and set up extensions.

The if name == "main" block at the bottom is a standard Python idiom that is used to specify the code that should be executed when the script is run directly, rather than imported as a module. In this case, it starts the app by calling the run method of the app object, with the debug parameter set to True. This enables the Flask debug mode, which reloads the app whenever the source code is changed, and provides helpful error messages when something goes wrong.

The templates directory will contain the HTML files for the application's views, and the static directory will contain any static assets such as CSS, JavaScript, and images. This project contains minimal CSS and Javascript code.

In the base.html file, I have the following code:

<!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">
    <title> | {% block title %} {% endblock %}</title>

    <link rel="stylesheet" href="static/main.css">
</head>
<body>
    <nav>
        <ul>
            {% if user.is_authenticated %}
                <li><a href="/">Home</a></li>
                <li><a href="/create">Create new post</a></li>
                <li><a href="/logout">Logout</a></li>
                <li><a href="/about">About</a></li>
                <li><a href="/contact">Contact</a></li>

            {% else %}
                <li><a href="/">Home</a></li>
                <li><a href="/signup">Sign Up</a></li>
                <li><a href="/login">Login</a></li>
                <li><a href="/about">About</a></li>
                <li><a href="/contact">Contact</a></li>

            {% endif %}
        </ul>
    </nav>


    {% with messages = get_flashed_messages(with_categories=true) %}
        {% if messages %}
            {% for category, message in messages %}
                <ul>
                    {% if category == 'error' %}
                        <li class="error">{{ message }}</li>
                    {% else %}
                        <li>{{ message }}</li>
                    {% endif %}
                </ul>                
            {% endfor %}
        {% endif %}
    {% endwith %}

    <div class="container">
        {% block content %} {% endblock %}
    </div>
</body>
</html>

The code above is an example of a Jinja2 template, which is a HTML file with placeholders for dynamic content. Jinja2 templates are often used in Flask to render HTML pages with dynamic content.

The template defines a basic HTML layout with a <head> section and a <body> section. The <head> section includes a <title> element, which has a block placeholder for the title of the page. The <body> section includes a navigation bar and a <div> element with a block placeholder for the content of the page.

The navigation bar includes links to different pages of the blog. The links shown in the navigation bar depend on whether the user is authenticated or not, which is determined by the user.is_authenticated variable. If the user is authenticated, the navigation bar will show a logout link. If the user is not authenticated, the navigation bar will show a sign up and login link.

The template also includes a block of code that displays flashed messages to the user. Flashed messages are short messages that are stored in the session and displayed to the user on the next request. The get_flashed_messages() function retrieves the messages from the session and the with_categories=true argument specifies that the messages should be returned with their categories. The messages are then displayed to the user in an unordered list. If a message has an "error" category, it will be displayed in a list item with a class of "error".

Finally, the template includes a <div> element with a block placeholder for the content of the page. The content of the page will be inserted into this element at the point where the {% block content %} directive appears.

Database

Defining the database: A blog typically stores data such as posts, tags, and comments in a database. Flask supports a variety of databases, including relational databases (such as MySQL and PostgreSQL) and NoSQL databases (such as MongoDB and Redis). For the purposes of this article, we'll use a simple SQLite database. To set up the database for our blog, we'll need to install the Flask-SQLAlchemy extension. Run the following command to install it:

pip install flask-sqlalchemy

Next, we'll need to define the database models for our blog in the models.py file. A model is a Python class that represents a table in the database. In the blog, we'll need a model for posts, and users.

Code the following into the model.py file:

from flask_login import UserMixin 

from sqlalchemy.sql import func 

from . import db

class Post(db.Model):

 id = db.Column(db.Integer, primary_key=True)

 title = db.Column(db.String(10000)) body = db.Column(db.String(10000))

 date = db.Column(db.DateTime(timezone=True), default=func.now())

 user_id = db.Column(db.Integer, db.ForeignKey('user.id'))

 author = db.relationship('User', foreign_keys=[user_id])

class User(db.Model, UserMixin):

id = db.Column(db.Integer, primary_key=True)

email = db.Column(db.String(255), unique=True)

age = db.Column(db.Integer())

username = db.Column(db.String(255), unique=True)

firstName = db.Column(db.String(255))

lastName = db.Column(db.String(255))

gender = db.Column(db.String(30), nullable=False)

password = db.Column(db.String(255))

posts = db.relationship('Post', backref= 'user')

This code defines two classes, Post and User, that represent a blog post and a user in a database using the Flask-SQLAlchemy extension. The Post class has several class variables that represent columns in a database table:

id: an integer column that is the primary key of the table title: a string column with a maximum length of 10000 characters body: a string column with a maximum length of 10000 characters date: a datetime column with timezone information and a default value of the current time user_id: an integer column that is a foreign key to the id column in the user table author: a relationship to the User class, using the user_id foreign key The User class has several class variables that represent columns in a database table:

id: an integer column that is the primary key of the table

email: a string column with a maximum length of 255 characters that must be unique

age: an integer column

username: a string column with a maximum length of 255 characters that must be unique

firstName: a string column with a maximum length of 255 characters

lastName: a string column with a maximum length of 255 characters

gender: a string column with a maximum length of 30 characters that cannot be null

password: a string column with a maximum length of 255 characters

To view one or multiple posts without signing up/logging in: Like twitter, it is possible to view posts without being logged in as a user. This code should be in the views.py file. This feature was implemented with the following code:

from flask import Blueprint, render_template from flask_login import current_user, login_required from .model import Post

views = Blueprint('views', name)

@views.route('/') 
def home()
    posts = Post.query.all()
    return render_template('home.html', user=current_user, posts=posts)

@views.route('/post/int:id')
def view_post(id):
    post = Post.query.filter_by(id=id).first()
    return render_template('post.html', post=post, user=current_user)

The first route, '/', is for the home page and displays all posts by calling the home() function. The home() function uses the Post model to get a list of all posts from the database and render a template called home.html with the list of posts and the current_user.

The second route, '/post/int:id', is for displaying a single post. It takes an id parameter, which is an integer, and calls the view_post() function. The view_post() function uses the Post model to get a specific post from the database based on the id and render a template called post.html with the post and the current_user.`

Building the blog features: Now that we have our Flask application set up and our database defined, we can start building the features of our blog. First, we'll need a way to create, read (retrieve), update, and delete (CRUD) posts, this was done in a file called auth.py. We can do this with the following routes:

from flask import Blueprint, flash, redirect, render_template, request, url_for from flask_login import current_user, login_required, login_user, logout_user from werkzeug.security import check_password_hash, generate_password_hash

from . import db from .model import Post, User

auth = Blueprint('auth', name)

@auth.route('/contact')
def contact(): 
    return render_template('contact.html')

@auth.route('/about')
def about(): 
    return render_template('about.html', user = current_user)

@auth.route('/login', methods=["GET", "POST"])
def login(): 
    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')

        user = User.query.filter_by(username=username).first() 
            if user: 
                if check_password_hash(str(user.password), password):
                 flash("Logged in sucessfully", category='success')
                 login_user(user, remember=True)
                 return redirect(url_for('views.home')) 
            else: flash("Incorrect password! Try Again.", category='error')
    else: flash(f"User with username '{username}' does not exist!", category='error')

return render_template("login.html", user=current_user, post = Posts)

#logout_function
@auth.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('auth.login'))

@auth.route('/signup', methods=["GET", "POST"])
def sign_up(): 
    if request.method == "POST":
        email = request.form.get('email')
        username = request.form.get('username')
        userAge = request.form.get('age')
        firstName = request.form.get('firstName')
        lastName = request.form.get('lastName')
        password1 = request.form.get('password1')
        password2 = request.form.get('password2')

usernameInDb = User.query.filter_by(username=username).first()
if usernameInDb:
    flash(f"The Username '{username}' is already taken! Try another.")

user = User.query.filter_by(email=email).first()
    if user: 
        flash(f"User with email '{email}' already exist!", category="error")
    if len(email) < 4: flash("Email must be greater than 3 characters", category="error")
    if len(firstName) < 2 or len(lastName) < 2:
        flash("Your name cannot be less than 2 characters", category="error")
    if len(password1) < 7:
        flash("password must be greater than 6 characters", category="error")
        if password1 != password2:
        flash("Password does not match", category="error")
    else: new_user = User( 
        email=email, 
        username = username, 
        userAge = age, 
        firstName = firstName, 
        lastName = lastName, 
        password = generate_password_hash(password1, method='sha256') 
    ) 
        db.session.add(new_user) 
        db.session.commit() 
        flash("You have Successfully Created An Account !", category="success")

    login_user(new_user, remember=True)    

    return redirect(url_for('views.home'))
 return render_template("signup.html", user=current_user)

@auth.route('/create', methods=["GET", "POST"]) 
@login_required
def create_post(): 
    if request.method == "POST":
        title = request.form.get('title')
        body = request.form.get('body')

        if len(title) < 4:
             flash("Title is too Short!!!", category='error')
        elif len(body) < 10:
             flash("Post content is too short!!!", category='error')
    else:
     new_post = Post(title=title, body=body, user_id=current_user.id)
     db.session.add(new_post)
     db.session.commit()
     flash("New Post Created!", category='success')

       return redirect(url_for('views.home'))

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

@auth.route('/edit/int:id', methods=["GET", "POST"])
@login_required def edit(id):
        post = Post.query.filter_by(id=id).first()
        title = post.title
        body = post.body
        if request.method == "POST":
            new_title = request.form.get('title')
            new_body = request.form.get('body')

        if len(new_title) < 4:
            flash("Title is too Short!!!", category='error')
        elif len(new_body) < 10:
            flash("Article is too short!!!", category='error')
        else: new_post = Post(title=new_title, body=new_body, user_id=current_user.id)
        db.session.add(new_post) db.session.commit()
        flash("You have successfully updated your post!", category='success')

        return redirect(url_for('views.home'))

return render_template('edit.html', user=current_user, title=title, body=body)

The first three routes, '/contact', '/about', and '/login', are for displaying static pages. The '/contact' route calls the contact() function, which renders the contact.html template. The '/about' route calls the about() function, which renders the about.html template with the current_user. The '/login' route calls the login() function, which handles GET and POST requests for the login page. If the request method is POST, the function gets the username and password from the form data and checks if the user exists and if the password is correct. If the user exists and the password is correct, it logs the user in using the login_user() function and redirects to the home page. If the user does not exist or the password is incorrect, it displays a flash message and stays on the login page. If the request method is GET, the function renders the login.html template with the current_user and Posts.

The '/logout' route calls the logout() function, which is protected by the login_required decorator. The function logs the user out using the logout_user() function and redirects to the login page.

The '/signup' route calls the sign_up() function, which handles GET and POST requests for the sign up page. If the request method is POST, the function gets the form data and checks if the email or username is already in use. It also checks if the password is at least 7 characters long and if the password fields match. If all the checks pass, it creates a new user using the User model, adds the user to the database using the add() function, and commits the changes using the commit() function. It then logs the user in using the login_user() function and displays a flash message. If any of the checks fail, it displays a flash message and stays on the sign up page. If the request method is GET, the function renders the signup.html template with the current_user.

The '/create' route calls the create_post() function, which is protected by the login_required decorator. The function handles GET and POST requests for the create post page. If the request method is POST, the function gets the form data and checks if the title and body are long enough. If the checks pass, it creates a new post using the Post model, adds the post to the database using the add() function, and commits the changes using the commit() function. It then displays a flash message and redirects to the home page. If the checks fail, it displays a flash message and stays on the create post page. If the request method is GET, the function renders the create.html template with the current_user

The remaining routes are for displaying and editing posts. The '/post/int:id' route calls the view_post() function, which takes an id parameter and displays a single post based on the id. The view_post() function uses the Post model to get a specific post from the database based on the id and renders a template called post.html with the post and the current_user.

The '/edit/int:id' route calls the edit() function, which is protected by the login_required decorator. The function takes an id parameter and handles GET and POST requests for the edit post page. If the request method is POST, the function gets the form data and checks if the title and body are long enough. If the checks pass, it updates the post in the database using the commit() function, displays a flash message, and redirects to the home page. If the checks fail, it displays a flash message and stays on the edit post page. If the request method is GET, the function gets the post from the database and renders the edit.html template with the current_user, the title, and the body of the post. If the user is not the author of the post, it displays a flash message and redirects to the home page.

In the __init__.py file, I have the following code:

from os import path
from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()
DB_NAME = "database.db"

def create_app():
    app = Flask(__name__)

    app.config['SECRET_KEY'] = '922aab7d331a1e87ef1701a3830950b7'
    app.config["SQLALCHEMY_DATABASE_URI"] = f'sqlite:///{DB_NAME}'
    db.init_app(app)



    from .auth import auth
    from .views import views

    app.register_blueprint(views, url_prefix='/')
    app.register_blueprint(auth, url_prefix='/')

    from .model import Post, User
    with app.app_context():
        db.create_all()

    login_manager = LoginManager()
    login_manager.login_view = 'auth.login'
    login_manager.init_app(app)

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

    return app


def create_database(app):
   if not path.exists('Blog/' + DB_NAME):
       with app.app_context():
           db.create_all()
           print("Database Successfully Created!")

This code is defining a function called create_app which creates a Flask web application and sets up some configurations for the application.

The function first creates a Flask app using app = Flask(__name__) and sets a secret key and a SQLAlchemy database URI for the app. The database URI specifies the database to be used for the application, in this case a SQLite database stored in a file called database.db.

The app is then initialized with the SQLAlchemy extension db.init_app(app), which allows the app to use the SQLAlchemy ORM (Object-Relational Mapper) to interface with the database.

The function then registers two blueprints, auth and views, with the app. Blueprints are a way to organize a Flask application into smaller and reusable components.

Next, the function creates all the tables in the database using db.create_all(). This will create tables for all the models defined in the model module.

The function then creates a LoginManager object and initializes it with the app. The LoginManager handles user authentication for the app, and the login_view attribute specifies the login endpoint for the app.

Finally, the function defines a load_user function which is used by the LoginManager to retrieve a user object given the user's id. The load_user function queries the User table in the database for the user with the given id and returns the user object.

The create_app function returns the Flask app instance at the end.

The second function, create_database, is a helper function that checks if the database.db file exists. If it does not exist, it creates the database by creating all the tables in the database using db.create_all().

Below, I have included a link to the project for reference: {% embed https://github.com/iOghenetega/Altblog %}