Table of contents
- Introduction
- Flask?...
- Virtual Environment
- Starting
- Rendering HTML
- Jinja templating
- What is Jinja?
- Bootstrap
- Creating our Navbar
- Login page
- HTTP Methods
- Setting up database
- Managing login
- New user
- Hashing password
- Logging in Users
- 'Logged in'Flash
- Restrictions
- Dynamic navbar
- Creating a post
- Liking posts
- And now we are done!!
- Conclusion
Introduction
Hey there:) I am about to take you on my journey of creating a blog using the Flask application (python) as a beginner.
Before we get started, let me shed a little light on what flask is.
Flask?...
Flask is a small web framework used to make using python easier. it is designed to make the use of python easier. in case you are wondering what a web framework is, it is a package/ collection of packages that are used by developers to create web applications.
In this tutorial, I'll be building a small blogging web app that would contain the following:
Home page
Login page
Register page
New post page
Edit post page
Virtual Environment
In python, before doing anything. It is advisable to create a virtual environment and use it to install frameworks so your project folder looks neat.
To do so,
Python3 -m venv name_of_virtual_enviroment
Starting
We are going to start by creating a folder in vs code called general
Next, we are going to make a file called app.py.
Note: file app.py should not be inside the general folder.
Now we need to install a few packages
Pip install flask sqlalchemy
and flask login
– this would help us authenticate our users
pip install flask sqlalchemy
pip install flask login
Now we are ready to create our flask application
In the general folder, we are going to create a file called init.py- this is going to initialize our flask app.
To do so, we need to import a few modules:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from os import path
The next thing we are going to do is create a function as follows (This is always required when making a flask app). We are going to also need to configure some variables for flask, this would be used to secure session data, so a secret key is needed. I used a random code generator, because “we don’t want ANYBODY to guess It “.
def create_app():
app=Flask(__name__)
app.config['SECRET_KEY'] ='4dbd2e68bf5..........'
return app
What we need to do now is go to app.py to import our general folder.
Then run debug function (this means that whenever we make a particular change to our code, it automatically runs the server)
from general import create_app
if __name__ == "__main__":
app = create_app()
app.run(debug=True)
Now to run the flask app, we click run python file. copy the link pasted in the terminal.
Now we need to manually create various routes we can access in the create app function.
I like my code a little organized, so we are going to create separate files for the route. so we are going to create two files, one will be called form.py and the other, display.py.
Form.py is going to contain all the routes for login, signout, authentication etc, While the display.py will have to contain the more obvious systems like the new posts page, home page and edit post page.
Inside our display.py we are going to import the blueprint and pass a name(display)
from flask import Blueprint
display = Blueprint("display", __name__)
display.route("/")
display.route("/home")
def home():
posts = Post.query.all()
return print('home page)
We have two routes so that whether we go to "/" or "/ home" it takes us to that same place.
we need to link this to our flask application, so in our init.py, we import the variable 'display' and register it.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from os import path
def create_app():
app=Flask(__name__)
app.config['SECRET_KEY'] ='4dbd2e68bf5f98122dd3e3341180c13d'
from .display import display
app.register_blueprint(display, url_prefix="/")
return app
Note: Using the dot before display shows that it is a relative import; this is because we are inside a python package.
Now for the form.py, we do the same thing; but this time for the register, login and logout
from flask import Blueprint
display = Blueprint("form", __name__)
form.route("/register")
def register():
return "register"
form.route("/login")
def login():
return "login"
form.route("/logout")
def logout():
return "logout"
Now that we are done, we need to register the blueprint for the form also and it's going to look something like this:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
from os import path
def create_app():
app=Flask(__name__)
app.config['SECRET_KEY'] ='4dbd2e68bf5f98122dd3e3341180c13d'
from .display import display
from .form import form
app.register_blueprint(display, url_prefix="/")
app.register_blueprint(form, url_prefix="/")
return app
Rendering HTML
To do this, we import render_template in our form.py and display.py, then create a folder called templates. In the templates folder, we are going to create a file called base.html. The content of our base.html file should look like this:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/css/main.css">
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Arsenal:wght@400;700&family=DM+Serif+Display:ital@1&family=JetBrains+Mono:ital,wght@0,100;0,400;0,500;0,800;1,100;1,200&family=Limelight&family=Macondo&family=Mali:ital,wght@0,400;1,200;1,500;1,700&family=Modak&family=Nunito:ital,wght@0,200;0,300;0,400;1,200;1,500&family=Playfair+Display:ital,wght@0,400;0,500;0,600;1,600&family=Poppins:wght@100;200;300&family=Syne:wght@400;500;600;700&family=Viaoda+Libre&display=swap" rel="stylesheet">
<title> </title>
</head>
<body>
</body>
</html>
Now that we have our base.html file, we are going to change our return string in to render template.
from flask import Blueprint
display = Blueprint("display", __name__)
display.route("/")
display.route("/home")
def home():
posts = Post.query.all()
return render_template("home.html")
Jinja templating
What is Jinja?
Let me help... Jinja is a powerful templating language for Python, which allows you to generate text output from input data and a template.
To make use of the Jinja template, the other templates should be able to inherit from the base template.
So to do this we are going to create another file called home.html, this is going to inherit the blocks of code from the base.html. As shown below:
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
{% block header %}All Posts {% endblock %}
{% endblock %}
Then we are going to make a few more files that are going to do the same thing ie login.html, logout.html, edit.html, posts.html, register.html and new_post.html.
Bootstrap
Bootstrap is a powerful desktop app for designing and prototyping websites. It's like CSS but contains prebuilt codes.
We are going to go over to the bootstrap website, to retrieve some code snippets that we are going to be used in our code.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha512-MV7K8+y+gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK+iQrJ7lzPJQd1w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Arsenal:wght@400;700&family=DM+Serif+Display:ital@1&family=JetBrains+Mono:ital,wght@0,100;0,400;0,500;0,800;1,100;1,200&family=Limelight&family=Macondo&family=Mali:ital,wght@0,400;1,200;1,500;1,700&family=Modak&family=Nunito:ital,wght@0,200;0,300;0,400;1,200;1,500&family=Playfair+Display:ital,wght@0,400;0,500;0,600;1,600&family=Poppins:wght@100;200;300&family=Syne:wght@400;500;600;700&family=Viaoda+Libre&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<title>{% block title %} {% endblock %}</title>
</head>
<body>
<div>{% block content %} {% endblock %}</div>
<!-- bootstrap js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js" integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js" integrity="sha384-IDwe1+LCz02ROU9k972gdyvl+AESN10+x7tBKgc9I5HFtuNz0wWnPclzo6p9vxnk" crossorigin="anonymous"></script>
</body>
</html>
Note: We need to place the javascript code at the bottom of our base.html while the rest will be right above the title.
Creating our Navbar
The most useful bootstrap code is the navigation bar which should be added to the body tag of our base.html. This is also gotten from the bootstrap website; I decided to tweak mine a little by creating a static folder for CSS- to spice it up :)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css" integrity="sha512-MV7K8+y+gLIBoVD59lQIYicR65iaqukzvf/nwasF0nqhPay5w/9lJmVM2hMDcnK1OnMGCdVK+iQrJ7lzPJQd1w==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link href="https://fonts.googleapis.com/css2?family=Abril+Fatface&family=Arsenal:wght@400;700&family=DM+Serif+Display:ital@1&family=JetBrains+Mono:ital,wght@0,100;0,400;0,500;0,800;1,100;1,200&family=Limelight&family=Macondo&family=Mali:ital,wght@0,400;1,200;1,500;1,700&family=Modak&family=Nunito:ital,wght@0,200;0,300;0,400;1,200;1,500&family=Playfair+Display:ital,wght@0,400;0,500;0,600;1,600&family=Poppins:wght@100;200;300&family=Syne:wght@400;500;600;700&family=Viaoda+Libre&display=swap" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Zenh87qX5JnK2Jl0vWa8Ck2rdkQ2Bzep5IDxbcnCeuOxjzrPF/et3URy9Bv1WTRi" crossorigin="anonymous">
<title>{% block title %} {% endblock %}</title>
</head>
<body>
<div style=" background-color: rgb(171, 119, 230);" class="container">
<header style="align-items: center; " class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">
<a href="/" class="d-flex align-items-center col-md-4 mb-2 mb-md-0 text-dark text-decoration-none">
<div style="color:rgb(38, 0, 52) ; font-family: 'Modak', cursive;
font-size: 50px; " class="logo">ARTISTBLOG</div>
</a>
<ul style="font-family: 'Syne', sans-serif ;" class="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0">
<li><a href="/home" style="font-weight: 600; " class="nav-link px-2 link-dark">Home</a></li>
<li><a href="/contact" style="font-weight:600;" class="nav-link px-2 link-dark">Contact</a></li>
<li><a href="/about" style="font-weight: 600;" class="nav-link px-2 link-dark">About</a></li>
</ul>
<div style="font-family: 'Syne', sans-serif ;" class="col-md-4 text-end"></div>
{% if user.is_authenticated %}
<a href="/logout"> <button type="button" class="btn btn-outline-primary me-2">Logout</button></a>
{% else %}
<a href="/register"><button type="button" class="btn btn-primary">Register</button></a>
<a href="/login"> <button type="button" class="btn btn-outline-primary me-2">Login</button></a>
{% endif%}
</div>
</header>
</div>
<div>{% block content %} {% endblock %}</div>
<!-- bootstrap js -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js" integrity="sha384-oBqDVmMz9ATKxIep9tiCxS/Z9fNfEXiDAYTujMAeBAsjFuCZSmKbSSUnQlmh/jp3" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.min.js" integrity="sha384-IDwe1+LCz02ROU9k972gdyvl+AESN10+x7tBKgc9I5HFtuNz0wWnPclzo6p9vxnk" crossorigin="anonymous"></script>
</body>
</html>
This is what our navbar should look like:
Now to link the navbar to the other template pages, we are going to go to form.py and render template then enter the name of the template for all. And for the logout, we are going to need to redirect the user to a different page. We need to import redirect
and a few others which we will use later on. Our code is going to look something like this:
from flask import Blueprint,render_template,redirect,request, url_for,flash
from .models import User
form = Blueprint("form", __name__)
@form.route("/register")
def register():
@form.route("/login")
def login():
return render_template('login.html')
@form.route("/logout")
def logout():
logout_user()
return redirect(url_for('display.home'))
Note: The reason we use display.home is because we want to be redirected to the home function in display.py.
Login page
First of all, we are going to change our title to login. To define it we are going to start with a form tag, with a POST method, then we write down our form contents. It's going to look like this:
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<form action="" method="POST">
<h2 style="text-align: center;">Login</h2>
<div class="group">
<label for="email">Email Address</label>
<input type="text" name="email" id="email" class="form-control" placeholder="Enter Email" >
<label for="password">Password</label>
<input type="password" name="password" id="password" class="form-control" placeholder="Enter password" >
<br>
<div class="">
<button class="btnn" type="submit">Login</button>
</div>
</div>
</form>
{% endblock %}
We are going to do the same thing for the register.html page because they are similar but not entirely we just need to tweak it a little.
{% extends "base.html" %}
{% block title %}Register{% endblock %}
{% block content %}
<form action="" method="POST">
<h2 style="text-align: center;">Register</h2>
<div class="group">
<label for="email">Email Address</label>
<input type="text" name="email" id="email" class="form-control" placeholder="Enter Email" >
<label for="username">Username</label>
<input type="text" name="username" id="username" class="form-control" placeholder="Enter Username" >
<label for="password">Password</label>
<input type="password" name="password" id="password" class="form-control" placeholder="Enter password" >
<label for="cpassword">Password</label>
<input type="password" name="cpassword" id="cpassword" class="form-control" placeholder="Confirm password" >
<br>
<div class="">
<button class="btnn" type="submit">Register</button>
</div>
</div>
</form>
{% endblock %}
HTTP Methods
The Hypertext Transfer Protocol (HTTP) is designed to allow communications between clients and servers. HTTP works as a request-response protocol between a client and a server. The most commonly used HTTP methods are POST, GET, PUT, PATCH, and DELETE. In this tutorial, we are going to make use of POST and GET.
For us to be able to enable these methods, we need to go to form.py and define the methods for the login and register routes.
@form.route("/register", methods= ['GET', 'POST'])
def register():
return render_template('register.html')
@form.route("/login" ,methods= ['GET', 'POST'])
def login():
return render_template('login.html')
And now, write down the data we need to access in our form.
@form.route("/register", methods= ['GET', 'POST'])
def register():
if request.method == 'POST':
email = request.form.get("email")
username = request.form.get("username")
password = request.form.get("password")
cpassword = request.form.get("cpassword")
return render_template('register.html')
@form.route("/login" ,methods= ['GET', 'POST'])
def login():
if request.method == 'POST':
email = request.form.get("email")
password = request.form.get("password")
return render_template('login.html')
Setting up database
To enable us to use and store the previous data, a database needs to be created. So we are going to go into init.py to first create a variable.
Then configure our database inside the create app function and call it:
def create_app():
app=Flask(__name__)
app.config['SECRET_KEY'] ='4dbd2e68bf5f.............'
app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{DB_NAME}'
db.init_app(app)
create_database(app)
def create_database(app):
if not path.exists("general/" + DB_NAME):
app.app_context().push()
db.create_all()
print("Database Created!!")
Now that we have created our database, we need to create a new file named models.py
. This is where we are going to define all of our database models.
In our model.py file, we need to import a few things.
from . import db
from flask_login import UserMixin
from sqlalchemy.sql import func
The following code shows how to model our database:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), nullable=False, unique=True)
password = db.Column(db.String(100), nullable=False)
email= db.Column(db.String(120), nullable=False, unique= True)
date_created = db.Column(db.DateTime(timezone=True), default= func.now())
posts =db.relationship('Post', backref='user', passive_deletes= True)
This will provide us with the main information about the user on our blog.
Now that we have done that, we are going to import the user model into init.py. This is done so that the database is actually created. all the models need to be imported individually.
from .models import User
Managing login
To allow users to log in and out of the website so that they don't always have to enter their username and password every time, and access certain pages when they are logged in, we are going to need to set up login manager in our init.py.
lmanager = LoginManager()
lmanager.login_display = "form.login"
lmanager.init_app(app)
@lmanager.user_loader
def load_user(id):
return User.query.get(int(id))
return app
New user
When a new user wants to register, we need to make sure that that user's information is totally different from our current user's. And to do that we are going to add some lines of code to our form.py as follows:
@form.route("/register", methods= ['GET', 'POST'])
def register():
if request.method == 'POST':
email = request.form.get("email")
username = request.form.get("username")
password = request.form.get("password")
cpassword = request.form.get("cpassword")
email_in_use = User.query.filter_by(email=email).first()
username_in_use = User.query.filter_by(username=username).first()
if email_in_use:
flash('Email already exists.', category='error')
elif len(email) < 8:
flash('Email is invalid.', category='error')
elif username_in_use:
flash('Username already exists.',category='error')
elif len(username) < 2:
flash('Your username is too short.', category='error')
elif password!= cpassword:
flash('Passwords do not match', category='error')
elif len(password) < 8:
flash('Your password is too short.', category='error')
else:
new_user = User(email= email, username =username,password = generate_password_hash(password, method='sha256'))
db.session.add(new_user)
db.session.commit()
login_user(new_user, remember=True)
flash('User Created')
return redirect(url_for('display.home'))
return render_template('register.html')
@form.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).first()
if user:
if check_password_hash(user.password, password):
flash('Logged In', category='success')
login_user(user,remember=True)
return redirect(url_for('display.home'))
else:
flash('The password you entered is incorrect', category='error')
else:
flash('Email does not exist.', category='error')
return render_template('login.html')
@form.route("/logout")
def logout():
logout_user()
return redirect(url_for('display.home'))
The code above checks whether the email and username are already taken, if it is, it flashes a message to the user saying so, and also checks if the passwords match. and if all conditions are perfect, a new user will be registered.
After this is done, we are going to have to import login_user,logout_user,current_user, and login_required.
from flask_login import login_user, logout_user,current_user, login_required
Hashing password
The passwords in our form need to be protected, and the way we can do this is by hashing our password. When a password has been “hashed” it means it has been turned into a scrambled representation of itself.
So we are going to go to our form.py to import modules that can hash the password
from werkzeug.security import generate_password_hash,check_password_hash
If you noticed, we have already added the password hashing to the New user code above.
Logging in Users
In this section, we will make modifications to our form.py route for logging in users. this is shown below:
@form.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).first()
if user:
if check_password_hash(user.password, password):
flash('Logged In', category='success')
login_user(user,remember=True)
return redirect(url_for('display.home'))
else:
flash('The password you entered is incorrect', category='error')
else:
flash('Email does not exist.', category='error')
return render_template('login.html')
'Logged in'Flash
When our users are logged in or unable to be logged in, we need them to know that, this is where message flashing comes in.
Now we go base.html, and write the following code above the block content :
{% with messages = get_flashed_messages(with_categories=True) %}
{% if messages %}
{% for category, message in messages %}
{% if category=='error' %}
<div class="alert alert-danger alter-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% else %}
<div class="alert alert-success alter-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endif %}
{% endfor%}
{% endif %}
{% endwith %}
<div>{% block content %} {% endblock %}</div>
Note: To make our flash message look more attractive, we added some bootstrap codes above.
Restrictions
Whenever a user is not logged in, there are some pages that they cannot access and to enable that we have to go to display.py.
And what we are going to do is import a few modules i.e login_required
and current_user
, define them in our function.
from flask_login import login_required,current_user
Dynamic navbar
When our users are logged in, our navbar should not be showing log in again, rather it should show logout and then vice versa. So to do this, we need to go to our base.html template to add a logout tag to our navbar div.
<div style=" background-color: rgb(171, 119, 230);" class="container">
<header style="align-items: center; " class="d-flex flex-wrap align-items-center justify-content-center justify-content-md-between py-3 mb-4 border-bottom">
<a href="/" class="d-flex align-items-center col-md-4 mb-2 mb-md-0 text-dark text-decoration-none">
<div style="color:rgb(38, 0, 52) ; font-family: 'Modak', cursive;
font-size: 50px; " class="logo">ARTISTBLOG</div>
</a>
<ul style="font-family: 'Syne', sans-serif ;" class="nav col-12 col-md-auto mb-2 justify-content-center mb-md-0">
<li><a href="/home" style="font-weight: 600; " class="nav-link px-2 link-dark">Home</a></li>
<li><a href="/contact" style="font-weight:600;" class="nav-link px-2 link-dark">Contact</a></li>
<li><a href="/about" style="font-weight: 600;" class="nav-link px-2 link-dark">About</a></li>
</ul><div style="font-family: 'Syne', sans-serif ;" class="col-md-4 text-end"></div>
<a href="/logout"> <button type="button" class="btn btn-outline-primary me-2">Logout</button></a>
<a href="/register"><button type="button" class="btn btn-primary">Register</button></a>
<a href="/login"> <button type="button" class="btn btn-outline-primary me-2">Login</button></a>
</div>
So what we are going to do next is add an if statement to deterine what shows up when a user is logged in and when a user is not.
<div style="font-family: 'Syne', sans-serif ;" class="col-md-4 text-end"></div>
{% if user.is_authenticated %}
<a href="/logout"> <button type="button" class="btn btn-outline-primary me-2">Logout</button></a>
{% else %}
<a href="/register"><button type="button" class="btn btn-primary">Register</button></a>
<a href="/login"> <button type="button" class="btn btn-outline-primary me-2">Login</button></a>
{% endif%}
</div>
The user variable in our if statement above needs to be passed to this template, and to do that we need to go to form.py to add it to the form routes for login and signup.
@form.route("/register", methods= ['GET', 'POST'])
def register():
if request.method == 'POST':
email = request.form.get("email")
username = request.form.get("username")
password = request.form.get("password")
cpassword = request.form.get("cpassword")
email_in_use = User.query.filter_by(email=email).first()
username_in_use = User.query.filter_by(username=username).first()
if email_in_use:
flash('Email already exists.', category='error')
elif len(email) < 8:
flash('Email is invalid.', category='error')
elif username_in_use:
flash('Username already exists.',category='error')
elif len(username) < 2:
flash('Your username is too short.', category='error')
elif password!= cpassword:
flash('Passwords do not match', category='error')
elif len(password) < 8:
flash('Your password is too short.', category='error')
else:
new_user = User(email= email, username =username,password = generate_password_hash(password, method='sha256'))
db.session.add(new_user)
db.session.commit()
login_user(new_user, remember=True)
flash('User Created')
return redirect(url_for('display.home'))
return render_template('register.html', name=current_user)
@form.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).first()
if user:
if check_password_hash(user.password, password):
flash('Logged In', category='success')
login_user(user,remember=True)
return redirect(url_for('display.home'))
else:
flash('The password you entered is incorrect', category='error')
else:
flash('Email does not exist.', category='error')
return render_template('login.html', name=current_user)
and also make a small change to display.py by changing name=current_user
to user=current_user.
return render_template("home.html",user=current_user, posts=posts)
Creating a post
Now for the post page, we need to create a file called new_post.html.
In this file, we are going to have a textarea for the author to write a post, a back home button and a post button. The following code shows this:
@display.route("/new_post", methods=['GET','POST'] )
@login_required
def new_post():
if request.method =="POST":
text =request.form.get('text')
if not text:
flash('Post box cannot be empty', category='error')
else:
post =Post(text=text,author=current_user.id)
db.session.add(post)
db.session.commit()
flash('Post created!', category='success')
return redirect(url_for('display.home'))
return render_template("new_post.html",user=current_user)
Now we need to create a display for it so we can actually render it, so in our display.py we write the following code:
@display.route("/new_post", methods=['GET','POST'] )
@login_required
def new_post():
if request.method =="POST":
text =request.form.get('text')
if not text:
flash('Post box cannot be empty', category='error')
else:
post =Post(text=text,author=current_user.id)
db.session.add(post)
db.session.commit()
flash('Post created!', category='success')
return redirect(url_for('display.home'))
return render_template("new_post.html",user=current_user)
So basically, the code above will check whether a user wrote a post or not, if they did, a flashed message will show up to notify the user using bootstrap.
To create a post, a database model needs to be created to store the posts. We need to go to models.py to do this:
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
author = db.Column(db.Integer, db.ForeignKey('user.id', on_delete="CASCADE"),nullable=False)
text= db.Column(db.Text(120), nullable=False)
posted_on = db.Column(db.DateTime(timezone=True), default= func.now())
likes=db.relationship('Like', backref='post', passive_deletes= True)
The foreign key above is used to make sure the author actually exists, with this we will be able to access all of the author's posts, while ondelete
is used so that when a user deletes their account, all their posts and data will be deleted as well.
Now that we have created the post model, we will need to import it into init.py
from .models import User, Post
and in display.py, import post from model and db from '.'
from . import db
To get all the posts to show on the home page, we addposts = Post.query.all(), user=current_user and posts=posts
to our display.py home function.
@display.route("/")
@display.route("/home")
def home():
posts = Post.query.all()
return render_template("home.html",user=current_user, posts=posts)
Next, we go to home.html and write a for loop that will enable us to view the posts and users in a nice format.
{% for post in posts %}
<div class="box">
<div class="ibox">
<a href="/user/{{post.user.username}}">{{post.user.username}}</a>
<div class="btn-group">
<div></div>
<a href="/post-like/{{post.id}}"><i class="fas fa-thumbs-up">{{ post.likes |length}}</i></a>
</div>
{% if user.id == post.author %}
<div class="btn-group">
<a href="/edit/{{post.id}}" class="pbtn1">Edit</a>
<a href="/delete-post/{{post.id}}" class="pbtn2">Delete </a>
</div>
{% endif %}
</div>
<div class="boxbody">
<div class="boxtext">
{{post.text}}
</div>
</div>
<div class="boxbottom">
<hr>
{{post.posted_on}}
</div>
</div>
{% endfor %}
And with the code above, we also created a delete and edit button that can be used only if a user is the author of the post.
To make sure that the post actually deletes and that the user is the real author of the post, we need to create a delete route in display.py.
@display.route("/delete-post/<id>")
@login_required
def deletepost(id):
post =Post.query.filter_by(id=id).first()
if not post:
flash('Post is non-existent', category='error')
elif current_user.id == post.id:
flash('You cannot delete this post', category='error')
else:
db.session.delete(post)
db.session.commit()
flash('Post Deleted',category='success')
return redirect(url_for('display.home'))
And the same goes for editing...
@display.route("/edit/<id>", methods=['GET', 'POST'])
@login_required
def edit(id):
if request.method == 'POST':
text= request.form.get('text')
if not text:
flash('Only text can be entered', category='error')
else:
post = Post(text=text , author=current_user.id)
db.session.add(post)
db.session.commit()
flash('Post has been Updated',category='success')
return redirect(url_for('display.home'))
return render_template('edit.html', user=current_user, post=posts)
Liking posts
To enable likes in our post, we are going to create a database model.This is because it is associated with a post and we will need to store it somewhere.
So in our models.py we write the following code:
class Like(db.Model):
id = db.Column(db.Integer, primary_key=True)
posted_on = db.Column(db.DateTime(timezone=True), default= func.now())
author = db.Column(db.Integer, db.ForeignKey('user.id', on_delete="CASCADE"),nullable=False)
post_id= db.Column(db.Integer, db.ForeignKey('post.id', on_delete="CASCADE"),nullable=False)
After this, we need to add likes=db.relationship('Like', backref='user', passive_deletes= True)
to the bottom of our User model and Post model to add a relationship.
This is what our code would look like:
from . import db
from flask_login import UserMixin
from sqlalchemy.sql import func
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), nullable=False, unique=True)
password = db.Column(db.String(100), nullable=False)
email= db.Column(db.String(120), nullable=False, unique= True)
date_created = db.Column(db.DateTime(timezone=True), default= func.now())
posts =db.relationship('Post', backref='user', passive_deletes= True)
likes=db.relationship('Like', backref='user', passive_deletes= True)
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
author = db.Column(db.Integer, db.ForeignKey('user.id', on_delete="CASCADE"),nullable=False)
text= db.Column(db.Text(120), nullable=False)
posted_on = db.Column(db.DateTime(timezone=True), default= func.now())
likes=db.relationship('Like', backref='post', passive_deletes= True)
class Like(db.Model):
id = db.Column(db.Integer, primary_key=True)
posted_on = db.Column(db.DateTime(timezone=True), default= func.now())
author = db.Column(db.Integer, db.ForeignKey('user.id', on_delete="CASCADE"),nullable=False)
post_id= db.Column(db.Integer, db.ForeignKey('post.id', on_delete="CASCADE"),nullable=False)
To show a like on our blog, we are going to need the thumbs-up icon, and this can be gotten from the font awesome library.
In our base.html, at the top of our code, we will add the link that contains all the font awesome icons.
Now in our home.html, right above our edit and delete if statement, we will add a div tag
div class="btn-group">
<a href="/post-like/{{post.id}}">
<i class="fas fa-thumbs-up">{{ post.likes |length}}</i></a>
</div>
{{ post.likes|length}}
was written, to show the number of likes the post has.
When a user clicks the button, we need to be able to view how many likes the post has. And to do this we need to go to display.py to create a function
@display.route("/post-like/<post_id>", methods=['GET'])
@login_required
def like(post_id):
post = Post.query.filter_by(id=post_id)
like =Like.query.filter_by(author=current_user.id, post_id=post_id).first()
if not post:
flash('This post does not exist.', category='error')
elif like:
db.session.delete(like)
db.session.commit()
else:
like =Like(author=current_user.id, post_id=post_id)
db.session.add(like)
db.session.commit()
return redirect(url_for('display.home'))
And now we are done!!
This is what our final blog looks like:
HOME PAGE
HOME PAGE WHEN LOGGED IN
ABOUT PAGE
CONTACT PAGE
Conclusion
This is just the tip of the iceberg to what you can create using Python. This is my personal walkthrouh of creating a blog using the flask appplication.
Hope you found this article helpful. I appreciate every opportunity to learn and share my understandings. Cheers!