Introduction
Flask is a lightweight Python web framework that provides useful tools and features for creating web applications. It's a microframework that doesn't include an ORM (Object Relational Mapper) or such features.
Creating a blog page using Flask is a great way to get started with web development using Python. In this tutorial, you'll learn how to set up a Flask application, create templates and routes, and work with a database to store blog posts.
Prerequisites
To follow this tutorial, you'll need:
Basic knowledge of Python
A text editor or Integrated Development Environment (IDE)
Flask and any other libraries specified in the tutorial
Setting up the Flask Application
To create a Flask application, you'll need to install Flask using pip, the Python package manager. Open a terminal and enter the following command:
pip install flask
Also,you need to install the following
Flask_SQLAlchemy
Flask_Login
by running the following command in your terminal respectively:
pip install flask-sqlalchemy
pip install flask-login
Also, creating a virtual environment is essential, you can activate your virtual environment if you have not done so, make sure you’re in your project directory and use the following command to activate the environment:
python -m venv env
The command above helps you create a virtual environment(venv) named ‘env’.
The command below is useful in knowing how to activate and deactivate your env on Windows
env\Scripts\activate.bat
env\Scripts\deactivate.bat
Let us now show you how to go about arranging your files for your application.
The templates folder consists of all your Jinja_templates used in building the UI(User Interface) of your application.
The ‘app.py’ houses all your python functional models and the creation of your Database.
The blog application consists of implemented functions like:
Login user(authentication process).
SignUp new user.
Inability to edit or delete posts not owned.
To make things easier, anytime you are building a Flask project, ensure to always have your schema or models.
class user(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(30), nullable=False)
email = db.Column(db.String(30), nullable=False)
password = db.Column(db.String(30), nullable=False)
def __repr__(self):
return '<user %r>' % self.username
The model above is clearly telling your route in either the login or signup form to expect the user model.
When dealing with the Post model, then you can put it this way
class post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(42), nullable=False)
content = db.Column(db.String(10000), nullable=False)
author = db.Column(db.String(30), nullable=False)
def __repr__(self):
return '<post %r>' % self.author
When creating an application, it's important to point out the essential part such as the LogIn and SignUp.
Routes in Flask
The routing in Flask is easier, more comfortable, and more convenient to use when dealing with routing on other Jinja_templates pages.
An example of routing to a home template in Flask:
@app.route('/')
def home():
# routing to home page
return render_template('index.html')
This routing method is applicable to all templates you want to use it on. The only changes that will be made severally will be the path for all routed templates and render_template(<file name>).
Writing HTML Templates
Instead of returning a hardcoded string like "Hello, World!", you can use templates to create a more dynamic and customizable website. Flask uses the Jinja2 template engine to render templates.
To display dynamic content on your blog page, you can use templates. Flask uses the Jinja2 template engine to parse templates and generate HTML.
To create a template, create a new folder called templates
in the same directory as your Python file. Inside the templates
folder, create a new HTML file for your template.
For example, you can create a template called home.html
for the home route:
<html>
<head>
<title>My Blog</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
The {% block %}
tags define a block that can be overridden in a child template.
Next, create a template for the homepage of the blog. In the templates
directory, create a new file called index.html
and include the following code:
To render this template in your view function, use the render_template
function from the Flask module:
from flask import render_template
@app.route('/')
def home():
return render_template('home.html')
You can pass variables to your template using the render_template
function. For example, you can pass a list of blog posts to be displayed on the home page:
You can then access the posts
variable in your template using Jinja2 syntax:
posts = [
{ 'author': 'John Smith',
'title': 'My first post',
'content': 'This is the content of my first post',
'date_posted': 'January 1, 2021' },
{ 'author': 'Jane Doe',
'title': 'My second post',
'content': 'This is the content of my second post', 'date_posted': 'January 2, 2021' }
]
@app.route('/')
def home():
return render_template('home.html', posts=posts)
You can then access the posts
variable in your template using Jinja2 syntax:
{% extends "base.html" %}
{% block content %}
<h1>My Blog</h1>
{% for post in posts %}
<h2>{{ post.title }}</h2>
The Authentication Process
The auth process of the blog was achieved with the help of the flask_login which helps in importing other packages alongside. Soft right!?!
from flask_login import LoginManager, login_user, login_required,
logout_user, current_user, UserMixin
In this process, all flask_login packages have their uses.
The login_user is used when you create a function that helps users to login into their dashboard.
The login_required is useful in restricting unregistered users from accessing the secure parts of the blog hence users will have to sign up to enjoy all features.
The current_user helps pick an element of a particular user to be used in a template.
Step One: The Signup Process
# route for signup
@app.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == 'POST':
name = request.form.get('name')
email = request.form.get('email')
password = request.form.get('password')
user_exist = User.query.filter_by(
name=name, email=email, password=password).first()
if user_exist:
flash('User already exist', 'error')
return redirect(url_for('signup'))
user = User(name=name, email=email, password=password)
db.session.add(user)
db.session.commit()
flash('User created successfully', 'success')
return redirect(url_for('login'))
return render_template('signup.html')
As indicated in the snapcode above, the route indicates the signup route and it renders its template at signup.html (you can always name it whatever you prefer).
Since it is a Signup page, we focus on the POST method.
def signup():
if request.method == 'POST':
name = request.form.get('name')
email = request.form.get('email')
password = request.form.get('password')
Define your function as signup and use the if or else statement as shown above, if the request.method is equal to POST then the stored variables should pick from our jinja_template where we have the naming in the form from the templates. Check the syntax below:
<input autocomplete="off" type="text" name="email" id="email" placeholder="Enter your email address"
class="w-full md:w-[50%] border-[1px] border-[#000] rounded-[5px] py-[10px] px-[15px] mt-[5px] focus:outline-none">
From the above, you can see the name is equal to the email included. It helps when dealing with any request on the POST method and GET method.
user_exist = User.query.filter_by(
name=name, email=email, password=password).first()
if user_exist:
flash('User already exist', 'error')
return redirect(url_for('signup'))
user = User(name=name, email=email, password=password)
db.session.add(user)
db.session.commit()
flash('User created successfully', 'success')
return redirect(url_for('login'))
return render_template('signup.html')
From the above, the user_exist which is our variable holding the data on queries filtered from the DB (database) tables.
Following by checking user_exist
This checks if the user exists in the database, then if it exists flash an error saying ‘user already exists’ with the category of ‘error’. and we display this on the frontend with:
{% with errors = get_flashed_messages(category_filter=["error"]) %}
{% if errors %}
<div id="error"
class="bg-[#dc2626] flex space-x-[10px] items-center p-[10px] text-[#fff] text-[14px] w-full md:w-[50%]">
<div id="cancelerror" class="close font-[600] text-[16px]">×</div>
<div>
{%- for msg in errors %}
<span>{{ msg }}</span>
{% endfor -%}
</div>
</div>
{% endif %}
{% endwith %}
The above code helps display the error output on the frontend.
Now if the user doesn’t exist, the user is added to the Database(DB) session which then commits and redirects to the login template.
user = User(name=name, email=email, password=password)
db.session.add(user)
db.session.commit()
flash('User created successfully', 'success')
return redirect(url_for('login'))
Step Two: The Login Process
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')
user = User.query.filter_by(email=email, password=password).first()
if user:
login_user(user)
return redirect(url_for('dashboard'))
else:
flash('Invalid credentials', 'error')
return redirect(url_for('login'))
return render_template('login.html')
This briefly explains the route of login with a function in it using the POST method.
From the lookup you can see we have the if or else statement here saying at first go:
if request.method == 'POST':
email = request.form.get('email')
password = request.form.get('password')
user = User.query.filter_by(email=email, password=password).first()
if user:
login_user(user)
return redirect(url_for('dashboard'))
if the request method is POST then do the following:
~ Get the request forms and query.filter from the User model.
~ If the user exists redirect the user to the dashboard.
But if the user doesn’t exist
else:
flash('Invalid credentials', 'error')
return redirect(url_for('login'))
Flash message on the frontend with the message saying ‘Invalid credentials’ alongside the category of ‘error’ and redirect the user back to the login page.
Step Three: Displaying Blogs On The Dashboard Only For The Current User
@app.route('/dashboard')
@login_required
def dashboard():
user = current_user
blogs = Post.query.all()
if user:
flash('You are successfully logged in', 'success')
else:
pass
return render_template('dashboard.html', blogs=blogs, user=user)
This explains the route pointing unto the dashboard with the authentication of the user must be logged in to access the page, which is ‘@login required’. Now, we have the variable called user which houses the current_user, and blog which houses the query.all from the Post model in the DB.
If it’s the current user, it flashes a message on the frontend saying ‘You are successfully logged in’ with the category of ‘success’. In the render_template we pass our blog which is queried and user storing our current user.
{% for blog in blogs %}
{% if user.name == blog.author %}
<div
class="bg-[#f9fafb] h-fit md:h-fit mt-[30px] ml-[10px] overflow-y-scroll p-[10px] shadow-lg w-full md:w-[45%] rounded-[6px]">
<div class="font-[700] text-[15px] text-[#134e4a]">{{ blog.title }}</div>
<div class="content font-[500] overflow-scroll h-fit w-full text-[15px] text-[#000] mt-[10px]">
<div class="overflow-scroll w-fit">{{ blog.content }}</div>
</div>
<div class="font-[500] text-[12px] md:text-[15px] text-[#134e4a] mt-[5px]">Created at: {{
blog.date }} by
{{ blog.author }}</div>
As you can see, we loop over the blog from the Post model and display it on the frontend with its' different post type/naming i.e by id.
Modifying Posts
Step Four: Creating And Deleting A Post By Its' User
@app.route('/create/<int:id>', methods=['GET', 'POST'])
@login_required
def create(id):
user = current_user
blog = Post.query.all()
if request.method == 'POST':
title = request.form.get('title')
content = request.form.get('content')
author = request.form.get('author')
blog = Post(title=title, content=content, author=author)
db.session.add(blog)
db.session.commit()
return redirect(url_for('dashboard'))
# create blog only for the current user
return render_template('createblog.html', user=user)
@app.route('/delete/<int:id>')
@login_required
def delete(id):
user = current_user
blog = Post.query.get_or_404(id)
if blog.author == user.name:
db.session.delete(blog)
db.session.commit()
return redirect(url_for('dashboard', id=id))
else:
flash('You are not authorized to delete this blog', 'error')
return redirect(url_for('dashboard'))
For this process, we have the route of creating and deleting by its identity in integer form which only authenticated users can create or can only delete a post the user created.
Step Five: Editing A Post By Its User
@app.route('/edit/<int:id>', methods=['GET', 'POST'])
@login_required
def edit(id):
blog = Post.query.get_or_404(id)
user = current_user
if request.method == 'POST':
blog.title = request.form.get('title')
blog.content = request.form.get('content')
db.session.commit()
return redirect(url_for('dashboard'))
# update blog only for the correct current user
if blog.author != user.name:
return redirect(url_for('dashboard'))
if blog.author == user.name:
return render_template('edit.html', blog=blog, user=user)
For this process we have the route of creating and deleting by its identity in integer form which only authenticated users can edit a post created by the current user.
<a href="{{ url_for('edit' ,id = blog.id) }}">
<div>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" />
</svg>
</div>
</a>
<!--delete svg-->
<a href="{{ url_for('delete' ,id = blog.id) }}">
<div class="text-red-900">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
<path stroke-linecap="round" stroke-linejoin="round"
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" />
</svg>
</div>
</a>
The above syntax is displaying the placement on the template of the dashboard where only its user is authorized to create, read, edit and delete his post.
Step Six: Reading Accessibility To Other Users' Posts
@app.route('/blog/<int:id>')
def blog(id):
blog = Post.query.filter_by(id=id).first()
return render_template('readmoreblog.html', blog=blog)
This function embedded into a route helps other user read more or in other terms view more on the post that user is reading.
And yeah you can add a logout function also check it out on step seven (7).
Step Seven: Logging Out a User
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('home'))
The ‘logout_user()’ logs out only that user that is ready to log out and not other users because the flask_login stores the active user’s ID in the Flask Session, and allows you easily log them in and out.
Okkkk!
I do hope I have been of great help!
Thank you for your time.xoxo