Photo by Glenn Carstens-Peters on Unsplash
Create a beginner-friendly blog website using Flask, a Python framework.
Introduction
Hey guys, welcome to my space🥰. Are you ready to dive into the world of web development and learn how to build your own blog? Look no further! In this tutorial, we will be using Flask, a powerful and flexible Python framework, to create a fully functional and customizable blog application.
Our Flask app offers easy user authentication allowing you to sign up, log in, and publish articles at any time. Our app also gives users the flexibility to make changes to their articles, so they can keep their content fresh and up-to-date.
Let's dive deeper and learn more about the framework we've selected for our app.
What makes Flask a suitable choice for web development?
Flask is a Python-based microframework used for web application development. It is considered a microframework due to its lightweight, independent, integrated unit testing, and flexible and scalable features.
In this tutorial, we will learn how to build a Flask application by implementing the following features:
Jinja2 Templates
Flash Messages
Routing
Use of Werkzeug to hash our passwords
Perform CRUD Operation
User Authentication
Prerequisites
This tutorial is designed for those who already have a basic understanding of front-end development technologies such as HTML and CSS. We also recommend that you have some familiarity with Python 3 before beginning. If you are new to web development, it may be helpful to take some introductory courses to build your skills.
Before we begin, you need to set up the following:
A code editor. We will use Visual Studio Code (VS Code) for this project.
Python 3.
Setup
To begin, create a new folder to hold your application files. This will help you keep your project organized and allow you to easily track any changes made. Then, open your terminal and enter the following commands:
$ mkdir my_blog
$ cd my_blog
Note: You can name the folder whatever you like. If you need a refresher on terminal commands, you can find more information here.
Create a Virtual Environment (venv)
A virtual environment, venv, is a tool used to isolate specific Python environments on a single machine, allowing you to work on multiple projects without running into conflicts caused by conflicting package versions. It's a good idea to create a separate venv for each of your Python projects.
Now, let's set up our virtual environment.
For Windows OS users,
To create a virtual environment, you can run the following command:
$ py -3 -m venv myvenv
Note: myvenv
is the name of your virtual environment. You can choose to give it any name you want.
- To activate your venv, run the following command:
$ myvenv/scripts/activate
For Mac OS users:
To create a virtual environment, you can run the following command:
$ python3 -m venv env
To activate your virtual environment, run the following command:
$ source env/bin/activate
Note: If the activation is successful, the terminal will display the name of the activated virtual environment. This is how the terminal will look when the virtual environment is activated.
**(myvenv)** PS C:\\Users\\user\\Documents\\CSS\\my_blog>
The next step is to install the following dependencies:
To install, run “pip install” followed by the name of the dependency.
Flask==2.2.2
Flask-Login==0.6.2
Flask-SQLAlchemy==3.0.2
For example, to install Flask, we can run the following command:
$ pip install flask
Remember to always activate your virtual environment before installing any packages to keep them isolated from the system-level packages and prevent conflicts.
After we finish installing these packages, we will create a requirements.txt file and execute the following command.”
pip freeze > requirements.txt
This updates our requirements.txt
file with the installed packages.
To view installed packages, run this on your terminal:
pip freeze
To learn more about pip and dependencies, you can refer to the documentation for pip
"We will now create our initial flask application
Create Flask app
To create a Flask app, create a file called app.py
. This file will import all of the necessary modules and folders for your project. All of your configurations for the app will be done in this file. Your Flask app will be housed in this file.
To create a Flask app, follow these steps:
from flask import Flask
def create_app():
app = Flask(__name__)
return app
Now that we have successfully created our first Flask app, let's move on to creating our database
Interacting with database
A database is a structured collection of data that can be easily accessed and modified. It is often organized in the form of tables with rows and columns. In order to create our blog application, we will be using SQLAlchemy's Object-Relational Mapper (ORM). SQLAlchemy is a Python library that offers a convenient way to communicate with various databases, including PostgreSQL, MySQL, and SQLite.
Define a database model
Install SQLAlchemy
To install SQLAlchemy, run this line of code on your terminal:
$ pip install Flask-SQLAlchemy
Create a db.py
To improve organization and management, we can divide our code into separate files. In this case, we will create a file called db.py
to handle the database. After installing the SQLAlchemy package, we can assign it to a variable called db
in the db.py
file. This will
allow us to use SQLAlchemy within the db.py
file.
We will create a file called db.py
and write the following code in it.
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
Incorporating Flask-Login for authentication
Flask-Login is a Flask extension that provides user session management, which is useful for authenticating users and keeping track of their login status. It does this by adding a UserMixin
class to your application, which you can use as a base class for your user model.
UserMixin
is a mixin class that provides default implementations for several methods that are required by Flask-Login. These methods include:
is_authenticated
: ReturnsTrue
if the user has been authenticated,False
otherwise.is_active
: ReturnsTrue
if the user's account is active,False
otherwise.is_anonymous
: ReturnsTrue
if the user is an anonymous user,False
otherwise.get_id
: Returns a unique identifier for the user.
By including UserMixin
as a base class in your user model, you can easily add Flask-Login's user session management functionality to your application.
To install Flask_Login, run the following command in your terminal:
pip install flask_login
Creating our Models
Using SQLAlchemy's ORM (Object-Relational Mapper), we will define a class for each table in our database, specifying the fields and data types for each class.
In order to store data in our database, we will need to create a model for each type of data we want to store. For example, we might have a user model and an article model.
It can include details about the types of data that are stored, such as a username, first name, and email address.
Next, create a directory called models
and add an __init__.py
file to it. This will allow Python to treat the directory as a package, which will make it easier for you to import the files into different modules. Then, create the following files in the models
directory:
user.py
article.py
views.py
User Model
Next, define a class called UserModel
to represent a user in the database. This model should have the following fields: id, username, firstname, lastname, email, and password_hash
from time import timezone
from datetime import datetime
from sqlalchemy import true
from sqlalchemy.sql import func
from flask_login import UserMixin
class UserModel(db.Model, UserMixin):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
username= db.Column(db.String(80))
firstname = db.Column(db.Text(80), nullable=False, unique=False)
lastname = db.Column(db.Text(80), nullable=False, unique=False)
email = db.Column(db.String(80), nullable=False, unique=True)
password_hash = db.Column(db.Text, nullable=False)
Above, is an example of how you might define a user model using Flask-Login's UserMixin
class.
The
id
is an identifier. When theprimary_key
attribute is set toTrue
, It becomes specific to that particular article and cannot be used for another.For the username,
firstname
andlastname
. When thenullable
attribute is set toFalse
, It means they can not be submitted empty.When an instance of an object uses the
unique
attribute, it is either set toFalse
orTrue
. Ifunique
is set toTrue
, then no two users can have the same username, email or password on the platform.For the
password_hash
, we will be creating a function that hashes our passwords into long Immemorable strings for security purposes.
Article Model
When defining these classes in each file, do well to retain the necessary modules that we installed to avoid running into errors.
In the articles.py
file we created, we will have:
class ArticleModel(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer(), primary_key=True)
title = db.Column(db.String(550))
bodytext = db.Column(db.Text(3000), nullable=False, unique=False)
author = db.Column(db.Integer, db.ForeignKey('user.id', ondelete="CASCADE"),
nullable=False)
date_created = db.Column(db.DateTime(timezone=True), default=datetime.utcnow())
SQLAlchemy creates a table, ArticleModel
with 4 columns
The
id
is an identifier. When theprimary_key
attribute is set toTrue
, It becomes specific to that particular article and cannot be used for another.The
title
andbodytext
are contents of our form. When thenullable
attribute is set toFalse
, It means they can not be submitted empty.The “
ForeignKey
” attribute in theauthor
Column is used to inherit data from two related/different tables.For the
date_created
, we import the current date/time.
Connect to the database using SQLModel
So back to our app.py
, let’s:
Create a path for our application
Configure the SQLAlchemy
Initialize our app
Import our models,
UserModel
,ArticleModel
To access the DB we initialized in our app.py
file, we simply need to import db by writing:
from db import db
- Create a path
To set up our app, the first thing we need to do is create a path for our SQLite database. We can do this by importing the os
module and assigning the directory path to a variable called base_dir
.
Here is the code to accomplish this:
import os
base_dir = os.path.dirname(os.path.realpath(__file__))
- Configuration
We can now set up the URI for SQLAlchemy, create a secret key, and initialize our application using these values
app.config["SQLALCHEMY_DATABASE_URI"] = 'sqlite:///' + \\
os.path.join(base_dir, 'blog.db')
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.config["SECRET_KEY"] = 'iLhqyLO3HtSpsE8cuQaj'
db.init_app(app)
- Initialize app
db.init_app(app)
Blueprints
In this step, we will create routes for our application which will allow us to access different pages by mapping specific URLs to functions that perform certain tasks. This is an exciting part of the process because we get to see and interact with what we are building. For example, we can create a route for the home page of our app by associating the URL "/" with a function. Before we do that, let's create a blueprint for our app.
We’ll look at:
Flask Blueprints
Define Blueprints
Register Blueprints
Jinja Templates
Flash Messages
Create a Flask Blueprint for our routes.
In order to organize our code and make it easier to maintain, we will use Flask Blueprints to divide our code into separate files. This is especially useful for larger applications.
First, create a folder called 'resources'. Inside the resources folder, create a init.py
file. This will allow Python to treat the folder as a package, making it easier to import the files into other parts of the application.
Next, create the following files within the resources folder:
user.py
: This file will handle user authentication.**article.py**
: This file will contain functions related to our articles, such as functions for commenting and publishing articles.views.py:
This file will handle most of our routes and redirections to different pages."
Defining our blueprint:
In each file, define your blueprint by setting it to a variable name of your choice. Do this for each script file. As shown in the code below:
article = Blueprint("article", __name__)
view = Blueprint("view", __name__)
user = Blueprint("user", __name__)
The “view
” is an endpoint parameter for the url_for
function. It's used to specify which view function to call when a client navigates to a certain URL.
The __name__
parameter is a special variable in Python that represents the name of the current module. It's often used in the context of Flask to specify the location of a Blueprint, which is a way to organize a group of related views and other code in a Flask application. When you initialize a Flask app and create a Blueprint, you can specify __name__
as the first argument to the Blueprint constructor to tell Flask the location of the Blueprint, relative to the root path of the application.
Register the defined Blueprints
This is done within our app.py file by first importing the blueprint.
from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
from models import ArticleModel, UserModel
from db import db
Then register the blueprint in our app.py file
app.register_blueprint(ViewBlueprint)
app.register_blueprint(ArticleBlueprint)
app.register_blueprint(UserBlueprint)
return app
if __name__=="__main__":
app = create_app()
app.run(debug=True)
Routing
Jinja Templating
Jinja is a powerful template engine for Python that is often used in web development. One of the key features of Jinja is its support for custom tags, filters, tests, and globals. These features allow developers to define custom logic and functionality that can be used within Jinja templates to control the rendering of the output.
Simply put, Jinja is a flask package that lets us write python code in an HTML file/environment.
This topic on Jinja templates is quite comprehensive and can be quite in-depth. Therefore, we are unable to cover all of the details in this tutorial. If you want to learn more about Jinja templates, we recommend exploring additional resources on the subject, such as this jinja template and jinja documentation.
Create a directory named templates, then create the following files:
base.html
index.html
publish.html
register.html
login.html
Base template
A base template in Jinja is a template that defines the overall structure of a page and provides a place for other templates to "inherit" from and override certain blocks of content.
It typically includes elements that are common to all pages of the site, such as the header, navigation, and footer. This allows developers to create a consistent look and feel for the site, while also making it easier to update the layout or design in one place rather than having to make changes to every individual page.
I recommend starting with Jinja templates to get a foundational understanding before moving on with me.
Our base.html template will contain:
{% if current_user.is_authenticated %}
<li class="active"><a href="{{url_for('views.index')}}">Home</a></li>
<li><a href="{{url_for('views.publish')}}">Publish</a></li>
<li><a href="{{url_for('views.about')}}">About</a></li>
<li><a href="{{url_for('user.contact')}}">Contact</a></li>
<li><a id="logout" href="{{url_for('user.logout')}}">Logout</a><li>
{% else %}
<li class="active"><a href="{{url_for('views.index')}}">Home</a></li>
<li><a href="{{url_for('views.about')}}">About</a></li>
<li><a href="{{url_for('user.contact')}}">Contact</a></li>
<li><a href="{{url_for('user.register')}}">Register</a></li>
<li><a id="login" href="{{url_for('user.login')}}">Login</a><li>
{% endif %}
</ul>
The code above is defining an HTML navigation menu. The menu will contain different items depending on whether the user is authenticated or not. If the user is authenticated (i.e. logged in), the menu will contain the following items: "Home", "Publish", "About", "Contact", and "Logout". If the user is not authenticated, the menu will contain the following items: "Home", "About", "Contact", "Register", and "Login".
The {% if %}
and {% else %}
tags define a control structure that allows the code to include or exclude certain blocks of HTML depending on the value of the expression within the if
tag. In this case, the expression is current_user.is_authenticated
, which will be either True
or False
depending on whether the user is authenticated.
The {{ }}
tags are used to output the value of a variable. In this case, the url_for()
function is being used to generate URLs for the different menu items. The argument to the url_for()
function is the name of a view function, which is a Python function that generates a response to a request.
Flash Messages
A flash message is a short message that appears on a webpage to communicate with the user. It is typically used to alert the user to something, such as an error, success, or warning. Flash messages are often temporary and will disappear after a short period of time or after the user has taken a certain action. They are useful for providing feedback to the user about their actions on the website.
<main>
<div class="container">
<!-- alerts for errors -->
{% with errors = get_flashed_messages(category_filter=["error"]) %}
{% if errors %}
<div id="failure" class="alert alert-danger alert-dismissible fade show"
role="alert" data-bs-dismiss="alert"
align="center">
{{ messages }}
{% for message in errors %}
{{ message }}
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- alerts for success -->
{% with success = get_flashed_messages(category_filter=["success"]) %}
{% if success %}
<div class="alert alert-success alert-dismissible fade show"
role="alert" data-bs-dismiss="alert" align="center">
{{ messages }}
{% for message in success %}
{{ message }}
{% endfor %}
</div>
{% endif %}
{% endwith %}
</div>
<div class="container">{% block content %}{% endblock %}</div>
</main>
The code first uses the get_flashed_messages()
function to retrieve any error messages that were previously "flashed" to the user using Flask's flash()
function. The with
statement is used to assign the returned error messages to a variable named errors
. The if
statement checks if there are any error messages, and if so, it creates a div element with the class "alert alert-danger" and displays the error messages.
The same process is repeated for success messages, but with different classes for the div element.
The last section of the code creates a container for the content of the web page, and the {% block content %}{% endblock %}
is used to define a block where other templates that extend this template can insert their own content.
This layout template provides the structure for the webpage and it will be used for all the pages that use this template. Also, it implements flash messages, which are messages that are passed to the next request after a redirect. They are typically used to show success or error messages to the user.
Moving on,
The next thing we will do is to create our first route which is our home page.
When creating our routes, we can make use of all the files in our resources folder. Recall that we created our Blueprints earlier. That's where they come into play!
In our views.py
file, we'll set up some basic routes such as the home page, about, and contact pages.
Let’s Import the following:
from flask import Blueprint, render_template, request, flash, redirect, url_for, jsonify
from models import ArticleModel, UserModel
from db import db
- Home page
For the home page, we can use the URL "/" to handle the index route. In a Flask app, we can use the @app.route()
decorator to define a route and pass in the URL as an argument. For example:
@app.route('/')
def index():
return 'Welcome to the home page!'
However, if we are using Blueprints in our application, we will use the name of the Blueprint in place of app
when defining the route. For example:
@view.route('/')
def index():
return 'Welcome to the home page!'
This way, we can organize our routes and views into logical groups using Blueprints."
This is what our index route will look like:
@view.route("/")
@view.route("/index")
def index():
articles = ArticleModel.query.all()
return render_template("index.html", user=current_user, articles=articles)
This code is defining a function called index
that will be run when the server receives a request to the "/"
or "/index"
route.
The function retrieves a list of all ArticleModel
objects from a database using the ArticleModel.query.all()
line of code and then renders the index.html
template, passing it the list of articles and the current_user
object as arguments. The render_template
function is a Flask function that generates an HTML response to the client by rendering an HTML template and injecting the variables provided as arguments into the template.
The @view.route
decorator above the function definition specifies the route (i.e., the URL path) that should trigger this function to run. The @view.route("/")
decorator specifies the root route (i.e., the home page) and the @view.route("/index")
decorator specifies an alternative route to the same function.
- About page
Here, we will create a page that renders information about the application. It is a static page that does not require the user’s input or interaction.
{% extends 'base.html' %}
{% block content %}
<section>
... write your about code here ...
</section>
{% endblock content %}
The following code sets up a route for the /about
URL, and when that route is requested, it will render the about.html
template and return it to the user.
# about route displays info about the page
@view.route('/about')
def about():
return render_template('about.html')
- Contact page
Here, we will create a page that renders information about the application. It is a static page that does not require the user’s input or interaction.
{% extends 'base.html' %}
{% block title%} Login {% endblock %}
{% block content %}
<form method="POST">
<div>
<label for="name">Name</label>
</div>
... input your form details here ...
<div>
<button>Submit</button>
</div>
</form>
{% endblock content %}
Do this in your **view.py**
file
The following code sets up a route for the /contact
URL that can handle both GET
and POST
requests. If the request is a POST
request, a flash message is sent to the user. In either case, the contact.html
template is rendered and returned to the user.
@user.route("/contact", methods=['GET', 'POST'])
def contact():
if request.method == 'POST':
flash('We appreciate the feedback, be on the lookout for our response', category='success')
return render_template("contact.html", current_user=current_user)
User Authentication
Register user
Login user
Logout user
Inside the register.html
This template defines an HTML form that allows the user to register by entering their email address, password and any other required info. The form will be inserted into the base.html
template and the resulting page will have the title "Home".
{% extends 'base.html' %}
{% block title %}Home{% endblock %}
{% block content %}
<div class="reg">
<h3>Register Now</h3>
</div>
<form class="forms" action= "{{url_for('user.register')}}" method="post" method="" id="register">
<div>
<label for="email"></label>Email:</label>
<input type="email" id="email" name="email" required>
</div><br>
<div>
<label for="password">Password(Minimum of 8 characters):</label>
<input type="password" id="password" name="password" minlength="8" required>
</div><br>
<div>
<button>Submit</button>
</div>
</form>
{% endblock content %}
In our user.py
, we’ll create our register route.
@user.route('/signup', methods = ['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form.get('username')
firstname = request.form.get('firstname')
lastname = request.form.get('lastname')
email = request.form.get('email')
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')
password_hash = generate_password_hash(password)
email_exists = UserModel.query.filter_by(email=email).first()
if email_exists:
flash('This mail already exists!', category='error')
elif password != confirm_password:
flash('Password does not match.', category='error')
elif len(password) < 8:
flash('Password is too short.', category='error')
elif len(email) < 5:
flash('Invalid email.', category='error')
else:
new_user = UserModel(firstname=firstname, lastname=lastname, username = username, email = email, password_hash = password_hash)
db.session.add(new_user)
db.session.commit()
flash('Your account has been created!')
login_user(new_user, remember=True)
return redirect(url_for('view.index'))
return render_template('register.html')
This function defines a route for the Flask web framework. The route is defined using the @user.route decorator, which specifies that this function should be called when the user visits the /signup URL.
The route can handle both GET and POST requests, as specified by the methods
parameter. If the request is a POST request, the function retrieves the form data from the request object and stores it in local variables. It then checks whether the email already exists in the database by querying the UserModel. If the email does not exist, the function checks whether the password and confirm_password fields match. If they match and the password is long enough, it creates a new user object and adds it to the database. Finally, it logs the user in and redirects them to the index page. If any of the validation checks fail, it displays an error message using the Flask flash function.
If the request is a GET request, the function simply renders the register.html template using the Flask render_template function.
Login User
The login process typically involves verifying the entered credentials against a database of authorized users to determine whether the user has the correct login credentials. If the entered credentials are correct, the user is granted access to the system or website. If the credentials are incorrect, the login process may be repeated until the correct credentials are entered, or the user may be denied access.
Inside the login.html file
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<form method="POST">
<br>
<h1 class="reg"> HELLO THERE!</h1>
<div class="form-group">
<fieldset>
<label for="email"> EMAIL:</label>
<input type="text" name="email" id="email" class="form-control" placeholder="Enter Email" required>
</fieldset>
<br>
<fieldset>
<label for="password"> PASSWORD:</label>
<input type="password" name="password" id="password" class="form-control" placeholder="Enter Password"
required>
</fieldset>
<br>
<fieldset align="center">
<button type="submit" id="button" class="button">SUBMIT</button>
</fieldset>
</div>
</form>
{% endblock %}
We’ll create the login route in our user.py file
@user.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'POST':
email = request.form.get("email")
password = request.form.get("password")
user = UserModel.query.filter_by(email=email).first()
if user:
if check_password_hash(user.password_hash, password):
flash(f"Good to have you back, {user.username}", category='success')
login_user(user, remember=True)
return redirect(url_for('view.index'))
else:
flash("Incorrect password!", category='error')
else:
flash('Email does not exist.', category='error')
return render_template("login.html")
When the route is accessed with the POST method, the code attempts to retrieve the user's email and password from the request form and use them to log the user in.
To log the user in, the code first queries a database for a user with the matching email. If a user with that email exists, it checks whether the password provided by the user matches the hashed password stored in the database for that user. If the passwords match, the user is logged in and redirected to the index page. If the passwords do not match, or if no user with the provided email exists, an error message is displayed.
If the route is accessed with the GET method, the code simply renders a template for the login page.
Logout route:
@auth.route('/logout')
def logout():
logout_user()
flash('Logout successful')
return redirect(url_for('view.index'))
This code defines a route for logging a user out of the system. The route is accessed through the '/logout' URL and can be accessed using any HTTP method.
When the route is accessed, the code calls the logout_user()
function, which is likely to clear any user session information or authentication tokens that were set when the user logged in. After logging the user out, the code displays a message indicating that the logout was successful and redirects the user to the index page.
Our article.py will handle some of the very basic routes, such as:
Publish article
Edit article
Delete article
Publish.html
This template defines an HTML form that allows the user to publish by filling in the **title
** and **bodytext
** fields. The form will be inserted into the publish.html
template.
{% extends 'base.html' %}
{% block title %}index{% endblock %}
{% block content %}
<div class="reg">
<h3>Publish your article</h3>
</div>
<form class="forms" action="{{url_for('view.publish')}}"method="POST">
<div>
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
</div>
<div class="body">
<h5>Text:</h5>
<textarea name="bodytext" id="result" class="txt" placeholder="Type here...">
</textarea>
</div>
<button class="pub">Publish</button>
</form>
{% endblock content %}
Just like in our view.py, we’ll do the necessary imports and then proceed to write our code.
@view.route("/publish", methods=['GET', 'POST'])
@login_required
def publish():
if request.method == "POST":
title = request.form.get('title')
bodytext = request.form.get('bodytext')
if not bodytext and not title:
flash('Please fill in the Title and Bodytext fields to proceed')
else:
article = ArticleModel(title=title, bodytext=bodytext, author=current_user.id)
db.session.add(article)
db.session.commit()
flash("Your article has been successfully published", category='success')
return redirect(url_for('view.index'))
return render_template('publish.html')
After creating a database model, the publish
function saves the title
and bodytext
from the form and adds them to the database using db.session.add
. The database is then updated with db.session.commit
. The user is then redirected to the index page using the url_for
function.
The publish
function uses the publish.html
form to gather data from the user's browser and stores it in ArticleModel
on the server. It then redirects the user back to the index page
Note: The HTTP "GET" and "POST" methods are used to retrieve data from and send data to a database, respectively. "GET" is the default method, and sends a request to the server to retrieve data. The server then returns the requested data. "POST" is used to send data from an HTML form to the server. This data is not cached by the server.
Edit Article
The edit function retrieves article data from the database to the browser. Once a user makes necessary changes, it updates the changes.
@article.route("/edit/<article_id>", methods=['GET', 'POST'])
@login_required
def edit(article_id):
article = ArticleModel.query.filter_by(id=article_id).first()
if request.method == 'POST':
title = request.form.get('title')
bodytext = request.form.get('bodytext')
if not article:
flash("We can't find your article, Try creating and publising one.", category='error')
elif current_user.id != article.author:
flash("You are not allowed to make changes to this article.", category='error')
else:
article.title = title
article.bodytext = bodytext
db.session.commit()
flash('You successfully edited this article', category='success')
return redirect(url_for('view.index'))
return render_template('edit.html', article=article)
This code defines a route for a webpage that allows a user to edit an article. The route is set to accept both GET and POST requests, and is decorated with the @login_required
decorator, which means that a user must be logged in to access this route.
The route is defined to accept a parameter, article_id
, which is the ID of the article that the user wants to edit. The function first retrieves the article from the database using the ArticleModel.query.filter_by(id=article_id)
method.
If the request method is a POST request, the function gets the new title and body text for the article from the request form. If the article does not exist or the user attempting to edit the article is not the author of the article, the function displays an error message using the flash
function. Otherwise, the function updates the article with the new title and body text, commits the changes to the database, and displays a success message. Finally, the function redirects the user back to the index page.
If the request method is a GET request, the function simply renders the edit.html
template and passes the article object to the template as a variable.
Delete Article
The delete function erases the article data from the database.
@article.route('/delete/<article_id>')
def delete(article_id):
article = ArticleModel.query.filter_by(id=article_id).first()
if not article:
flash("This article does not exist.", category='error')
elif current_user.id != article.author:
flash('You are not allowed to delete this article', category='error')
else:
db.session.delete(article)
db.session.commit()
flash('You have deleted this article', category='success')
return redirect(url_for('view.index'))
Styling with CSS
Enhance the visual appeal and user experience of your blog by incorporating CSS. Unfortunately, due to time constraints, we will not be covering that aspect in this tutorial.
Now that we are done building our blog project, we will go ahead and test our code.
To test your code, run either of the following commands on your terminal:
$ python app.py
$ flask run
Copy the URL to your browser. You can now interact with your code, and see if all your functions and routes are working.
Note: If you want to terminate the flask process on your terminal, hit ctrl c on your keyboard. Then you may now deactivate your virtual environment.
Summary of what we did:
At the end of this project, we worked on:
Setting up our flask environment
Use of jinja templates
CRUD Operations
Use of Werkzeug to hash our passwords
Flash messages
User authentication.
Conclusion
We are pleased to have completed a successful blog project using the Flask framework, which allowed us to showcase our skills. I hope this project was a valuable learning experience for you, and I look forward to collaborating on future projects. As a next step, I recommend we consider implementing user profiles and integrating social media features. In the future, I plan to document my learning journey and would appreciate your feedback and comments.
You can follow me here on Starr’s Space. Also connect with me on Twitter, GitHub, Hashnode, and Medium.