How to build a Blog using Flask

Photo by Chris Ried on Unsplash

How to build a Blog using Flask

How to use Flask (Python), HTML and CSS to create our very first blog

Flask is a micro web framework written in Python that speeds up application development by providing essential back-end components for programmers to build upon. Flask is simple and lightweight—one of the most manageable frameworks around—and contains only the vital necessities for web development. It is, however, also designed to be highly extensible so developers can customize it however they see fit.

Set Ups

In this tutorial we have to be familiar with Python, HTML and CSS.

we have to setup the following before starting

Steps

Step 1 - Create your Project Folder

$ mkdir samdamiblog
$ cd samdamiblog

Step2 - Virtual Environment

Create a virtual environment.

NOTE: We have different way of creating a virtual environment depending on your Operating System (OS) but in this tutorial we are using Windows OS

Name your virtual environment venv

$ py -3 -m venv venv

Activate the environment

NOTE: Before you work on your project, activate the corresponding environment:

$ venv\Scripts\activate

Install Flask

After you activated your environment, we use the following command to install Flask:

$ pip install Flask

NOTE: In Python, pip is a standard package management system that is used to install and manage other software modules.

Now Flask is installed.

now we open our VS Code:

$ code.

Step3 - app.py

create a folder named app.py then input this code

from flask import Flask
app = Flask(__name__)

Step4 - Database

The database is a collection of organized information that can easily be used, managed, update. Learn more

Now we will install Flask-SQLAlchemy:

$ pip install Flask-SQLAlchemy

we import:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os

app = Flask(__name__)

Configuration:

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

We will create a database object using the SQLAlchemy class

db = SQLAlchemy(app)

Now we create two database model.

Firstly, we install Flask-login and install UserMixin.

NOTE: We can run our code on VS Code terminal.

 $ pip install Flask-Login
 from flask_login import UserMixin

Then we create our user class and BlogPost class:

class User(db.Model, UserMixin):
    """This is the User database model"""
    id = db.Column(db.Integer(), primary_key=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.Text(), nullable=False)


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

class BlogPost(db.Model):
    """This is the blogpost database model"""
    id = db.Column(db.Integer(), primary_key=True)
    title = db.Column(db.String(50))
    subtitle = db.Column(db.String(50))
    date_posted = db.Column(db.DateTime)
    content = db.Column(db.Text)
    author = db.Column(db.Text)
@app.before_first_request
def create_tables():
    db.create_all()

Step5 - Routes

Routing is the mechanism of mapping the URL directly to the code that creates the webpage. Learn more here...

We import the code:

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

Now create a home route.

NOTE: The home page is also the index page and represented in this format ('/') in routing.

@app.route('/')
def index():
    return render_template('index.html', posts=BlogPost.query.all())

create an about route:

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

create a contact route:

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

NOTE: The return render_template('.html') is use to connect the HTML to the route, it a function that collects data from the linked HTML file.

Now we link our HTML and CSS the Flask by creating a folder called templates under our blog folder

NOTE: All the HTML file created would be under the templates folder

Then create another folder named static the folder would contain our linked image files and CSS

You can check out my codes here...

Learn more on how to link HTML and CSS to Flask here...

Step7 - Security/Route

First, we instance LoginManager(): to use Flask-Login:

login_manager = LoginManager(app)

Now input these codes:

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

Then our code would look like this:

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

Werkzeug.security module documentation Undocumented def check_password_hash (pwhash, password): Check a password against a given salted and hashed password value. Learn more here...

Now we create our Login and Logout Route:

 @app.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 and check_password_hash(user.password_hash, password):
            login_user(user)
            return redirect(url_for('index'))
        else:
            return render_template('login.html', error_msg="Invalid username or password. Try again.")
    return render_template('login.html')


@app.route('/logout')
def logout():
    logout_user() 
    return redirect(url_for('login'))


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

Then create a Sign-up Route the allows users who doesn't have an account to create one:

@app.route('/signup', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form.get('username')
        email = request.form.get('email')
        password = request.form.get('password')
        confirm = request.form.get('confirm')
        passwords_no_match = ""

        if confirm != password:
            passwords_no_match = "The two passwords must match"

        username_exists = User.query.filter_by(username=username).first()
        email_exists = User.query.filter_by(email=email).first()

        if username_exists or email_exists or passwords_no_match:
            username_msg = "This Username is already being used by another user."
            email_msg = "This Email is already being used by another user."
            return render_template('signup.html', username_error=username_msg, email_error=email_msg, password_error=passwords_no_match)
        password_hash = generate_password_hash(password)

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

        return redirect(url_for('login'))

    return render_template('signup.html')

We will create the Add, Edit and Delete post route also linked to their HTML file. Also adding a security in which unregister user can only read post and only allow the owner of the post to edit and delete his/her post.

@app.route('/add')
def add():
    if current_user.is_authenticated:
        return render_template('add.html')
    else:
        return redirect(url_for('login'))

@app.route('/post_deleted')
def post_deleted():
    """Notifies a user whether the delete operation was successful or not"""
    return render_template('post_deleted.html')

@app.route('/posts/<int:post_id>/delete')
def delete(post_id):
    post_to_be_del = BlogPost.query.filter_by(id=post_id).one()
    if current_user.is_authenticated and current_user.username == post_to_be_del.author:
        BlogPost.query.filter_by(id=post_id).delete()
        db.session.commit()
        return redirect(url_for('post_deleted'))
    else:
        return redirect(url_for('login'))

@app.route('/posts/<int:post_id>/edit', methods=['GET', 'POST'])
def edit_post(post_id):
    post_to_be_edited = BlogPost.query.filter_by(id=post_id).one()
    if current_user.is_authenticated and current_user.username == post_to_be_edited.author:
        if request.method == 'GET':
            return render_template('edit_post.html', post=post_to_be_edited)
        elif request.method == 'POST':
            post_to_be_edited.title = request.form['title']
            post_to_be_edited.subtitle = request.form['subtitle']
            post_to_be_edited.content = request.form['content']
            db.session.commit()
            return redirect(url_for('post', post_id=post_id))
    else:
        return redirect(url_for('login'))

@app.route('/addpost', methods=['POST'])
def addpost():
    title = request.form['title']
    subtitle = request.form['subtitle']
    content = request.form['content']
    author = current_user.username

    post = BlogPost(title=title, subtitle=subtitle, content=content, author=author, date_posted=datetime.now())

    db.session.add(post)
    db.session.commit()

    return redirect(url_for('index'))

Then you run your code using:

python app.py

and your terminal should look like this:

Step8- Requirements.txt

Requirements.txt file this allow anyone who want to access your code know the required package to install for the codes to run.

First create a folder named Requirements.txt under your blog folder the input the required package to install:

autopep8==1.7.0
click==8.1.3
Flask==2.2.2
Flask-Login==0.6.2
Flask-SQLAlchemy==2.5.1
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
pycodestyle==2.9.1
SQLAlchemy==1.4.41
toml==0.10.2
Werkzeug==2.2.2

How to install this Requirements.txt:

pip freeze > requirements.txt
pip install -r requirements.txt

Conclusion

Thanks for going through the tutorial

I hope we learnt how to create our very first blog using Flask (python) and some basics HTML and CSS knowledge.

Thanks to AltSchool Africa for this opportunity.

You can check out more of my project here..