Create a beginner-friendly blog website using Flask, a Python framework.

·

24 min read

Introduction

Hey guys, welcome to my space🥰. Are you ready to dive into the world of web development and learn how to build your own blog? Look no further! In this tutorial, we will be using Flask, a powerful and flexible Python framework, to create a fully functional and customizable blog application.

Our Flask app offers easy user authentication allowing you to sign up, log in, and publish articles at any time. Our app also gives users the flexibility to make changes to their articles, so they can keep their content fresh and up-to-date.

Let's dive deeper and learn more about the framework we've selected for our app.

What makes Flask a suitable choice for web development?

Flask is a Python-based microframework used for web application development. It is considered a microframework due to its lightweight, independent, integrated unit testing, and flexible and scalable features.

In this tutorial, we will learn how to build a Flask application by implementing the following features:

  • Jinja2 Templates

  • Flash Messages

  • Routing

  • Use of Werkzeug to hash our passwords

  • Perform CRUD Operation

  • User Authentication

Prerequisites

This tutorial is designed for those who already have a basic understanding of front-end development technologies such as HTML and CSS. We also recommend that you have some familiarity with Python 3 before beginning. If you are new to web development, it may be helpful to take some introductory courses to build your skills.

Before we begin, you need to set up the following:

Setup

To begin, create a new folder to hold your application files. This will help you keep your project organized and allow you to easily track any changes made. Then, open your terminal and enter the following commands:

$ mkdir my_blog
$ cd my_blog

Note: You can name the folder whatever you like. If you need a refresher on terminal commands, you can find more information here.

Create a Virtual Environment (venv)

A virtual environment, venv, is a tool used to isolate specific Python environments on a single machine, allowing you to work on multiple projects without running into conflicts caused by conflicting package versions. It's a good idea to create a separate venv for each of your Python projects.

Now, let's set up our virtual environment.

For Windows OS users,

To create a virtual environment, you can run the following command:

$ py -3 -m venv myvenv

Note: myvenv is the name of your virtual environment. You can choose to give it any name you want.

  • To activate your venv, run the following command:
$ myvenv/scripts/activate

For Mac OS users:

To create a virtual environment, you can run the following command:

$ python3 -m venv env

To activate your virtual environment, run the following command:

$ source env/bin/activate

Note: If the activation is successful, the terminal will display the name of the activated virtual environment. This is how the terminal will look when the virtual environment is activated.

**(myvenv)** PS C:\\Users\\user\\Documents\\CSS\\my_blog>

The next step is to install the following dependencies:

To install, run “pip install” followed by the name of the dependency.

  • Flask==2.2.2

  • Flask-Login==0.6.2

  • Flask-SQLAlchemy==3.0.2

For example, to install Flask, we can run the following command:

$ pip install flask

Remember to always activate your virtual environment before installing any packages to keep them isolated from the system-level packages and prevent conflicts.

After we finish installing these packages, we will create a requirements.txt file and execute the following command.”

pip freeze > requirements.txt

This updates our requirements.txt file with the installed packages.

To view installed packages, run this on your terminal:

pip freeze

To learn more about pip and dependencies, you can refer to the documentation for pip

"We will now create our initial flask application

Create Flask app

To create a Flask app, create a file called app.py. This file will import all of the necessary modules and folders for your project. All of your configurations for the app will be done in this file. Your Flask app will be housed in this file.

To create a Flask app, follow these steps:

from flask import Flask

def create_app():
    app = Flask(__name__)

    return app

Now that we have successfully created our first Flask app, let's move on to creating our database

Interacting with database

A database is a structured collection of data that can be easily accessed and modified. It is often organized in the form of tables with rows and columns. In order to create our blog application, we will be using SQLAlchemy's Object-Relational Mapper (ORM). SQLAlchemy is a Python library that offers a convenient way to communicate with various databases, including PostgreSQL, MySQL, and SQLite.

Define a database model

Install SQLAlchemy

To install SQLAlchemy, run this line of code on your terminal:

$ pip install Flask-SQLAlchemy

Create a db.py

To improve organization and management, we can divide our code into separate files. In this case, we will create a file called db.py to handle the database. After installing the SQLAlchemy package, we can assign it to a variable called db in the db.py file. This will

allow us to use SQLAlchemy within the db.py file.

We will create a file called db.py and write the following code in it.

from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()

Incorporating Flask-Login for authentication

Flask-Login is a Flask extension that provides user session management, which is useful for authenticating users and keeping track of their login status. It does this by adding a UserMixin class to your application, which you can use as a base class for your user model.

UserMixin is a mixin class that provides default implementations for several methods that are required by Flask-Login. These methods include:

  • is_authenticated: Returns True if the user has been authenticated, False otherwise.

  • is_active: Returns True if the user's account is active, False otherwise.

  • is_anonymous: Returns True if the user is an anonymous user, False otherwise.

  • get_id: Returns a unique identifier for the user.

By including UserMixin as a base class in your user model, you can easily add Flask-Login's user session management functionality to your application.

To install Flask_Login, run the following command in your terminal:

pip install flask_login

Creating our Models

Using SQLAlchemy's ORM (Object-Relational Mapper), we will define a class for each table in our database, specifying the fields and data types for each class.

In order to store data in our database, we will need to create a model for each type of data we want to store. For example, we might have a user model and an article model.

It can include details about the types of data that are stored, such as a username, first name, and email address.

Next, create a directory called models and add an __init__.py file to it. This will allow Python to treat the directory as a package, which will make it easier for you to import the files into different modules. Then, create the following files in the models directory:

  • user.py

  • article.py

  • views.py

User Model

Next, define a class called UserModel to represent a user in the database. This model should have the following fields: id, username, firstname, lastname, email, and password_hash

from time import timezone
from datetime import datetime
from sqlalchemy import true
from sqlalchemy.sql import func
from flask_login import UserMixin

class UserModel(db.Model, UserMixin):
        __tablename__ = "users"
        id = db.Column(db.Integer, primary_key=True)
        username= db.Column(db.String(80))
        firstname = db.Column(db.Text(80), nullable=False, unique=False)
        lastname = db.Column(db.Text(80), nullable=False, unique=False)
        email = db.Column(db.String(80), nullable=False, unique=True)
     password_hash = db.Column(db.Text, nullable=False)

Above, is an example of how you might define a user model using Flask-Login's UserMixin class.

  • The id is an identifier. When the primary_key attribute is set to True, It becomes specific to that particular article and cannot be used for another.

  • For the username, firstname and lastname. When the nullable attribute is set to False, It means they can not be submitted empty.

  • When an instance of an object uses the unique attribute, it is either set to False or True. If unique is set to True, then no two users can have the same username, email or password on the platform.

  • For the password_hash, we will be creating a function that hashes our passwords into long Immemorable strings for security purposes.

Article Model

When defining these classes in each file, do well to retain the necessary modules that we installed to avoid running into errors.

In the articles.py file we created, we will have:

class ArticleModel(db.Model):
        __tablename__ = "article"
        id = db.Column(db.Integer(), primary_key=True)
        title = db.Column(db.String(550))
        bodytext = db.Column(db.Text(3000), nullable=False, unique=False)
        author = db.Column(db.Integer, db.ForeignKey('user.id', ondelete="CASCADE"), 
    nullable=False)
        date_created = db.Column(db.DateTime(timezone=True), default=datetime.utcnow())

SQLAlchemy creates a table, ArticleModel with 4 columns

  • The id is an identifier. When the primary_key attribute is set to True, It becomes specific to that particular article and cannot be used for another.

  • The title and bodytext are contents of our form. When the nullable attribute is set to False, It means they can not be submitted empty.

  • The “ForeignKey” attribute in the author Column is used to inherit data from two related/different tables.

  • For the date_created , we import the current date/time.

Connect to the database using SQLModel

So back to our app.py, let’s:

  1. Create a path for our application

  2. Configure the SQLAlchemy

  3. Initialize our app

  4. Import our models, UserModel, ArticleModel

To access the DB we initialized in our app.py file, we simply need to import db by writing:

from db import db
  • Create a path

To set up our app, the first thing we need to do is create a path for our SQLite database. We can do this by importing the os module and assigning the directory path to a variable called base_dir.

Here is the code to accomplish this:

import os
base_dir = os.path.dirname(os.path.realpath(__file__))
  • Configuration

We can now set up the URI for SQLAlchemy, create a secret key, and initialize our application using these values


app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + \\
        os.path.join(base_dir, 'blog.db')
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    app.config["SECRET_KEY"] = 'iLhqyLO3HtSpsE8cuQaj'
    db.init_app(app)
  • Initialize app

    db.init_app(app)

Blueprints

In this step, we will create routes for our application which will allow us to access different pages by mapping specific URLs to functions that perform certain tasks. This is an exciting part of the process because we get to see and interact with what we are building. For example, we can create a route for the home page of our app by associating the URL "/" with a function. Before we do that, let's create a blueprint for our app.

We’ll look at:

  • Flask Blueprints

    • Define Blueprints

    • Register Blueprints

  • Jinja Templates

  • Flash Messages

Create a Flask Blueprint for our routes.

In order to organize our code and make it easier to maintain, we will use Flask Blueprints to divide our code into separate files. This is especially useful for larger applications.

First, create a folder called 'resources'. Inside the resources folder, create a init.py file. This will allow Python to treat the folder as a package, making it easier to import the files into other parts of the application.

Next, create the following files within the resources folder:

  • user.py: This file will handle user authentication.

  • **article.py**: This file will contain functions related to our articles, such as functions for commenting and publishing articles.

  • views.py:This file will handle most of our routes and redirections to different pages."

Defining our blueprint:

In each file, define your blueprint by setting it to a variable name of your choice. Do this for each script file. As shown in the code below:

article = Blueprint("article", __name__)
view = Blueprint("view", __name__)
user = Blueprint("user", __name__)

The “view” is an endpoint parameter for the url_for function. It's used to specify which view function to call when a client navigates to a certain URL.

The __name__ parameter is a special variable in Python that represents the name of the current module. It's often used in the context of Flask to specify the location of a Blueprint, which is a way to organize a group of related views and other code in a Flask application. When you initialize a Flask app and create a Blueprint, you can specify __name__ as the first argument to the Blueprint constructor to tell Flask the location of the Blueprint, relative to the root path of the application.

Register the defined Blueprints

This is done within our app.py file by first importing the blueprint.

from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
from models import ArticleModel, UserModel
from db import db

Then register the blueprint in our app.py file

        app.register_blueprint(ViewBlueprint)
        app.register_blueprint(ArticleBlueprint)
        app.register_blueprint(UserBlueprint)

        return app

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

Routing

Jinja Templating

Jinja is a powerful template engine for Python that is often used in web development. One of the key features of Jinja is its support for custom tags, filters, tests, and globals. These features allow developers to define custom logic and functionality that can be used within Jinja templates to control the rendering of the output.

Simply put, Jinja is a flask package that lets us write python code in an HTML file/environment.

This topic on Jinja templates is quite comprehensive and can be quite in-depth. Therefore, we are unable to cover all of the details in this tutorial. If you want to learn more about Jinja templates, we recommend exploring additional resources on the subject, such as this jinja template and jinja documentation.

Create a directory named templates, then create the following files:

  • base.html

  • index.html

  • publish.html

  • register.html

  • login.html

Base template

A base template in Jinja is a template that defines the overall structure of a page and provides a place for other templates to "inherit" from and override certain blocks of content.

It typically includes elements that are common to all pages of the site, such as the header, navigation, and footer. This allows developers to create a consistent look and feel for the site, while also making it easier to update the layout or design in one place rather than having to make changes to every individual page.

I recommend starting with Jinja templates to get a foundational understanding before moving on with me.

Our base.html template will contain:

{% if current_user.is_authenticated %}
    <li class="active"><a href="{{url_for('views.index')}}">Home</a></li>
    <li><a href="{{url_for('views.publish')}}">Publish</a></li>
    <li><a href="{{url_for('views.about')}}">About</a></li>
    <li><a href="{{url_for('user.contact')}}">Contact</a></li>
    <li><a id="logout" href="{{url_for('user.logout')}}">Logout</a><li>

    {% else %}
    <li class="active"><a href="{{url_for('views.index')}}">Home</a></li>
    <li><a href="{{url_for('views.about')}}">About</a></li>
    <li><a href="{{url_for('user.contact')}}">Contact</a></li>
    <li><a href="{{url_for('user.register')}}">Register</a></li>    
    <li><a id="login" href="{{url_for('user.login')}}">Login</a><li>

    {% endif %}
    </ul>

The code above is defining an HTML navigation menu. The menu will contain different items depending on whether the user is authenticated or not. If the user is authenticated (i.e. logged in), the menu will contain the following items: "Home", "Publish", "About", "Contact", and "Logout". If the user is not authenticated, the menu will contain the following items: "Home", "About", "Contact", "Register", and "Login".

The {% if %} and {% else %} tags define a control structure that allows the code to include or exclude certain blocks of HTML depending on the value of the expression within the if tag. In this case, the expression is current_user.is_authenticated, which will be either True or False depending on whether the user is authenticated.

The {{ }} tags are used to output the value of a variable. In this case, the url_for() function is being used to generate URLs for the different menu items. The argument to the url_for() function is the name of a view function, which is a Python function that generates a response to a request.

Flash Messages

A flash message is a short message that appears on a webpage to communicate with the user. It is typically used to alert the user to something, such as an error, success, or warning. Flash messages are often temporary and will disappear after a short period of time or after the user has taken a certain action. They are useful for providing feedback to the user about their actions on the website.

<main>
<div class="container">
        <!-- alerts for errors -->
        {% with errors = get_flashed_messages(category_filter=["error"]) %}
        {% if errors %}
        <div id="failure" class="alert alert-danger alert-dismissible fade show"
 role="alert" data-bs-dismiss="alert"
        align="center">
        {{ messages }}
        {% for message in errors %}
        {{ message }}
        {% endfor %}
        </div>
        {% endif %}
        {% endwith %}

        <!-- alerts for success -->
        {% with success = get_flashed_messages(category_filter=["success"]) %}
        {% if success %}
        <div class="alert alert-success alert-dismissible fade show"
         role="alert" data-bs-dismiss="alert" align="center">
        {{ messages }}
        {% for message in success %}
        {{ message }}
        {% endfor %}
        </div>
        {% endif %}
        {% endwith %}

        </div>

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

</main>

The code first uses the get_flashed_messages() function to retrieve any error messages that were previously "flashed" to the user using Flask's flash() function. The with statement is used to assign the returned error messages to a variable named errors. The if statement checks if there are any error messages, and if so, it creates a div element with the class "alert alert-danger" and displays the error messages.

The same process is repeated for success messages, but with different classes for the div element.

The last section of the code creates a container for the content of the web page, and the {% block content %}{% endblock %} is used to define a block where other templates that extend this template can insert their own content.

This layout template provides the structure for the webpage and it will be used for all the pages that use this template. Also, it implements flash messages, which are messages that are passed to the next request after a redirect. They are typically used to show success or error messages to the user.

Moving on,

The next thing we will do is to create our first route which is our home page.

When creating our routes, we can make use of all the files in our resources folder. Recall that we created our Blueprints earlier. That's where they come into play!

In our views.py file, we'll set up some basic routes such as the home page, about, and contact pages.

Let’s Import the following:

from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
from models import ArticleModel, UserModel
from db import db
  • Home page

For the home page, we can use the URL "/" to handle the index route. In a Flask app, we can use the @app.route() decorator to define a route and pass in the URL as an argument. For example:

@app.route('/')
def index():
    return 'Welcome to the home page!'

However, if we are using Blueprints in our application, we will use the name of the Blueprint in place of app when defining the route. For example:

@view.route('/')
def index():
    return 'Welcome to the home page!'

This way, we can organize our routes and views into logical groups using Blueprints."

This is what our index route will look like:

@view.route("/")
@view.route("/index")
def index():
    articles = ArticleModel.query.all()
    return render_template("index.html", user=current_user, articles=articles)

This code is defining a function called index that will be run when the server receives a request to the "/" or "/index" route.

The function retrieves a list of all ArticleModel objects from a database using the ArticleModel.query.all() line of code and then renders the index.html template, passing it the list of articles and the current_user object as arguments. The render_template function is a Flask function that generates an HTML response to the client by rendering an HTML template and injecting the variables provided as arguments into the template.

The @view.route decorator above the function definition specifies the route (i.e., the URL path) that should trigger this function to run. The @view.route("/") decorator specifies the root route (i.e., the home page) and the @view.route("/index") decorator specifies an alternative route to the same function.

  • About page

Here, we will create a page that renders information about the application. It is a static page that does not require the user’s input or interaction.

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

<section>
... write your about code here ...
</section>

{% endblock content %}

The following code sets up a route for the /about URL, and when that route is requested, it will render the about.html template and return it to the user.

# about route displays info about the page
@view.route('/about')
def about():
        return render_template('about.html')
  • Contact page

Here, we will create a page that renders information about the application. It is a static page that does not require the user’s input or interaction.

{% extends 'base.html' %}
{% block title%} Login {% endblock %}
{% block content %}
<form method="POST">
 <div>
  <label for="name">Name</label>
 </div>
        ... input your form details here ...
 <div>
  <button>Submit</button>
 </div>
</form>

{% endblock content %}

Do this in your **view.py** file

The following code sets up a route for the /contact URL that can handle both GET and POST requests. If the request is a POST request, a flash message is sent to the user. In either case, the contact.html template is rendered and returned to the user.

@user.route("/contact", methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
            flash('We appreciate the feedback, be on the lookout for our response', category='success')
    return render_template("contact.html", current_user=current_user)

User Authentication

  • Register user

  • Login user

  • Logout user

Inside the register.html

This template defines an HTML form that allows the user to register by entering their email address, password and any other required info. The form will be inserted into the base.html template and the resulting page will have the title "Home".

{% extends 'base.html' %}
{% block title %}Home{% endblock %}
{% block content %}

            <div class="reg">
                <h3>Register Now</h3>
            </div>

    <form class="forms" action= "{{url_for('user.register')}}" method="post" method="" id="register">
        <div>
            <label for="email"></label>Email:</label>
            <input type="email" id="email" name="email" required>
        </div><br>

        <div>
            <label for="password">Password(Minimum of 8 characters):</label>
            <input type="password" id="password" name="password" minlength="8" required>
        </div><br>

        <div>
            <button>Submit</button>
        </div>

    </form>
{% endblock content %}

In our user.py, we’ll create our register route.

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

        password_hash = generate_password_hash(password)
        email_exists = UserModel.query.filter_by(email=email).first()
        if email_exists:
            flash('This mail already exists!', category='error')
        elif password != confirm_password:
            flash('Password does not match.', category='error')
        elif len(password) < 8:
            flash('Password is too short.', category='error')
        elif len(email) < 5:
            flash('Invalid email.', category='error')
        else:
            new_user = UserModel(firstname=firstname, lastname=lastname, username = username, email = email, password_hash = password_hash)
            db.session.add(new_user)
            db.session.commit()
            flash('Your account has been created!')
            login_user(new_user, remember=True)

            return redirect(url_for('view.index'))

    return render_template('register.html')

This function defines a route for the Flask web framework. The route is defined using the @user.route decorator, which specifies that this function should be called when the user visits the /signup URL.

The route can handle both GET and POST requests, as specified by the methods parameter. If the request is a POST request, the function retrieves the form data from the request object and stores it in local variables. It then checks whether the email already exists in the database by querying the UserModel. If the email does not exist, the function checks whether the password and confirm_password fields match. If they match and the password is long enough, it creates a new user object and adds it to the database. Finally, it logs the user in and redirects them to the index page. If any of the validation checks fail, it displays an error message using the Flask flash function.

If the request is a GET request, the function simply renders the register.html template using the Flask render_template function.

Login User

The login process typically involves verifying the entered credentials against a database of authorized users to determine whether the user has the correct login credentials. If the entered credentials are correct, the user is granted access to the system or website. If the credentials are incorrect, the login process may be repeated until the correct credentials are entered, or the user may be denied access.

Inside the login.html file

{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<form method="POST">
    <br>
    <h1 class="reg"> HELLO THERE!</h1>
    <div class="form-group">
        <fieldset>
            <label for="email"> EMAIL:</label>
            <input type="text" name="email" id="email" class="form-control" placeholder="Enter Email" required>
        </fieldset>
        <br>
        <fieldset>
            <label for="password"> PASSWORD:</label>
            <input type="password" name="password" id="password" class="form-control" placeholder="Enter Password"
                required>
        </fieldset>
        <br>
        <fieldset align="center">
            <button type="submit" id="button" class="button">SUBMIT</button>
        </fieldset>
    </div>
</form>
{% endblock %}

We’ll create the login route in our user.py file

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

        user = UserModel.query.filter_by(email=email).first()
        if user:
            if check_password_hash(user.password_hash, password):
                flash(f"Good to have you back, {user.username}", category='success')
                login_user(user, remember=True)
                return redirect(url_for('view.index'))
            else:
                flash("Incorrect password!", category='error')
        else:
            flash('Email does not exist.', category='error')

    return render_template("login.html")

When the route is accessed with the POST method, the code attempts to retrieve the user's email and password from the request form and use them to log the user in.

To log the user in, the code first queries a database for a user with the matching email. If a user with that email exists, it checks whether the password provided by the user matches the hashed password stored in the database for that user. If the passwords match, the user is logged in and redirected to the index page. If the passwords do not match, or if no user with the provided email exists, an error message is displayed.

If the route is accessed with the GET method, the code simply renders a template for the login page.

Logout route:

@auth.route('/logout')
def logout():
    logout_user()
    flash('Logout successful')
    return redirect(url_for('view.index'))

This code defines a route for logging a user out of the system. The route is accessed through the '/logout' URL and can be accessed using any HTTP method.

When the route is accessed, the code calls the logout_user() function, which is likely to clear any user session information or authentication tokens that were set when the user logged in. After logging the user out, the code displays a message indicating that the logout was successful and redirects the user to the index page.

Article.py

Our article.py will handle some of the very basic routes, such as:

  • Publish article

  • Edit article

  • Delete article

Publish.html

This template defines an HTML form that allows the user to publish by filling in the **title** and **bodytext** fields. The form will be inserted into the publish.html template.

{% extends 'base.html' %}
{% block title %}index{% endblock %}
{% block content %}
<div class="reg">
    <h3>Publish your article</h3>
</div>

<form class="forms" action="{{url_for('view.publish')}}"method="POST">
        <div>
                <label for="title">Title:</label>
                <input type="text" id="title" name="title" required>
        </div>

        <div class="body">
            <h5>Text:</h5>
            <textarea name="bodytext" id="result" class="txt" placeholder="Type here...">
            </textarea>
        </div>

        <button class="pub">Publish</button>
</form>
{% endblock content %}

Just like in our view.py, we’ll do the necessary imports and then proceed to write our code.

@view.route("/publish", methods=['GET', 'POST'])
@login_required
def publish():
    if request.method == "POST":
        title = request.form.get('title')
        bodytext = request.form.get('bodytext')

        if not bodytext and not title:
            flash('Please fill in the Title and Bodytext fields to proceed')
        else:
            article = ArticleModel(title=title, bodytext=bodytext, author=current_user.id)
            db.session.add(article)
            db.session.commit()
            flash("Your article has been successfully published", category='success')
            return redirect(url_for('view.index'))
    return render_template('publish.html')

After creating a database model, the publish function saves the title and bodytext from the form and adds them to the database using db.session.add. The database is then updated with db.session.commit. The user is then redirected to the index page using the url_for function.

The publish function uses the publish.html form to gather data from the user's browser and stores it in ArticleModel on the server. It then redirects the user back to the index page

Note: The HTTP "GET" and "POST" methods are used to retrieve data from and send data to a database, respectively. "GET" is the default method, and sends a request to the server to retrieve data. The server then returns the requested data. "POST" is used to send data from an HTML form to the server. This data is not cached by the server.

Edit Article

The edit function retrieves article data from the database to the browser. Once a user makes necessary changes, it updates the changes.

@article.route("/edit/<article_id>", methods=['GET', 'POST'])
@login_required
def edit(article_id):
    article = ArticleModel.query.filter_by(id=article_id).first()
    if request.method == 'POST':
        title = request.form.get('title')
        bodytext = request.form.get('bodytext')

        if not article:
            flash("We can't find your article, Try creating and publising one.", category='error')
        elif current_user.id != article.author:
            flash("You are not allowed to make changes to this article.", category='error')
        else:
            article.title = title
            article.bodytext = bodytext
            db.session.commit()
            flash('You successfully edited this article', category='success')
        return redirect(url_for('view.index'))
    return render_template('edit.html', article=article)

This code defines a route for a webpage that allows a user to edit an article. The route is set to accept both GET and POST requests, and is decorated with the @login_required decorator, which means that a user must be logged in to access this route.

The route is defined to accept a parameter, article_id, which is the ID of the article that the user wants to edit. The function first retrieves the article from the database using the ArticleModel.query.filter_by(id=article_id) method.

If the request method is a POST request, the function gets the new title and body text for the article from the request form. If the article does not exist or the user attempting to edit the article is not the author of the article, the function displays an error message using the flash function. Otherwise, the function updates the article with the new title and body text, commits the changes to the database, and displays a success message. Finally, the function redirects the user back to the index page.

If the request method is a GET request, the function simply renders the edit.html template and passes the article object to the template as a variable.

Delete Article

The delete function erases the article data from the database.

@article.route('/delete/<article_id>')
def delete(article_id):
    article = ArticleModel.query.filter_by(id=article_id).first()

    if not article:
        flash("This article does not exist.", category='error')
    elif current_user.id != article.author:
        flash('You are not allowed to delete this article', category='error')
    else:
        db.session.delete(article)
        db.session.commit()
        flash('You have deleted this article', category='success')
    return redirect(url_for('view.index'))

Styling with CSS

Enhance the visual appeal and user experience of your blog by incorporating CSS. Unfortunately, due to time constraints, we will not be covering that aspect in this tutorial.

Now that we are done building our blog project, we will go ahead and test our code.

To test your code, run either of the following commands on your terminal:

$ python app.py
$ flask run

Copy the URL to your browser. You can now interact with your code, and see if all your functions and routes are working.

Note: If you want to terminate the flask process on your terminal, hit ctrl c on your keyboard. Then you may now deactivate your virtual environment.

Summary of what we did:

At the end of this project, we worked on:

  • Setting up our flask environment

  • Use of jinja templates

  • CRUD Operations

  • Use of Werkzeug to hash our passwords

  • Flash messages

  • User authentication.

Conclusion

We are pleased to have completed a successful blog project using the Flask framework, which allowed us to showcase our skills. I hope this project was a valuable learning experience for you, and I look forward to collaborating on future projects. As a next step, I recommend we consider implementing user profiles and integrating social media features. In the future, I plan to document my learning journey and would appreciate your feedback and comments.

You can follow me here on Starr’s Space. Also connect with me on Twitter, GitHub, Hashnode, and Medium.

Â