Photo by Andrew Neel on Unsplash
Build Your Own Blog Quickly And Easily With Flask: A Step-By-Step Guide
1.0 Introduction
Are you interested in starting your own blog but don’t know where to start? Have you heard about Flask, but don’t know how to use it? This article is here to help! We provide a step-by-step tutorial on how to build a blog using Flask so that you can finally get your blog off the ground. Read on for an easy-to-follow guide on how to dive into the world of web development and create your very own blog!
In this article, we will walk you through the steps of creating a blog with flask. We'll cover setting up the web application framework, configuring a database, and integrating other features like authentication. By the end of this tutorial, you'll have your very own blog running live on the web!
Flask is a microframework written in Python that enables developers to quickly and easily create web applications. It is lightweight and can be used for small-scale projects and mostly it is easier to learn. With its simple yet powerful design flask makes building an accessible task for all developers of different skill sets.
Flask is said to be more pythonic because it does not require particular tools or libraries. It has no database abstraction layer, form validation, or any other components where pre-existing third-party libraries provide common functions.
In this tutorial, we will be concerned with writing the backend code and using a free template for the frontend code.
Concepts learned
Routing
Jinja2template
Responsive website using Bootstrap.
User authentication.
Database with flask SQLaclhemy
Prerequisites
To get the most out of this course you must have an understanding of python 3 or higher and also a little understanding of HTML and CSS you don't need to be an expert because this tutorial will walk you through everything you need to know to build your first blog application.
Before we dive deeper you should have the following installed:
DB Browser for SQLite (DB4S) is a high-quality, visual, open-source tool to create, design, and edit database files compatible with SQLite.
DB4S is for users and developers who want to create, search, and edit databases. DB4S uses a familiar spreadsheet-like interface, and complicated SQL commands do not have to be learned and also helps you to identify if a table was created or not.
Take a deep breath you have come so far!!!
Preparation
Before we begin writing code we need to create our folders and make sure our folders are in the proper workspace environment.
Creating your project folder
Based on personal preference I create my folder using git bash
as follows
$ cd desktop
$ mkdir JezzyBlog
$ cd JezzyBlog
$ code .
code . will open the folder JezzyBlog in vs code
Creating virtual environment
A virtual environment is important here because it helps us to manage all dependencies used in the course of creating this blog. These dependencies may include the different packages installed amongst all others.
Virtual environment is important to python users because it helps to isolate different packages used for the project.
The creation of a virtual environment is done by executing the command venv
: followed by the name of the environment, In this project, we will be using env
as the name of our virtual environment.
On your terminal in vscode type the following command:
> py -m venv env
After running this command, a directory named env
will be created. This is the directory that contains all the necessary executables to use and packages that this Python project would need.
Activating virtual environment
To activate the virtual environment we use the following:
> env\Scripts\Activate
After running this command, your vscode terminal should look like this:
(env) PS C:\Users\Jessica Ovabor\Desktop\JezzyBlog>
Also if it is not working you are probably doing something wrong or has misspelt something take a careful look at it and try again.
NOTE: This method of creating a virtual environment is only applicable to window users, it might differ for both Mac/Linux users. To learn more about virtual environments you can visit full stack python to learn more about the virtual environment.
Flask Installation
To install the flask framework we use the pip module which is a package installer for python that enables us to install different packages into our application.
pip install flask
pip freeze > requirement.txt
Congratulations you have successfully installed your flask application.
Tip: pip freeze >requirement.txt will help document all the packages installed in your application.
where requirement.txt is the name of file that document all the packages installed in the application
you can pip freeze after every installation or pip freeze after all your packages has been installed.
Getting started
We have successfully installed our flask application now we need to be able to use it in our application.
In our project directory that is the JezzyBlog create a new file and name it app.py
and write the following code inside of it
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello'
if __name__=="__main__":
app.run(debug=True)
You first import the
Flask
object from theflask
packageYou then create your Flask application instance with the name
app
and then you pass a special variable__name__
that holds the name of the current Python module. This argument is passed in the Flask class to create its instance, which is then used to run the application.Once you create the
app
instance, you use it to handle incoming web requests and send responses to the user.@app.route
is a decorator that turns a regular Python function into a Flask view function, which converts the function’s return value into an HTTP response to be displayed by an HTTP client, such as a web browser. You pass the value'/'
to@app.route()
to signify that this function will respond to web requests for the URL/
, which is the main URL.The
hello()
view function returns the string'Hello, World!'
as a response.Save and close the file
app.run(debug=True)
is used to run your web application, and in this case, we set debug to True
In your terminal type the following command app.py
to run your web application in a Flask environment
(env) PS C:\Users\Jessica Ovabor\Desktop\JezzyBlog>app.py
Once the application is running the output will be something like this:
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 143-974-277
This code block means that:
The name of the application you’re running.
The environment in which the application is being run.
Debug mode: on
signifies that the Flask debugger is running. This is useful when developing because it gives us detailed error messages when things go wrong, which makes troubleshooting easier and it points directly to where things go wrong.The application is running locally on the URL
http://127.0.0.1:5000/
,127.0.0.1
is the IP that represents your machine’slocalhost
and:5000
is the port number.
Open a browser and type in the URL http://127.0.0.1:5000/
, you will receive the string Hello
as a response, this confirms that your application is successfully running.
Creating HTML files
Our current application only displays a message without any HTML. The next step is to learn how to display and incorporate HTML into our application
Flask provides a render_template()
helper function a templating package used to generate output from a template file based on the Jinja template engine that is found in the application's templates folder.
render_template()
looks for the templates
folder in the application and runs the HTML files inside.
Firstly you create a folder called templates
inside the JezzyBlog folder in our application and this is where all our .html
files will be and will also import the render_template()
helper function that lets you render HTML template files that exist in the templates
the folder created.
To see fully how the render_template()
the function works we create a index.html
file inside our template and input the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>JezzBlog</title>
</head>
<body>
<h1>Welcome to Jezz<span class= "h1-color">B</span>log</h1>
</body>
</html>
Then in your app.py
type the following code and delete the hello()
since it is of no use anymore.
from flask import Flask,render_template
app = Flask(__name__)
@app.route('/')
def index()
render_template("index.html")
if name=="__main__":
app.run(debug=True)
Save the file and use your browser to navigate to http://127.0.0.1:5000/
. This time the browser should display the text Welcome to JezzBlog
in an <h1>
tag.
In addition to the templates
folder, Flask web applications also typically have a static
folder for hosting static files, such as CSS files, JavaScript files, and media such as images the application might use.
You can create a style.css
style sheet file to add CSS to your application. First, create a directory called static
inside your JezzyBlog
directory and Then create another directory called css
inside the static
directory to host .css
files. This is typically done to organize static files in dedicated folders, as such, JavaScript files typically live inside a directory called js
, images are put in a directory called images
(or img
), and so on.
Your directory should be like this:
Then open a style.css
file inside the css
directory and add the following CSS rule to the file
h1 {
text-align: center;
padding: 10px;
}
.h1-color{
color: red;
}
The CSS code will centre the text, and add a little padding of 10px to the <h1>
tags.
.h1-color
is a class property in CSS it will change the colour of B
part of the blog to red.
Save and close the file.
Next, open the index.html
template file
You’ll add a link to the style.css
file inside the <head>
section of the index.html
template file to link the CSS file to the HTML file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>JezzBlog</title>
<link rel="stylesheet" href="{{ url_for('static', filename= 'css/style.css') }}">
</head>
<body>
<h1>Welcome to Jezz<span class= "h1-color">B</span>log</h1>
</body>
</html>
Here we use the url_for()
helper function to generate the location of the file. The first argument specifies that the file is static and the second argument is the path of the file inside the static directory.
Save and close the file.
Upon refreshing the index page of your application, you will notice that the text Welcome to JezzBlog
is now in black with the B in red, centred.
You can use the CSS language to style the application and make it more appealing suiting your taste. However, we will be using Bootstrap which provides easy-to-use components for styling your application.
Next to work with the DRY(do not repeat yourself) we are going to create a base.html
file which is a base template file, where all other .html
files inherits from. You can also avoid unnecessary code repetition. See Template Inheritance in Jinja for more information.
To make a base template, first create a file called base.html
inside your templates
directory:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<title> {%block title %} blog Home{% endblock %}</title>
</head>
<body>
<!-- Responsive navbar-->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#!">Jezz<span class= "h1-color">B</span>log</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link href="#top">Home</a></li>
<li class="nav-item"><a class="nav-link" href="/blog">Blog</a></li>
<li class="nav-item"><a class="nav-link" href="/create">Create</a></li>
<li class="nav-item"><a class="nav-link" href="/logout">Logout</a></li>
<li class="nav-item"><a class="nav-link" href="/about">About</a></li>
<li class="nav-item"><a class="nav-link active" aria-current="page" id= "nav-item2"href="/contact">Contact</a></li>
</ul>
</div>
</div>
</nav>
<!-- Page header with logo and tagline-->
<header class="py-5 bg-light border-bottom mb-4">
<div class="container">
<div class="text-center my-5">
<h1 class="fw-bolder">Welcome to Jezz<span class= "h1-color">B</span>log</h1>
<p class="lead mb-0">Home of quality information, latest tech news, fashion and definitely gossips.</p>
</div>
</div>
</header>
<!-- Footer-->
<footer class="py-5 bg-dark">
<div class="container"><p class="m-0 text-center text-white">Copyright © JezzBlog 2022</p></div>
</footer>
<div class="other-container">
{% block content %} {% endblock %}
</div>
<!-- Bootstrap core JS-->
<script src="https://cd
</body>n.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script src="{{ url_for('static', filename='index.js') }}"> </script>
</html>
Save and close the file once you’re done editing it.
Most of the code is standard HTML and code required for Bootstrap. The <meta>
tags provide information for the web browser, the <link>
tag links the Bootstrap CSS files, and the <script>
tags are links to JavaScript code that allows some additional Bootstrap features, check out the Bootstrap documentation for more.
{% block title %} {% endblock %}
: A block that serves as a placeholder for a title, you’ll later use it in other templates to give a custom title for each page in your application without rewriting the entire<head>
section each time.{% block content %} {% endblock %}
: Another block that will be replaced by content depending on the child template (templates that inherit frombase.html
) that will override it.
Now that you have a base template, you open the index.html
file and inherit from the base.html
as follows:
{% extends 'base.html' %}
{% block content %}
<h1>{% block title %} Welcome to JezzBlog {% endblock %}</h1>
<!--responsive navbar after a user logout-->
<li class="nav-item"><a class="nav-link" href="#">Home</a></li>
<li class="nav-item"><a class="nav-link" href="/signup">Signup</a></li>
<li class="nav-item"><a class="nav-link" href="/login">Login</a></li>
<li class="nav-item"><a class="nav-link active" aria-current="page" id= "nav-item2"href="/contact">Contact</a></li>
{% endblock %}
In this new version of the index.html
template, we use the {% extends %}
tag to inherit from the base.html
template. You then extend it by replacing the content
block in the base template with what is inside the content
block in the code block.
This content
block contains an <h1>
tag with the text Welcome to JezzBlog
inside a title
block, which in turn replaces the original title
block in the base.html
template with the text Welcome to JezzyBlog
. This way, you can avoid repeating the same text twice, as it works both as a title for the page and a heading that appears below the navigation bar inherited from the base template.
Template inheritance also gives you the ability to reuse the HTML code you have in other templates (base.html
in this case) without having to repeat it each time it is needed.
Save and close the file and refresh the index page on your browser. You’ll see your page with a navigation bar and a styled title.
Setting up the Database
In this step, you’ll set up a database to store data: the blog posts for your application, logout user, signup user, and sign-in user.
You’ll use a flask SQLAlchemy file to store your data and use it to interact with the database, which is readily available in the standard Python library.
There are a lot of database engine such as PostgreSQL or MySQL, you’ll need to use the proper URI as they may differ for the sake of this tutorial we wll be using the flask sqlalchemy.
Open your terminal and install flask sqlalchemy
pip install Flask-SQLAlchemy
pip freeze > requirement.txt
Open your app.py
inside your JezzyBlog
directory:
from flask import Flask,render_template
from flask_sqlalchemy import SQLAlchemy
import os
base_dir = os.path.dirname(os.path.realpath(__file__))
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + os.path.join(base_dir, 'blog.db')
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = '78f81b3cb11851782d6ead66'
db = SQLAlchemy(app)
@app.route('/')
def index()
render_template("index.html")
if name=="__main__":
app.run(debug=True)
Here we did the following:
You first import the
SQLAlchemy
module and then open a connection to a database file namedblog.db
, which will be created once you run the Python file.you import the
os
module, which gives you access to miscellaneous operating system interfacesTo get a 12-digit (any number of choice) secret key, run this in the terminal: python import secrets.token_hex(12) exit() Copy the token from the terminal and paste it as the secret key in app. config above
db = SQLAlchemy(app)
makes an instance of the app in the database.SQLALCHEMY_DATABASE_URI
: The database URI to specify the database you want to establish a connection with. In this case, the URI follows the formatsqlite:///path/to/database.db
. You use theos.path.join()
function to intelligently join the base directory you constructed and stored in thebasedir
variable, and thedatabase.db
file name. This will connect to adatabase.db
database file in yourflask_app
directory. The file will be created once you initiate the database.SQLALCHEMY_TRACK_MODIFICATIONS
: A configuration to enable or disable tracking modifications of objects. You set it toFalse
to disable tracking and use less memory. For more, see the configuration page in the Flask-SQLAlchemy documentation.
To learn more about Flask sqlalchemy you can visit this tutorial
- Creating tables
With the database connection established and the database object created, you’ll use the database object to create a database table for User
, Post
, Message
which is represented by a model.
To define aUser
table as a model, we first create a table of users using the UserMixin
which represents each post done by a particular user
To use the UserMixin
will have to install the Flask-login
. Go to your and type the following:
pip install Flask-Login
Then import the UserMixin
from Flask-Login
. add the following code in your app.py
file:
from flask_login import UserMixin
Now as guessed we can now build the User model
#every signed in user must have the following details
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer(), primary_key=True)
firstname = db.Column(db.String(255), nullable=False, unique=True)
lastname = db.Column(db.String(255), nullable=False, unique=True)
username = db.Column(db.String(255), nullable=False, unique=True)
email = db.Column(db.String(255), nullable=False, unique=True)
password_hash = db.Column(db.String(10), nullable=False)
def __repr__(self):
return f"User <{self.username}>"
id
: Theuser
ID. You define it as an integer withdb.Integer
.primary_key=True
defines this column as a primary key, which will assign it a unique value by the database for each entry (that is a user).firstname
: The user's first name. A string with a maximum length of255
characters.nullable=False
signifies that this column should not be empty.unique=True
signifies each firstname should be unique.lastname
: The user's last name. A string with a maximum length of255
characters.nullable=False
signifies that this column should not be empty.unique=True
signifies each lastname should be unique.username
: The user name. A string with a maximum length of255
characters.nullable=False
signifies that this column should not be empty.unique=True
signifies each lastname should be unique.email
: The user email. A string with a maximum length of255
characters.unique=True
signifies that each email should be unique for each student.nullable=False
signifies that this column should not be empty.password_hash
: The user password. A string with a maximum length of10
characters.unique=True
signifies that each email should be unique for each student.
See the SQLAlchemy documentation for more information
NOTE: The
UserMixin
returns as the username as its represation for the user table
Post Model
The Post model create a table that consists of post created by a user and the user or author who created it. The UserMixin
returns as the title of the post as its representation for the post table.
Before we create the model we need to get the date and time each post was created to do that we import an inbuilt module that comes with python called the datetime
from datetime import datetime
The post table is given below:
class Post(db.Model):
__tablename__ = 'posts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False, unique=True)
slug = db.Column(db.String(100), nullable=False, unique=True)
content = db.Column(db.Text, nullable=False)
posted_by = db.Column(db.String(20), nullable=False, default='N/A')
posted_on = db.Column(db.DateTime, nullable=False,
default=datetime.utcnow())
user_id=db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return f"Posts:<self.title>"#initilising mydatabase
Message model
The message model indicates the message sent from our site visitor. You can also view it as a way of keeping track of every visitor that fills that contact. It returns text as its representation.
The message table is given below:
class Message(db.Model):
__tablename__ = 'message'
id = db.Column(db.Integer(), primary_key=True)
usernames= db.Column(db.String(255), nullable=False)
email = db.Column(db.String(255), nullable=False)
needs= db.Column(db.Text(255), nullable=False)
text = db.Column(db.String(255))
def __repr__(self):
return f"Message <{self.text}>"
A quick tip: your tables can have more fields depending on what you want.
- Database Initialisation
@app.before_first_request
def create_tables():
db.create_all()
The above code ensures that a database file with our given name is created for the first time and only if the file does not exist will it be created.
when we run
the terminal a blog.db file will appear in our project directory.
After we run we can then use DB browser for SQlite
what we previously installed to check what is happening in our database as vscode
doesn't show us what is happening in our database.
Routing
Routing means mapping the URLs to a specific function that will handle the logic for that URL. Routing makes navigation easier.
Routing is done using the @app.route()
function in which a desired URL is passed as an argument.
Home page
for our home page, we will be using the route we created earlier to represent a static page in which a user is yet to sign in
@app.route('/')
def index()
render_template("index.html")
About page
This page tells us about the developer and what the whole application is all about
@app.route('/')
def about()
render_template("about.html")
Contact page
The contact page allows us to receive constructive feedback from users, on how the site can be improved or if they have any questions.
Before we can write the code for our contact page
we need to import the following into our app.py:
from flask import Flask,render_template,url_for,redirect,flash,request
Here is a brief explanation of the above code
redirect
as the name applies helps us to easily navigate back to a certain page after an action has been applied.request
indicate we are performing an HTTP request.flash
indicate that a message will be displayed after an action has been completed.The code for the
contact p
age.@app.route("/contact", methods=['GET', 'POST']) def contact(): if request.method == 'POST': user_name = request.form.get('username') user_email = request.form.get('email') user_need = request.form.get('need') user_message = request.form.get('message') new_contact_message = Message(email=user_email, usernames=user_name,needs=user_need,text=user_message) db.session.add(new_contact_message) db.session.commit() flash("Your message has been succesfully sent") return redirect('index') else: return render_template('contact.html')
This indicates it collects contact information from our
contact.html
and then stores the information collected to yourmessage
table in our database it flashes a message usingflash()
to display the message that was passed to it and after that, it redirects the user to the home page.GET
the method gets data from the database to display it to users and it is a default argument.POST
the method allows the user to make a request to the database.request.form.get()
: The parameter passed into it is an argument from thename
value in the HTML
To learn more about the HTTP
method visit Mozilla docs.
NOTE: To view these messages we make use of our DB Browser.
Frontend code for each page
This section of the article gives a detailed explanation of how we use templates
and static
and how we can easily manipulate them.
Recall from earlier that we have created our base.html
file from which other .html
pages can inherit. In this section, we will be creating the about.html
, create.html,edit.html
,blog.html
,signin.html
,signup.html
.
- About.html: it gives us information about the developer and what the site is all about.
create a new html
file about.html
inside the templates
folder and input the following code:
{% extends 'base.html' %}
{% block content %}
{%block head%}
<title>Contact</title>
{%endblock%}
{%block body%}
<div class="container">
<div class=" text-center mt-5 ">
<h1 >Contact Us</h1>
</div>
<div class="row ">
<div class="col-lg-7 mx-auto">
<div class="card mt-2 mx-auto p-4 bg-light">
<div class="card-body bg-light">
<!--form-->
<div class = "container">
<form action="/contact" method="POST">
<div class="controls">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="form_name">Firstname *</label>
<input id="form_name" type="text" name="fname" class="form-control" placeholder="Please enter your firstname *" required="required" data-error="Firstname is required.">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="form_lastname">Lastname *</label>
<input id="form_lastname" type="text" name="lname" class="form-control" placeholder="Please enter your lastname *" required="required" data-error="Lastname is required.">
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
<label for="form_email">Email *</label>
<input id="form_email" type="email" name="email" class="form-control" placeholder="Please enter your email *" required="required" data-error="Valid email is required.">
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<label for="form_need">Please specify your need *</label>
<select id="form_need" name="need" class="form-control" required="required" data-error="Please specify your need.">
<option value="" selected disabled>--Select Your Issue--</option>
<option >Unable to create new post</option>
<option >Improved features</option>
<optio>Others</option>
<option >Other</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<label for="form_message">Message *</label>
<textarea id="form_message" name="message" class="form-control" placeholder="Write your message here." rows="4" required="required" data-error="Please, leave us a message."></textarea
>
</div>
</div>
<div class="col-md-12">
<input type="submit" class="btn btn-success btn-send pt-2 btn-block
mt-3 " value="Send Message" >
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- /.8 -->
</div>
<!-- /.row-->
</div>
</div>
{%endblock%}
- Create.html: The
create.html
file allows the user to create a blog post.
create a new html
file create.html
inside the templates
folder and input the following code:
{% extends 'base.html' %}
{% block content %}
{% block head %}
<title>New Post</title>
{% endblock%}
{% block body %}
<h1>New Post</h1>
<hr>
<form action= "/create"method="POST">
<label for="title">Title:</label>
<input class="form-control" type="text" name="title" id="title" placeholder="Enter Title" aria-label="Enter Title">
<br>
<label for="slug">Slug:</label>
<input class="form-control" type="text" name="slug" id="author" placeholder="Enter Subtitle"
aria-label=" Enter Author">
<br>
<label for="author">Author:</label>
<input class="form-control" type="text" name="author" id="slug" placeholder="Enter Author"
aria-label=" Enter Author">
<br>
<label for="content">Content</label>
<textarea class="form-control" id="content" name="content" placeholder="Enter Content" aria-label=" Enter Content"
rows="3"></textarea>
<br>
<input type="submit" class="btn btn-success" value="Post">
</form>
<hr>
{% endblock%}
- Edit.html: The
edit.html
file allows the user to edit a blog post.
create a new html
file edit.html
inside the templates
folder and input the following code:
{% extends 'base.html' %}
{% block content %}
<h1>Edit Post </h1>
<hr>
<form action="/edit/{{blog.id}}" method="POST">
<label for="content">Title:</label>
<input class="form-control" type="text" name="title" id="title" value="{{blog.title}}">
<br>
<label for="content">Slug:</label>
<input class="form-control" type="text" name="slug" id="slug" value="{{blog.slug}}">
<br>
<label for="post">Content:</label>
<textarea class="form-control" id="post" name="content" rows="3">{{blog.content}}</textarea>
<br>
<input type="submit" class ="btn btn-success" value="Save">
</form>
<hr>
{%endblock%}
- blog.html: The
blog.html
allows the user to see a list of blog posts created by the user and all other users and also proving them with options to delete and edit a blog post.
create a new html
file blog.html
inside the templates
folder and input the following code:
{% extends 'base.html' %}
{% block content %}
{% block body %}
<div>
<h1 style="display: inline">Create New Posts:</h1>
<a style="display: inline" class="btn btn-success float-right " href="/create">+ New Post</a>
</div>
<br>
<hr>
<!--displaying blog post in blog.html-->
{% for blogs in blog%}
<div class="card mb-4 w-75">
<div class="card-body">
<h2>{{blogs.title}}</h2>
<h3 style="color:gray;">{{blogs.slug}}</h3>
{% if blogs.author %}
<small>Written By {{blogs.posted_by}} on {{blogs.posted_on}}</small>
{% else%}
<small>Authored by {{blogs.posted_by}} on {{blogs.posted_on}}
</small>
{% endif %}
<p style="white-space: pre-wrap">{{blogs.content}}
</p>
</p>
<!--edit a blog post-->
<a class="btn btn-success btn-send pb-2 pt-2 btn-block
mt-3" href="/edit/{{blogs.id}}">Edit →</a>
<!--delete a blog post-->
<a class="btn btn-danger btn-send pb-2 pt-2 btn-block
mt-3" href="/delete/{{blogs.id}}">Delete →</a>
<br>
</div>
</div>
{% endfor %}
{%endblock%}
- signup.html: The
signup.html
allows users to sign up to our application.
create a new html
file signup.html
inside the templates
folder and input the following code:
{% extends 'base.html' %}
{% block content %}
{% block body %}
<div class="container">
<div class="row">
<div class="col-lg-3 col-md-2"></div>
<div class="col-lg-6 col-md-8 login-box">
<div class="col-lg-12 login-key">
<i class="fa fa-key" aria-hidden="true"></i>
</div>
<div class="col-lg-12 login-title">
<p> New user welcome to Jezz<span class= "h1-color">B</span>log</p>
</div>
<div class="col-lg-12 login-form">
<div class="col-lg-12 login-form">
<form "{{url_for('signup')}}" method = "POST">
<div class="form-group">
<label class="form-control-label">FIRSTNAME</label>
<input type="text" class="form-control" name='first_name'>
</div>
<div class="form-group">
<label class="form-control-label">LASTNAME</label>
<input type="text" class="form-control" name='last_name' i>
</div>
<div class="form-group">
<label class="form-control-label">USERNAME</label>
<input type="text" class="form-control" name='username'>
</div>
<div class="form-group">
<label class="form-control-label">EMAIL</label>
<input type="text" class="form-control" name='email' i>
</div>
<div class="form-group">
<label class="form-control-label">PASSWORD</label>
<input type="password" class="form-control" name='password'>
</div>
<div class="form-group">
<label class="form-control-label">CONFIRM PASSWORD</label>
<input type="password" class="form-control" name='confirm_password' i>
</div>
<div class="col-lg-12 loginbttm">
<div class="col-lg-6 login-btm login-text">
<!-- Error Message -->
</div>
<div class="col-lg-6 login-btm login-button">
<button type="submit" class="btn btn-outline-primary">signup</button>
</div>
<p> Already have an account? <a class="a-link" href="{{url_for('login')}}">Login</a> </p>
</div>
</form>
</div>
</div>
<div class="col-lg-3 col-md-2"></div>
</div>
</div>
{%endblock%}
- signin.html: The
signin.html
allows users to sign in to our application.
create a new html
file signin.html
inside the templates
folder and input the following code:
{% extends 'base.html' %}
{% block content %}
{% block body %}
<div class="col-lg-12 login-title">
<p> Welcome back to Jezz<span class= "h1-color">B</span>log</p>
</div>
<div class="col-lg-12 login-form">
<div class="col-lg-12 login-form">
<form method = "POST">
<div class="form-group">
<label class="form-control-label">USERNAME</label>
<input type="text" class="form-control" name=username>
</div>
<div class="form-group">
<label class="form-control-label">PASSWORD</label>
<input type="password" class="form-control" name=password i>
</div>
<div class="col-lg-12 loginbttm">
<div class="col-lg-6 login-btm login-text">
<!-- Error Message -->
</div>
<div class="col-lg-6 login-btm login-button">
<button type="submit" class="btn btn-outline-primary">LOGIN</button>
</div>
</div>
</div>
<p> Dont have an account? <a class="a-link" href="{{url_for('signup')}}">Signup</a> </p>
</div>
</form>
</div>
</div>
<div class="col-lg-3 col-md-2"></div>
</div>
{%endblock%}
Note: WE DO NOT NEED a new html
file delete.html
and signout.html
inside the templates
folder because there is nothing to display when a user deletes or signout from our application. whenever a user deletes or a signout from our page we will redirect them to the blog
and home
page respectively.
User Activity and code for each page
In this section of the article, we are going to write the code block for each page and import different modules to help with our application and also show the different activity that can be performed by each user on our application.
Before we do that we need to import this number of modules in addition to the ones we have installed before.
open your app.py
and input the following:
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import login_user, logout_user, login_required, LoginManager
After this import, your app.py
the file should look like this in the beginning:
from flask import Flask,render_template,url_for,redirect,flash,request
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import login_user, logout_user, login_required, LoginManager, UserMixin
from datetime import datetime
Here is a brief explanation of the above code block:
werkzeug.security
is an inbuilt module that comes with Flask. It provides a library for hashing passwords giving us the opportunity to store users' password hashed rather than as it was given using the generate_password_hash() and then use then check_password_hash_()
to decode the generated password whenever a login is attempted. This help to improve the security
- User Registration
User registration means creating a new user account, which is a record in the database describing how you will prove your identity.
Before you can even create a post you must have an account that clearly indicates your identity so you can use the full functionality.
- User signup()
open your app.py
and input the following code :
# new user signup
@app.route("/signup" ,methods=['GET', 'POST'])
def signup():
if request.method == 'POST':
first_name=request.form.get('first_name')
last_name= request.form.get('last_name')
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
confirm_password1 = request.form.get('confirm_password')
username_exists = User.query.filter_by(username=username).first()
if username_exists:
return redirect(url_for('signup'))
email_exists = User.query.filter_by(email=email).first()
if email_exists:
return redirect(url_for('signup'))
password_hash = generate_password_hash(password)
new_user = User(username=username, email=email,lastname=last_name,firstname=first_name,password_hash=password_hash)
db.session.add(new_user)
db.session.commit()
return redirect(url_for('blog'))
return render_template('signup.html')
Here we created asignup()
the function which collects user data from the signup.html
templates, if the username_exists and email_exists already, it will reload the signup page for the user to make another attempt of the username and email otherwise it will add the new users account to the "user table" with a password configured as hashed using the User model we created earlier and then redirect them back to the blog.html
page.
request.form.get()
takes an argument from the value passed to the name variable
in our HTML form
db.session.add()
: add new user to our database anytime an account is created.
db.session.commit()
: commits the current action.
- User Authentication:
It verifies the identity of a user attempting to gain access to our blog(signin) and deactivates a user's current session when it is done(sign out)
User signin()
The
signin()
function collects user data from thesignin.html
form template. it checks if ausername_exists
andemail_exists
if yes it will reload thesignin.html
page if otherwise, it redirects the user to the loginFor us to properly make use of the
Flask-Login
we need to pass our app as an instance of theLoginManager
. This tells whichFlask-Login
file it is currently listening to.
login_manager = LoginManager(app)
#login manager loader
@login_manager.user_loader
def user_loader(id):
return Users.query.get(int(id))
@login_manager.user_loader
indicate that protected routes will only be rendered to user who are logged in.
The code is given below:
#signin
@app.route("/login" ,methods=['GET', 'POST'])
def signin():
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password_hash, password):
login_user(user)
return redirect(url_for('blog'))
return render_template('signin.html')
Here the signin()
function checks the user details as entered in the signin.html
form template with the record(username and password) in the user table if it matches, if yes, then it redirects the user to the blog.html
page otherwise it prompts the user to re-enter the correct details.
User signout()
Flask-Login
module provides us with an inbuiltlogout_user()
function, which helps us deny users access to certain functionality when they click the logout button.This logout does not require an
.html
file because we have nothing to display whenever a user logout we should redirect them to theindex.html
page and no route is requiredThe code is given below:
#logout
@app.route('/logout')
def signout():
logout_user()
return redirect(url_for('index'))
- User's Operation
In this section we will give a detailed explaination of what can kind of operation a user will be able to perform.This the CRUD operation which signifies a user will be able to create, read,update and delete a post.
Creating a post
After a user logs in they should be able to create a new post .
#create a blog post
@app.route("/create",methods=['GET', 'POST'])
def create():
if request.method == 'POST':
post_title = request.form.get('title')
post_slug= request.form.get('slug')
post_content = request.form.get('content')
post_author = request.form.get('author')
new_post = Post(title=post_title,
content=post_content, slug = post_slug,posted_by=post_author)
db.session.add(new_post)
db.session.commit()
return redirect(url_for('blog'))
else:
all_posts = Post.query.order_by(Post.posted_on).all()
return render_template('create.html', blog=all_posts)
Editing/Updating a post
Whenever a user creates a post they must be able to make changes to the post.
#edit a blog post from the database
@app.route('/edit/<int:id>', methods=['GET', 'POST'])
def edit(id):
post_to_edit = Post.query.get_or_404(id)
if current_user.username =post_to_edit.post_author
if request.method == 'POST':
post_to_edit.title = request.form['title']
post_to_edit.slug = request.form['slug']
post_to_edit.content = request.form['content']
db.session.commit()
return redirect(url_for('blog'))
else:
return render_template('edit.html', blog=to_edit)
Here edit(id)
function takes in a parameter id which compares the post_author from the database and checks if it matches with the logged in username in the database if yes, the user is allowed to make an edit otherwise he will not be able.
Deleting a post
#delete a blog post from the database @app.route('/delete/<int:id>') def delete(id): post_to_delete = Post.query.get_or_404(id) if current_user.username == to_delete.post_author: db.session.delete(post_to_delete) db.session.commit() return redirect(url_for('blog'))
Here
delete(id)
function takes in a parameter id which compares the post_author from the database and checks if it matches with the logged in username in the database if yes, the user is allowed to make an delete after which will be redirected to theblog.html
blog page otherwise he will not be able.Rendering/Displaying the post
This shows that whenever a user create or edit a new post it will be rendered in the
blog.html
@app.route('/blog') def blog(): if request.method == 'POST': post_title = request.form['title'] post_content = request.form['post'] post_author = request.form['author'] post_slug = request.form['slug'] new_post = JezzyBlog(title=post_title, content=post_content,slug=post_slug, posted_by=post_author) db.session.add(new_post) db.session.commit() return redirect('blog') else: all_posts = Post.query.order_by(Post.posted_on).all() return render_template('blog.html', blog=all_posts)
Here we created a skeleton which matches with the different field an author or user has to field to be able to create or edit a post with this skeleton the user will be able to render all post created from
create.html
and edit.html in the blog.html