Building A Simple Blog Using Flask

This article provides a step by step guide to using Flask for building a simple Blog Site.

Introduction

Flask is a web framework. It's a Python module that provides tools, libraries, and technologies that allow you to build web applications quickly. Flask is a microframework, meaning it is a lightweight framework that does not include an ORM (Object Relational Mapper) or such features. Instead, Flask lets you use the components you need and scale up as needed.

Here are some key features of Flask:

  • Lightweight and easy to learn

  • Built-in development server and debugger

  • Support for unit testing

  • Integrated support for WSGI (Web Server Gateway Interface)

  • Jinja2 templating

  • Support for secure cookies (sessions)

We shall use the Flask framework to build a simple blog site that allows users to post, read, delete and edit articles (CRUD). Our blog shall also employ user authorization to ensure the safety of the articles.

Prerequisites

In order to build this blog page we would need the following:

  • A working knowledge of python and its basic concepts. You can find a quick refresher into python on w3schools.

  • Knowledge of HTML, and CSS, w3schools has good tutorials on HTML and CSS

  • A computer with python3 installed.

  • A suitable code editor like Visual Studio Code.

Setup

Firstly we shall create a project directory where our blog will be situated. We can do this using the file manager, visual studio code, or the Command Prompt.

We will create a project folder and then move into it in the command prompt as shown below

>mkdir blog_project

>cd blog_project

Setup Virtual Environment

A virtual environment is a tool that allows you to isolate a Python environment on your system so that you can install and run packages in that environment without affecting the packages and dependencies of other Python projects on your system. This is particularly useful when you have different projects that require different versions of the same packages, or if you want to test a package in a clean environment before installing it globally on your system.

To create a virtual environment, you can use the venv module that is included in the Python standard library. The basic syntax for creating a virtual environment in the project directory is shown as follows:

python3 -m venv blog_venv

This will create a new directory at the specified path, which will contain the Python executable and all the necessary files for the virtual environment. Once you have created the virtual environment, you can activate it by running the activate script:

blog_venv\Scripts\activate

After activating the virtual environment, you can use the pip command to install packages without affecting other Python projects on your system. And when you are done, you can deactivate the virtual environment by running:

deactivate

All the packages that we need to install for this project will be installed in the virtual environment.

Install Flask

The next step will be to install Flask in the project directory using the python package installer. Therefore in the command window and the virtual environment activated, we will run the following script.

pip install flask

This script will install the flask package into the virtual environment in our project directory.

Setup Visual Studio Code

The first step is to open our VS Code in the project directory. We will have to use the interpreter from our virtual environment.

To use the virtual environment interpreter on VS Code, we will use a button on the bottom right of our VS Code window. The button is shown below

We will then select a suitable interpreter that matches the virtual environment that we have created.

Create Flask App

In our main project directory, we will create a file named __init__.py and another named app.py.

In our __init_.py file, we shall write the following code.

from flask import Flask
from blog_project.config import Config

def create_app(config_class = Config):
    app = Flask(__name__)
    app.config.from_object(Config)

    return app

In the snippet above, we import flask and created an instance of the Flask application. The __init__ .py file will hold the instances of our installed packages that will be used during the building of the blog.

In the app.py file, we will write the code with which the app will run. The code written in the app.py file is shown below.

from blog_project import create_app

app = create_app()

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

In the code above, we imported the create_app function from the __init__.py file and used the instance of the app that was created. We then asked the app to run using the app.run statement.

Create A Database

The next step for us to take is to create a database. In the blog, we would want to store the articles that are being created and the users that are registered on the blog. Hence we need to create databases. In Flask, we use SQLAlchemy to manage databases.

SQLAlchemy is a popular Python library for working with databases. It provides a set of high-level APIs for connecting to different types of databases and manipulating data in them, while also allowing us to work with low-level SQL statements if needed. With SQLAlchemy, we can work with databases in a Pythonic way, using familiar programming concepts like classes, objects, and methods.

One of the key features of SQLAlchemy is its Object-Relational Mapper (ORM), which allows us to interact with our database in a way that's similar to working with Python objects. Using the ORM, we can define classes that represent the tables in our database, and then use instances of those classes to insert, update, and retrieve data. The ORM automatically generates the necessary SQL statements for us, so we don't have to write them ourselves.

In Flask specifically, we have an SQLAlchemy module that is specific to Flask known as Flask_SQLAlchemy, we will use Flask_SQLAlchemy to set up our database for this project.

To install Flask_SQLAlchemy in our project directory, we will run the following script in the command window

pip install Flask-SQLAlchemy

In the project __init__.py file, we will create an instance of the Flask-SQLAlchemy class. We will add the following code snippet to the __init__.py file

from flask_sqlalchemy import SQLAlchemy

Then before the create_app function, we will create an instance of the SQLAlchemy class.

db = SQLAlchemy()

We will therefore configure the database by creating a config.py file, in the config.py file, we will configure the settings for the database to be created as shown in the code below.

import os

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

class Config:
    SECRET_KEY = 'anysuitablesecretkey'
    SQLALCHEMY_DATABASE_URI = 'sqlite:///'+ os.path.join(base_dir, 'blog.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False

And inside the create_app function, we will initialize the database as follows

db.init_app(app)

@app.before_first_request
def create_tables():
    db.create_all()

In our Command window, we will run the following script.

python app.py

Creating Pages

We will divide our blog project into different modules depending on what we need to use them for. The modules we have are shown as follows.

  • Main: This module will house all the routes, pages, and forms for the general pages on the blog site e.g. The Homepage and the contact page.

  • Users: This module is for all pages, forms, and routes that have to do with the Users. e.g. login, register, logout, etc.

  • Posts: This module handles all the pages, forms, and routes that have to do with the articles being posted on the blog site. Pages like the post, edit, and delete pages will be stored in this module.

We will create three folders in the main project directory named main, posts and users. In each of the folders, we will create an __init__.py file, a routes.py file, and a forms.py file.

We will also create a templates folder to hold all the HTML pages for our blog site. A static folder will also be created to hold the CSS files and the static files for the blog site.

Creating Models

In general, a model is a Python class that defines the structure and behavior of an object. In the context of a web application, a model might be used to represent a database table or to perform complex calculations.

We will use models to establish the structure of the tables in our database.

In the main project directory, we will create a models.py file, where we will construct the models that represent the User, Posts, and Messages received on the blog site.

In the models.py page, we will add the code given below.

from blog_project import db, login_manager
from datetime import datetime
from flask_login import UserMixin
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
from flask import current_app


@login_manager.user_loader
def load_user(user_id):

    return User.query.get(int(user_id))




class User(db.Model,UserMixin):

    __tablename__ = 'user'
    id = db.Column(db.Integer(), primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable = False)
    email = db.Column(db.String(120), unique=True, nullable = False)
    password = db.Column(db.String(60), nullable = False)
    post = db.relationship('Post', backref = 'author', lazy=True)

    def get_reset_token(self, expires_sec = 1800):

        s = Serializer(current_app.config['SECRET_KEY'], expires_sec)

        return  s.dumps({'user_id': self.id}).decode('utf-8')


    @staticmethod
    def verify_reset_token(token):
        s = Serializer(current_app.config['SECRET_KEY'])

        try:
            user_id = s.loads(token)['user_id']

        except:
            return None

        return User.query.get(user_id)

    def __repr__(self) -> str:
        return f'This is User {self.username}, with email {self.email}'

class Post(db.Model):

    __tablename__ = 'post'
    id = db.Column(db.Integer(), primary_key=True)
    title = db.Column(db.String(100), nullable = False)
    date_posted = db.Column(db.DateTime, default=datetime.utcnow)
    content = db.Column(db.Text(), nullable = False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable = False)

    def __repr__(self) -> str:
        return f'This is post {self.title}, posted on  {self.date_posted}'

class Message(db.Model):

    __tablename__ = 'message'

    id = db.Column(db.Integer(), primary_key = True)
    name = db.Column(db.String(), nullable = False)
    email = db.Column(db.String(), nullable = False)
    message = db.Column(db.String(), nullable = False)

    def __repr__(self) -> str:
        return f"This is a message from {self.name}, with email {self.email} and a message of {self.message}  "

In the code above, we created a class for each of the tables in our database, where each table shows the structure of the Columns on the tables.

In the User Model, we also added the get_reset_token and verify_reset_token methods in order to get passwords reset in the event that the user forgets their password.

We also used UserMixin from the flask_login extension that allows to create a user model. The flask_login package will be explained in more detail later on in this guide.

From the itsdangerous extension which is installed when we install the flask framework, we imported the TimedJSONWebSignatureSerializer which creates a token for a user in order for them to be verified for the purpose of a password reset.

Creating The Main Pages

In the main module, we will create routes for the homepage and the contact page. In this module, we will create two new files, namely the routes.py file that will contain the routes for the homepage and the contact page. The second file to be created is the forms.py file that will hold the contact form for the contact page.

In the routes.py file, we will add the following code.

from flask import Blueprint, render_template, redirect, request, flash, url_for
from blog_project.main.forms import ContactForm
from blog_project.models import Post, Message
from blog_project import db


main = Blueprint('main', __name__)


@main.route('/')
def home():

    page = request.args.get('page', 1, type=int)
    posts = Post.query.order_by(Post.date_posted.desc()).paginate(page = page, per_page = 5)

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

@main.route('/about')
def about():

    return render_template('about.html')



@main.route('/contact', methods=['GET', 'POST'])
def contact():
    form = ContactForm()

    if form.validate_on_submit():

        name = form.name.data
        email = form.email.data
        message = form.email.data

        contact_message = Message(name = name, email = email, message = message)

        db.session.add(contact_message)
        db.session.commit()

        flash('Your Message has Been Sent, we will get back to you soon!!', 'success')

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



    return render_template('contact.html', form = form, title = 'Contact')

In the routes.py file, we have two methods being implemented, the first is the about method which renders the about.html template.

The other function is the contact function which uses a form imported from the main forms.py file to receive data from the user to store in a contact messages table in our database.

We also created a blueprint that enables us to group related views and other code together, and then register those views with the application. Each blueprint defines its own set of routes and views, and can also have its own templates, static files, and other resources.

In the forms.py document, we created the ContactForm that was used in the routes.py file to receive messages from the users.

Flask has a package that allows us to process forms in python, this module is known as flask_wtf .

flask_wtf is a Flask extension that integrates the WTForms library, which is a popular Python library for working with forms in web applications. It provides a simple and convenient way to handle form submissions and validation in a Flask application.

WTForms is a library that provides form handling and validation functionality. It allows you to create form classes that define the fields that should be in the form, as well as the validation rules for those fields. The flask_wtf extension integrates these form classes with a Flask application, providing a convenient way to render forms in templates and handle form submissions.

With flask_wtf, you can create form classes using WTForms, and then use those classes in your views to handle form submissions. The forms can be rendered in your templates using the flask_wtf.FlaskForm class, and you can use the wtforms.validators module to define validation rules for the fields in your form.

To install flask_wtf , we will run the following in our command window.

pip install Flask-WTF

This installed package is then used in the forms.py file

The code that was used in the forms.py file is shown below.

from flask_wtf import FlaskForm
from wtforms import StringField, EmailField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Email

class ContactForm(FlaskForm):

    name = StringField('Name', validators=[DataRequired()])
    email = EmailField('Email', validators=[DataRequired(), Email()])
    message = TextAreaField('Message', validators=[DataRequired()])
    submit = SubmitField('Send Message')

In the code snippet shown above, we see that the form ContactForm was created by inheriting from the FlaskForm Subclass of flask_wtf.

The fields to be filled are also established in the code snippets shown above.

Creating The User Pages.

The next step to be taken is the creation of the routes, forms, and other functions that apply to the users. This involves the creation of login, logout, register, and password_recovery functions that enable the blog to manage its users properly.

One of the most important flask packages that allow us to manage users is the flask_login package. flask_login is a Flask extension that provides user session management for Flask applications. It helps us to handle user authentication and authorization, which are important aspects of most web applications.

flask_login provides a simple and convenient way to handle user login and logout in a Flask application. It allows us to define a user model, which represents the user that is currently logged in and provides a decorator @login_required to protect views that should only be accessible to logged-in users.

The extension also provides a way to easily handle cookies and session data, which is used to keep track of a user's login status between requests. Additionally, it provides flexibility in terms of storing the user session data, it can be stored in a database, in memory, in a file, and so on.

To use flask_login, we first need to initialize the extension in our Flask application and set up a user model. The user model is a Python class that defines the properties and methods of a user, such as a user's username, email, and password. We can also define custom methods for checking if a user is authenticated or active.

Then we can use the login_user(user) function to log a user in and logout_user() the function to log the user out. We can also use the @login_required decorator to protect views that should only be accessible to logged-in users.

To install the flask_login package in our project we will run the following in our command window.

pip install Flask-Login

Upon Installation, we can now embark upon adding useful functionality to our blog site. The first thing to do is to create a forms.py file where we will establish all the forms that our user may need to navigate the blog forms such as the LoginForm, RegistrationForm, PasswordResetForm and so on. As earlier stated, we will use the power of flask_wtf and flask_login to create these forms.

To set up the flask_login, we will add the following code to the imports of the __init__.py file in the project directory

from flask_login import LoginManager

Also in the __init__.py file before the create_app method, we will add the following code.

login_manager = LoginManager()
login_manager.login_view = 'users.login'
login_manager.login_message_category = 'info'

Finally, in the create_app function, we will initialize the login_manager.

login_manager.init_app(app)

In the user forms.py file we will then write the following code.

from flask_wtf import FlaskForm
from wtforms import StringField, EmailField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Email, Length, EqualTo, ValidationError
from altschool_blog_project.models import User
from flask_login import current_user


class RegistrationForm(FlaskForm):

    username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
    email = EmailField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password',validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password',validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Sign Up')

    def validate_username(self, username):
        user = User.query.filter_by(username=username.data).first()

        if user:

            raise ValidationError('The Username is already taken')

    def validate_email(self, email):
        user = User.query.filter_by(email = email.data).first()

        if user:

            raise ValidationError('The email is already in use')



class LoginForm(FlaskForm):

    email = EmailField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password',validators=[DataRequired()])
    remember =BooleanField('Remember Me')
    submit = SubmitField('Log In')


class UpdateProfileForm(FlaskForm):

    username = StringField('Username', validators=[DataRequired(), Length(min=2, max=20)])
    email = EmailField('Email', validators=[DataRequired(), Email()])
    submit = SubmitField('Update')

    def validate_username(self, username):
        if current_user.username != username.data :
            user = User.query.filter_by(username=username.data).first()

            if user:

                raise ValidationError('The Username is already taken')

    def validate_email(self, email):
        if current_user.email != email.data :
            user = User.query.filter_by(email = email.data).first()

            if user:

                raise ValidationError('The email is already in use')

class ResetRequestForm(FlaskForm):

    email = EmailField('Email', validators=[DataRequired(), Email()])
    submit = SubmitField('Request Password Reset')

    def validate_email(self, email):

        user = User.query.filter_by(email = email.data).first()

        if user is None:

            raise ValidationError('The Email Does Not Exist, Create A New Account')

class ResetPasswordForm(FlaskForm):

    password = PasswordField('New Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm New Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Reset Password')

In the code snippet for the user forms.py file, we were able to write code to create forms for RegistrationForm , LoginForm, UpdateProfileForm, ResetRequestForm and ResetPasswordForm . We also created our own validators for the form fields that we have created. We used the flask_login package to import the current_user instance that allows us to perform user validation in our forms.

The next file we will create in our user module is the utils.py file that allows us to store important functionalities that we will need to facilitate our blog. In our utils.py file, we will write a function that enables us to send password reset mail to our registered users.

In the utils.py we have the following:

import secrets, os
from flask_mail import Message
from flask import url_for, current_app
from altschool_blog_project import mail


def send_reset_email(user):
    token = user.get_reset_token()
    msg = Message('Password Reset Request', sender = 'temitopeadebayo749@gmail.com', recipients=[user.email])
    msg.body = f"""
        To reset Your password, visit the following link
        {url_for('users.reset_password', token= token, _external = True)}

        If you did not send this request simply ignore this mail
    """
    mail.send(msg)

The send_reset_email function utilizes the power of flask_mail to send a message that helps the user reset their password if they have forgotten it.

flask_mail is a Flask extension that integrates the Python library Flask-Mail into a Flask application, allowing you to send email messages from your application. It provides a simple and convenient way to send emails with Flask, abstracting away many of the complexities of working with the smtplib library.

The Flask-Mail extension provides a Mail class that is a simple wrapper around the smtplib library. You can use this class to send email messages by configuring the SMTP settings for your email server, then creating an instance of the Mail class and using its methods to send email messages.

To use flask_mail in your application, you first need to initialize the extension by setting the configuration options for your email server, such as the server's address, port, and authentication credentials. You can do this using the Flask's config object. After that, you can use the Mail class to send emails by calling the send() method and passing in the message data as an argument.

In order to setup flask_mail in our project, we first need to install the package. This is done using the pip install script in our command window as shown below.

pip install Flask-Mail

After installation, we will set it up in our __init__.py and config.py files for the project.

In the __init__.py file, we will add the following with the other imports.

from flask_mail import Mail

Before the create_app function, we create an instance of the Mail class.

mail = Mail()

And finally, in the create_app function we will add the following.

mail.init_app(app)

Now the flask_mail is all set up.

Now that the flask_mail is set up, we continue by creating our user route. In the User module, we will create a routes.py file. In this file, we will create all the routes that will apply to the user. The code in the user routes.py file is shown below.

from flask import Blueprint, render_template, redirect, url_for, flash, request
from blog_project.users.forms import RegistrationForm, LoginForm, UpdateProfileForm, ResetPasswordForm, ResetRequestForm
from flask_login import current_user, login_user, logout_user, login_required
from blog_project import db, bcrypt
from blog_project.models import User, Post
from blog_project.users.utils import send_reset_email

users = Blueprint('users', __name__)

@users.route('/register', methods=['GET', 'POST'])
def register():

    if current_user.is_authenticated:
        return redirect(url_for('main.home'))

    form = RegistrationForm()

    if form.validate_on_submit():


        hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        name = form.username.data
        email = form.email.data
        user = User(username = name, email = email, password = hashed_password)

        db.session.add(user)
        db.session.commit()
        flash(f' Account Created For {form.username.data}! You can now Log In', 'success')
        return redirect(url_for('users.login'))


    return render_template('register.html', title = 'Register', form = form)

@users.route('/login', methods= ['GET','POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():

        user = User.query.filter_by(email = form.email.data).first()

        if user and bcrypt.check_password_hash(user.password, form.password.data):

            login_user(user, remember=form.remember.data)

            next_page = request.args.get('next')



            return redirect(next_page ) if next_page else redirect(url_for('main.home'))    

        else:
            flash('Log In failed, Use The Right Credentials', 'danger')


    return render_template('login.html', title='Login', form = form)

@users.route('/logout')
def logout():

    logout_user()

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

@users.route('/profile', methods = ['GET', 'POST'])
@login_required
def profile():
    form = UpdateProfileForm()

    if form.validate_on_submit():


        current_user.username = form.username.data
        current_user.email = form.email.data

        db.session.commit()

        flash('Your Profile has been Updated', 'success')

        return redirect(url_for('users.profile'))

    elif request.method == 'GET':
        form.username.data = current_user.username
        form.email.data = current_user.email


    image_file = url_for('static', filename = 'profile_pics/'+current_user.image_file)
    return render_template('profile.html', title = 'Profile', image_file = image_file, form = form)

@users.route('/user/<int:user_id>/posts', methods=['GET', 'POST'])
@login_required
def get_user_posts(user_id):

    page = request.args.get('page', 1, type=int)
    author = User.query.get_or_404(user_id)

    posts = Post.query.filter_by(author = author).order_by(Post.date_posted.desc()).paginate(page=page, per_page = 5)

    return render_template('user_posts.html', title = author.username, author = author, posts = posts)

@users.route('/reset_password', methods=['GET', 'POST'])
def reset_request():
    if current_user.is_authenticated:
        return redirect(url_for('main.home'))

    form = ResetRequestForm()

    if form.validate_on_submit():
        user = User.query.filter_by(email = form.email.data).first()
        send_reset_email(user)

        flash('An Email Has Been Sent WIth Instructions on How To Change The Password', 'success')

        return redirect(url_for('users.login'))

    return render_template('reset_request.html', title = 'Reset Password', form = form)



@users.route('/reset_password/<token>', methods=['GET', 'POST'])
def reset_password(token):
    if current_user.is_authenticated:
        return redirect(url_for('main.home'))

    user = User.verify_reset_token(token)

    if user is None:
        flash('The Token Is Invalid or Expired', 'warning')
        return redirect(url_for('users.reset_request'))

    form = ResetPasswordForm()

    if form.validate_on_submit():
        hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        user.password = hashed_password


        db.session.commit()
        flash(f' Your Password has been changed. You can now Log In', 'success')
        return redirect(url_for('users.login'))

    return render_template('reset_password.html', title= "Reset PassWord", form = form)

In the code shown in the routes.py file shown above, we were able to create routes for our users.

The register route allows us to create a user for our blog

The login route uses the power of flask_login to login our users into their accounts.

The logout route uses the power of flask_login to log out users out of their accounts.

One of the imports that we used in our user routes was Bcrypt .

Bcrypt is a password hashing function designed to be secure and efficient. It is designed to be used as a secure method of storing passwords in a database. The main advantage of Bcrypt over other password hashing algorithms is that it is designed to be "future-proof", meaning that it is designed to be secure even against future advancements in computational power.

We use Bcrypt to hash our user passwords in order to keep them safe from malicious attacks.

We install the Bcrypt package in our project from our command window as follows:

pip install flask_bcrypt

We will setup bcrypt in our __init__.py file as we have done the others

With the imports,

from flask_bcrypt import Bcrypt

before the create_app function,

bcrypt = Bcrypt()

and in the create_app function,

bcrypt.init_app(app)

We have completed the setup.

With this, we have all our routes and functions for user management complete.

Creating The Posts Pages.

The next step is to set up the posts section of our blog site. Here we create the functionalities needed for our users to write posts, read posts, edit and delete posts. As shown when constructing the previous sections, we create forms that allow our users to create and edit their posts, we also create routes that give the functionality that the user needs to save, delete, read or update their posts.

As before we shall create a forms.py file in our posts folder. In the forms.py folder, the following code will be added.

from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired


class PostForm(FlaskForm):

    title = StringField('Title', validators=[DataRequired()])
    content = TextAreaField('Content', validators=[DataRequired()])
    submit = SubmitField('Post')

The form created above allows a user to create an article using the PostForm .

The next step is to create the routes.py file where as before we will implement the functionalities that we need from our posts.

from flask import Blueprint, render_template, redirect, url_for, request, flash, abort
from blog_project import db, login_manager
from flask_login import login_required, current_user
from blog_project.models import Post
from blog_project.posts.forms import PostForm

posts = Blueprint('posts', __name__)

@posts.route('/posts/new', methods=['GET', 'POST'])
@login_required
def create_post():

    form = PostForm()
    if form.validate_on_submit():

        title = form.title.data
        content = form.content.data
        post = Post(title = title, content= content, author = current_user)
        db.session.add(post)
        db.session.commit()
        flash('Post Has Been Created', 'success')

        return redirect(url_for('main.home'))
    return render_template('create_post.html', title='Create Post', form = form, legend = 'Add A New Post')

@posts.route('/post/<int:post_id>', methods=['GET', 'POST'])
def post(post_id):

    post = Post.query.get_or_404(post_id)

    return render_template('post.html', title= post.title, post = post, legend = 'New Post' )

@posts.route('/post/<int:post_id>/update', methods=['GET', 'POST'])
@login_required
def update_post(post_id):

    post = Post.query.get_or_404(post_id)

    if post.author != current_user:
        abort(403)


    form = PostForm()

    if form.validate_on_submit():

        post.title = form.title.data
        post.content = form.content.data

        db.session.commit()

        flash('Your Post Has been Updated', 'success')

        return redirect(url_for('posts.post', post_id = post.id))

    elif request.method == 'GET':

        form.title.data = post.title
        form.content.data = post.content

    return render_template('create_post.html', title= post.title, form = form, legend = 'Update Post' )


@posts.route('/post/<int:post_id>/delete', methods=['GET', 'POST'])
@login_required
def delete_post(post_id):

    post = Post.query.get_or_404(post_id)

    if post.author != current_user:
        abort(403)

    else:
        db.session.delete(post)
        db.session.commit()

        flash('Your Post Has been Deleted', 'success')

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

In our routes, we were able to implement CRUD for the articles in our functions. We also used @loginrequired decorator that is made available from our flask_login to ensure that only logged-in users are able to create posts. We added a current_user verification to our update_post and delete_post methods in order to ensure that only users that created a post can either edit or delete them.