Python Flask: A Comprehensive Guide from Basic to Advanced

Moraneus
15 min readMar 9, 2024

--

Python Flask is a lightweight and powerful web framework, perfect for developers looking to build web applications quickly and with minimal fuss. It stands out for its simplicity, flexibility, and fine-grained control over its components, making it an ideal choice for both beginners and seasoned developers. In this comprehensive guide, we’ll explore Flask from the ground up, diving into its core features, comparing it with other frameworks, and illustrating its capabilities with practical examples.

The Anatomy of Flask Routing

Imagine the web as a sprawling metropolis, and your web application as a series of destinations within it. Routing in Flask is the signage that guides visitors to their desired locations. A Python decorator defines each route in Flask — @app.route(), which is akin to a signpost, dictating the URL path that leads to a specific function or "destination" in your web application.

The @app.route() Decorator

At its core, the @app.route() decorator is a Python function that Flask provides to associate a URL pattern with a function. The decorator takes the URL path as its first argument and optionally, methods (like GET, POST) as another. When Flask receives a request, it matches the URL against these patterns and invokes the associated function. Here’s a closer look at a simple route definition:

@app.route('/welcome-astronaut')
def welcome_astronaut():
return 'Welcome to the Interstellar Space Station!'

In this example, @app.route('/welcome-astrunaut') tells Flask that whenever a web browser requests our URL ending in /welcome-astrunaut, it should be called the welcome_astronaut function and respond with "Welcome to the Interstellar Space Station!".

Dynamic routing adds a layer of flexibility, allowing routes to capture parts of the URL as variables. This is particularly useful for user-specific pages or categories:

@app.route('/astronaut/<username>')
def show_astronaut_profile(username):
# Imagine fetching astronaut details from a cosmic database here
return f'Galactic Explorer Profile: {username}'

In the above route, <username> acts as a placeholder for any name entered into the URL. Flask captures this as the username variable and passes it to the show_astronaut_profile function. This dynamic approach enables the creation of personalized content with minimal effort.

Specifying HTTP Methods in Flask

To allow a route to handle different HTTP methods, you include a methods parameter in the @app.route() decorator, passing in a list of the methods you want to accept. For example, if you're creating a form submission page, you might want to handle both GET and POST requests; GET to fetch the form and POST to submit it.

Here’s how you can specify these methods in your route:

@app.route('/mission-application', methods=['GET', 'POST'])
def mission_application():
if request.method == 'POST':
# Process the mission application submission
# For example, save applicant details to a database or perform eligibility calculations
return 'Application for the Mars Mission submitted successfully!'
# If it's not a POST request, render the mission application form
return render_template('mission_application_form.html')

In this example, the route /mission-application serves as a portal for aspiring astronauts or mission supporters to apply for a journey to Mars. The function mission_application handles both the viewing of the application form (via a GET request) and the submission of the form (via a POST request). Upon successful submission, the user receives a confirmation that their application for the Mars Mission has been successfully submitted, making the interaction engaging and thematic to the space exploration concept.

Handling Different HTTP Methods

The pattern shown above is typical for web forms and similar scenarios where you might want to render a page for GET requests and perform actions for POST requests. However, Flask’s flexibility with HTTP methods allows for RESTful API development as well, where you might use methods like PUT, DELETE, etc., to manipulate resources.

For example, let’s envision we’re managing a catalog of planets in an intergalactic database. Each route will correspond to operations for adding, retrieving, updating, and deleting planetary data. This thematic approach transforms the API into a tool for exploring and maintaining a database of celestial bodies.

@app.route('/planets', methods=['POST'])
def add_planet():
# Code to add a new planet to the database
return 'Planet added to the catalog', 201

@app.route('/planets/<int:planet_id>', methods=['GET'])
def get_planet(planet_id):
# Code to return details of a specific planet
return 'Planet details retrieved'

@app.route('/planets/<int:planet_id>', methods=['PUT'])
def update_planet(planet_id):
# Code to update existing planet details in the database
return 'Planet information updated'

@app.route('/planets/<int:planet_id>', methods=['DELETE'])
def delete_planet(planet_id):
# Code to remove a planet from the database
return 'Planet removed from the catalog', 204

In this example, the /planets route serves as an endpoint for managing a catalog of planets. This setup allows space enthusiasts, astronomers, or database managers to add new planets (POST), retrieve details about specific planets (GET), update information on existing planets (PUT), and remove planets from the catalog (DELETE). Each operation enhances the interactivity of the database, inviting users to contribute to and explore a rich repository of knowledge about the universe. This API, while practical, also sparks the imagination, allowing users to engage with the vastness of space through a structured, RESTful interface.

The Art of Jinja2 Templating: Bringing Data to Life

Beyond routing, Flask employs Jinja2 for templating, which allows for the creation of dynamic HTML content. Templating separates the logic of data handling from the presentation layer, making your code cleaner and more maintainable.

Templates in Flask are HTML files with placeholders for variables and expressions, which get filled with data when rendered. This is especially powerful for displaying user data, settings, or any dynamic content.

A Quick Dive into Templating

Consider an HTML file named welcome.html stored in the templates folder:

<!DOCTYPE html>
<html>
<head>
<title>Welcome Page</title>
</head>
<body>
<h1>Welcome, {{ name }}!</h1>
</body>
</html>

The {{ name }} syntax denotes a variable placeholder. When rendering this template with Flask:

@app.route('/welcome/<name>')
def welcome(name):
return render_template('welcome.html', name=name)

Flask fetches welcome.html, replaces {{ name }} with the name variable's value provided in the URL, and serves the customized page to the user.

Extending Flask’s Wings: The Power of Extensions

Flask’s “micro” designation refers to its core: small and lightweight. Yet, its design is deliberately extensible. The ecosystem thrives with extensions for database integration, form handling, authentication, and more, each designed to seamlessly integrate with Flask applications.

Spotlight on Flask Extensions

  • Flask-SQLAlchemy: Simplifies database operations, offering an ORM layer for mapping Python classes to database tables.
  • Flask-WTF: Provides tools for working with forms, including CSRF protection and integration with WTForms for validation.
  • Flask-Login: Manages user authentication, handling logins, and maintaining user sessions.

Each extension is a gateway to added functionality, adhering to Flask’s philosophy of simplicity and extensibility. For instance, integrating Flask-Login into your application involves minimal setup but offers robust authentication mechanisms, enhancing security and user experience.

Getting Started with Flask

Flask is built on two core components: Werkzeug, a WSGI web application library, and Jinja2, a template engine. It is often referred to as a microframework because it keeps the core simple but extensible with numerous extensions.

Installation

To get started with Flask, you first need to install it. Assuming you have Python installed on your system, you can install Flask using pip:

pip install Flask

To The Flask And Byond — First Web Application

Let’s dive deeper into crafting a sophisticated Flask application, where we integrate user registration, login functionality, database interactions, and dynamic webpage rendering with Jinja2. This guide will walk you through setting up a mini web app that allows users to register, log in, and access a personalized dashboard. It’s a fantastic way to explore Flask’s capabilities further and see how different pieces fit together to create a functional application.

Getting Your Environment Ready

Before we start coding, ensure you have Flask-SQLAlchemy and Flask-Login installed. These libraries are essential for our project, providing the database integration, and user authentication functionalities, respectively. You can install them using pip, but first, it is recommended to use a virtual environment:

  1. Create a new directory for your project and navigate into it.
  2. Set up a virtual environment for Python dependencies:
python3 -m venv venv
source venv/bin/activate

3. Install Flask and Flask components by running:

pip install Flask Flask-SQLAlchemy Flask-Login flask-wtf email_validator

For our Flask application which includes user authentication, database interaction, and Jinja2 templating, the directory structure would look like this:

/your-flask-app
├── app.py # Main application file where Flask app is define
├── /models # Hold the model for db
│ └── users.py # The user model
├── /forms # Hold the forms for our web application
│ ├── login_form.py
│ └── register_form.py
├── /templates # Directory for Jinja2 templates
│ ├── login.html # Template for the login page
│ ├── register.html # Template for the registration page
│ └── index.html # Template for the dashboard/index page
├── /instance # Directory for db file (created upon first run)
│ └── site.db. # The db file (created upon first run)
└── /venv # Virtual environment directory (optional but recommended)

Organizing a Flask project efficiently is crucial for maintainability and scalability. Here’s how a well-structured Flask project can streamline development:

  • app.py: This central file is where your Flask app comes to life. It's responsible for defining and configuring your application, connecting URLs to Python functions, and setting up extensions like SQLAlchemy for database interactions and LoginManager for handling user authentication. It orchestrates the app's routing, view functions, and database initialization.
  • /models: This directory is the backbone of your application's data structure, housing models that define the schema of your database. For instance, a users.py file within this directory outlines the User model, incorporating both Flask-SQLAlchemy's db.Model for database interactions and Flask-Login's UserMixin for authentication features. This model details user attributes, including id, username, email, and password_hash, and provides methods for password management.
  • /forms: User input and interactions are managed here. The directory includes form classes like LoginForm and RegisterForm, built with Flask-WTF to define form fields and validators. This setup ensures user data is correctly captured and validated, enhancing security and user experience.
  • /templates: Containing Jinja2 templates, this directory enables dynamic HTML rendering. Templates for login, registration, and the dashboard are defined here, allowing for a rich, interactive user interface. Jinja2's templating language makes it easy to integrate Python logic directly into HTML, facilitating the creation of responsive, dynamic web pages.
  • /instance: Generated automatically on your application's first run, this folder typically holds configuration files and local databases like site.db for SQLite. Flask's design to look for an instance folder aids in separating instance-specific resources from the main application code, which is useful for configuration that shouldn't be committed to version control.
  • /venv (optional but recommended): A virtual environment is pivotal for managing project-specific dependencies isolated from the global Python environment. This ensures that your project's libraries are kept separate, preventing version conflicts and aiding in replicable development setups.

This modular approach not only clarifies the project’s structure, making it easier to navigate and understand but also separates concerns, ensuring that each part of the application focuses on a specific task. Whether it’s handling data models, processing user forms, rendering templates, or managing configurations and dependencies, a well-organized project structure is key to building robust and efficient web applications with Flask.

Initialize Your Flask App

The first step is to create a new Python file, app.py, where we'll set up our Flask application, configure the database, and initialize Flask extensions like Flask-Login:

app.py (we will extend this file later)

# app.py

from flask import Flask
from flask_login import LoginManager
from models.users import db

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key_here'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db.init_app(app)

login_manager = LoginManager(app)
login_manager.login_view = 'login'

Define the User Model

Next, let’s create a User model that Flask-SQLAlchemy will use to create the users table in our database. This model will include methods for setting and checking passwords securely, ensuring that user credentials are handled with the utmost care. The incorporation of the UserMixin from Flask-Login provides essential user session management functionalities, making our User model not only a representation of user data but also a cornerstone for secure authentication in our application.

models/users.py

# models/users.py

from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))

def set_password(self, password):
self.password_hash = generate_password_hash(password)

def check_password(self, password):
if self.password_hash is None:
return False
return check_password_hash(self.password_hash, password)


def init_models(app):
with app.app_context():
db.create_all()

In our Flask application, the UserMixin class from Flask-Login plays a pivotal role in our User model. It brings essential functionalities for managing user sessions, such as identifying if a user is authenticated or active. This mixin helps ensure our model meshes well with Flask's authentication mechanisms, streamlining user management.

We define our User model using Flask-SQLAlchemy, turning a simple Python class into a robust database table. This table includes fields for user identification, such as id, username, email, and password_hash, with specific constraints to maintain uniqueness and security. This password_hash is crucial for safeguarding user passwords by storing them in a hashed format, thanks to Werkzeug's security features.

To further bolster password security, our model incorporates methods to set and verify passwords. The set_password method hashes passwords before they're stored while check_password ensuring login attempts match the hashed passwords in the database.

Initializing the model in our application is straightforward with the init_models function, which calls db.create_all() to create the necessary tables based on our models. This can also be achieved using the Flask shell or separate scripts for flexibility and control over database setup.

The user_loader function is essential for Flask-Login integration, allowing the application to fetch user details for session management. By initializing our User model and defining this function, we enable Flask-Login to maintain secure user sessions, a cornerstone of user authentication in Flask applications.

Together, these elements form a comprehensive system for user authentication and management, exemplifying how Flask and its extensions empower developers to create secure, efficient web applications.

User Loader Function

In Flask applications that require user authentication, integrating Flask-Login is a common approach to managing user sessions. A key part of setting up Flask-Login involves establishing a way for the application to identify and load user details from the database, based on the unique session identifiers that Flask-Login maintains. This process is facilitated through the initialization of the User model and the definition of a user loader function within the application’s setup, typically found in the app.py file.


# app.py

from sqlalchemy.orm import Session
from models.users import init_models, User

# your existing app.py code continues here...

init_models(app)

@login_manager.user_loader
def load_user(user_id):
with Session(db.engine) as session:
return session.get(User, int(user_id))

Initializing your User model with init_models(app) is a crucial first step in setting up your Flask application. This process prepares your database, ensuring it's ready to store, retrieve, and manage user data smoothly. After setting up the model, defining a user loader function with @login_manager.user_loader becomes necessary. This function, critical for user authentication, fetches user instances from the database using user IDs. Flask-Login relies on this to maintain user sessions, calling it whenever it needs to identify a user session.

Updating your code to use Session.get() instead of Query.get() not only aligns with the best practices for SQLAlchemy but also clarifies the transaction boundaries within your application. This update is vital for future-proofing your code, especially with the upcoming SQLAlchemy 2.0 changes.

In essence, these steps are foundational for secure user authentication in Flask, ensuring your application can efficiently manage user sessions. This structure showcases Flask’s capability to support developers in creating secure, user-centric web applications.

Creating Routes

In Flask, as I explained before, routes are used to direct users to different parts of our application. We’ll need routes for logging in, logging out, registering, and accessing the dashboard:

  • Login: Show the login form and handle login submissions.
  • Logout: Log the user out and redirect them to the login page.
  • Register: Show the registration form and handle user sign-ups.
  • Index/Dashboard: Show a personalized dashboard to logged-in users.

Here’s how you define these routes in app.py:


# app.py

from flask import render_template, redirect, url_for, flash
from flask_login import, login_required, login_user, logout_user, current_user
from forms.login_form import LoginForm
from forms.register_form import RegisterForm
from models.users import User, db

# your existing app.py code continues here...

@app.route('/')
@login_required
def index():
if not current_user.is_authenticated:
return redirect(url_for('login'))
return render_template('index.html', title='Dashboard', username=current_user.username)


@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user, remember=True) # Add remember=True parameter
flash('Logged in successfully!', 'success')
return redirect(url_for('index'))
else:
flash('Invalid username or password', 'danger')
return render_template('login.html', form=form)


@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))


@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Registration successful! Please log in.', 'success')
return redirect(url_for('login'))
return render_template('register.html', form=form)

Forms

Forms are crucial for interactive web applications. They collect user inputs, which can range from simple text data to passwords and email addresses. Effective form handling is pivotal not only for capturing this data but also for validating it to ensure that it meets certain criteria. This is where Flask-WTF and WTForms come into play, providing a robust framework for dealing with forms in Flask-based applications.

forms/login_form.py

# forms/login_form.py

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired


class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Login')

forms/register_form.py

# forms/register_form.py

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, EmailField
from wtforms.validators import DataRequired, Email


class RegisterForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()])
email = EmailField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired()])
submit = SubmitField('Register')

Two common forms in web applications are login and registration forms, each serving a distinct purpose:

  • Login Forms: These forms are gateways for users to access their accounts. A typical login form includes fields for a username and password. It’s a straightforward yet critical component for authenticating users, ensuring that access is granted only to those with valid credentials.
  • Registration Forms: These are used for signing up new users. They usually collect more information than a login form, such as a username, email address, and password. The registration form is the first step in creating a user account, making it essential for gathering initial user data while ensuring its validity.

Templates

You will need to create Jinja2 templates for the login, registration, and index (dashboard) pages. Here are basic examples for each:

templates/login.html

<!DOCTYPE html>
<html>
<head>
<title>Login</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container mt-5">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endfor %}
{% endif %}
{% endwith %}

<h1>Login</h1>
<form method="POST" action="{{ url_for('login') }}">
{{ form.csrf_token }}
<div class="form-group">
{{ form.username.label }}
{{ form.username(class="form-control") }}
</div>
<div class="form-group">
{{ form.password.label }}
{{ form.password(class="form-control") }}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
<p class="mt-3">Don't have an account? <a href="{{ url_for('register') }}">Register</a></p>
</div>
</body>
</html>

templates/register.html

<!DOCTYPE html>
<html>
<head>
<title>Register</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container mt-5">
<h1>Register</h1>
<form method="POST" action="{{ url_for('register') }}">
{{ form.csrf_token }}
<div class="form-group">
{{ form.email.label }}
{{ form.email(class="form-control") }}
</div>
<div class="form-group">
{{ form.username.label }}
{{ form.username(class="form-control") }}
</div>
<div class="form-group">
{{ form.password.label }}
{{ form.password(class="form-control") }}
</div>
{{ form.submit(class="btn btn-primary") }}
</form>
</div>
</body>
</html>

templates/index.html

<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">First Flask App</a>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('logout') }}">Logout</a>
</li>
</ul>
</div>
</nav>

<div class="container mt-5">
<h1>Welcome, {{ username }}!</h1>
<p>This is the dashboard.</p>
</div>
</body>
</html>

Running Your Flask Application

Finally, to make your app come to life, add the following to the end of your app.py:

# app.py

# your existing app.py code continues here...

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

This command starts your Flask application. By default, the web server is defined in http://127.0.0.1:5000. Just open your web browser in this. address.

Here is the final version of the app.py file:

# app.py

from flask import Flask, render_template, redirect, url_for, flash
from flask_login import LoginManager, login_required, login_user, logout_user, current_user
from sqlalchemy.orm import Session
from forms.login_form import LoginForm
from forms.register_form import RegisterForm
from models.users import init_models, User, db

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key_here'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db.init_app(app)

login_manager = LoginManager(app)
login_manager.login_view = 'login'
init_models(app)


@login_manager.user_loader
def load_user(user_id):
with Session(db.engine) as session:
return session.get(User, int(user_id))


@app.route('/')
@login_required
def index():
if not current_user.is_authenticated:
return redirect(url_for('login'))
return render_template('index.html', title='Dashboard', username=current_user.username)


@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user, remember=True) # Add remember=True parameter
flash('Logged in successfully!', 'success')
return redirect(url_for('index'))
else:
flash('Invalid username or password', 'danger')
return render_template('login.html', form=form)


@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('login'))


@app.route('/register', methods=['GET', 'POST'])
def register():
form = RegisterForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Registration successful! Please log in.', 'success')
return redirect(url_for('login'))
return render_template('register.html', form=form)


if __name__ == '__main__':
app.run(debug=True)
Login screen
Register screen
Dashboard screen

You can also find the whole code in:

Your Support Means a Lot! 🙌

If you enjoyed this article and found it valuable, please consider giving it a clap to show your support. Feel free to explore my other articles, where I cover a wide range of topics related to Python programming and others. By following me, you’ll stay updated on my latest content and insights. I look forward to sharing more knowledge and connecting with you through future articles. Until then, keep coding, keep learning, and most importantly, enjoy the journey!

Happy programming!

References

--

--