A great sports club website is more than an attractive landing page—it should also provide a secure and seamless experience for its members. In this tutorial, we'll transform the Jersey City Pickleball Club website by building a modern Sign In and Sign Up system using Flask, Bootstrap 5, HTMX, TinyDB, and Flask-Login. You'll learn how to create a responsive authentication interface with smooth partial page updates, securely hash user passwords, manage user sessions, and protect member-only pages, all while keeping the codebase clean, lightweight, and easy to maintain.
Prerequisite:
This tutorial is part of the Flask Landing Page and Reservation System Series.📚 View the Complete Flask Landing Page and Reservation System Series
Before I begin, please activate the virtual environment and install the required dependencies.
python -m venv venvvenv\Scripts\activatepip install flask flask_login tinydb flask_wtf
- - basically all the routes are in this file,
- db.py - setting up user authentication management and creating a simple JSON database,
- form.py - creates a separate form and defines the field for each form, and
- users.py - defines a
Userclass using Flask-Login'sUserMixin.
- base.html - this file contains the Bootstrap, HTMX, and custom CSS links and CDNs. Therefore, I simply call out the content and need not repeat it in other HTML files.
- dashboard.html - the destination page after the user's successful sign-in,
- new_signin.html - Flask backend returns just the sign-up form without reloading the entire page,
- privacy - loading the privacy page entirely,
- terms_conditions - loading the terms and conditions entirely
- auth_box.html - it contains the 2 tabs, including the sign-in and sign-up tabs. Each tab is attached to the respective form.
- signin_form.html - a description of the fields of the sign-in form,
- signup_form.html - a description of the fields of the sign-up form.
from tinydb import TinyDB, Query
from user import User
from flask_login import LoginManager
db = TinyDB("users.json")
users_table = db.table("users")
UserQuery = Query()
login_manager = LoginManager()
@login_manager.user_loader
def load_user(user_id):
doc = users_table.get(doc_id=int(user_id))
if doc:
return User(doc)
return None
I create a TinyDB "users.json," then create a users table, and create a query object used for searching and filtering through those user records later. user_id stored inside their secure browser cookie.from flask_login import UserMixin
class User(UserMixin):
def __init__(self, user_data):
self.id = str(user_data.doc_id)
self.username = user_data["username"]
self.password_hash = user_data["password_hash"]Even though I use TinyDB to create a users.json. However, Flask-Login does not know anything about my database. It only knows how to manage a user session. Therefore, I need to provide a user class that represents a logged-in user. Why use an ID?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>User-Login | App{% block title %} {% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/
dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static',
filename='
') }}">
</head>
<body class="bg-light">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show">
{{ message }}
<button type="button"
class="btn-close"
data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% block content %}
{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/
dist/js/bootstrap.bundle.min.js"></script>
<script src="https://unpkg.com/htmx.org@2.0.0"></script>
</body>
</html>
This is a 'mother' for every HTML page where I have located - Bootstrap CDN,
- HTMX CDN and
- My custom CSS location.
{% extends 'base.html' %}
{% block content %}
Content here
{% endblock %}It will load those CDNs and links without the need to repeat them. This aligns with the philosophy of not repeating it in coding. Here is exactly how it works behind the scenes when Flask renders this template:
Inheritance:
{% extends 'base.html' %}tells Flask to grab the entire structure of your base file (including the<head>, Bootstrap CSS/JS, and HTMX scripts).Injection: Flask takes the
"Content here"text from your child template and injects it right into the middle of the body where it{% block content %}{% endblock %}is defined in the base layout.The Result: The browser receives a single, complete HTML page that contains all your CDN dependencies alongside your specific page content.
from flask import (Flask, render_template, redirect, url_for,
flash, make_response)
from flask_login import login_required,login_user, logout_user
from werkzeug.security import (generate_password_hash,
check_password_hash)
from form import SigninForm, SignupForm, SignoutForm
from db import login_manager, users_table, UserQuery
from user import User
app=Flask(__name__)
app.secret_key = 'your_secret_key'
@app.route('/')
def home():
return render_template(
'new_signin.html',
form=SigninForm()
)
new_signin.html template. {% block title %}New Sign In{% endblock %}
{% block content %}
<section class="hero d-flex flex-column align-items-center justify-content-center">
<img src="{{ url_for('static', filename='assets/title.png') }}"
class="hero-title mb-4">
<div class="card shadow-sm w-100" style="max-width:400px;">
<div id="form-container"
hx-get="{{ url_for('signin_form') }}"
hx-trigger="load"
hx-swap="innerHTML">
</div>
</div>
<footer class="footer">
<div class="footer-content text-body-secondary small">
<a href="{{ url_for('privacy') }}"
class="text-decoration-none text-body-secondary">Privacy</a>
<span>|</span>
<a href="{{ url_for('terms_conditions') }}"
class="text-decoration-none text-body-secondary">Terms & Condition</a>
<span>|</span>
<span>NJPC.right reserved @ 2026</span>
</div>
</footer>
</section>
{% endblock %}style.css
body, html {
margin: 0;
padding: 0;
height: 100%;
}
.hero {
min-height: 100vh;
width: 100%;
position: relative;
background:
linear-gradient(
rgba(0,0,0,.5),
rgba(0,0,0,.5)
),
url("../static/assets/login.jpg");
background-size: cover;
background-position: center center;
padding-bottom: 80px;
}
/* Title image - No hardcoded positioning needed */
.hero-title {
max-width: 100%;
width: 800px;
height: auto;
z-index: 2;
}
.footer {
/* Remove absolute positioning so it cannot overlap */
margin-top: 20px;
/* Shrink-wrap configuration */
width: max-content;
max-width: 95%;
background-color: #ffffff !important;
/* Spacing and Styling */
padding: 8px 20px;
border-radius: 30px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
text-align: center;
}
/* Keeps everything horizontally aligned in 1 single row */
.footer-content {
display: flex;
align-items: center;
gap: 10px;
margin: 0;
white-space: nowrap;
}(i) Hero Section (<section>)
Bootstrap/custom classes used here are
- hero is a custom CSS class that styles the hero section by applying the background image, height, overlay, and overall appearance of the authentication page.
- d-flex is a Bootstrap Flexbox utility class that enables the section to use the Flexbox layout.
- flex-column is a Bootstrap utility class that arranges all child elements vertically from top to bottom.
- align-items-center is a Bootstrap utility class that horizontally centers all elements within the hero section.
- justify-content-center is a Bootstrap utility class that vertically centers the content within the hero section.
(ii) Title Image (<img>)
Bootstrap/custom classes used here are
- hero-title is a custom CSS class that controls the size and responsiveness of the Jersey City Pickleball Club title image.
- mb-4 is a Bootstrap spacing utility class that adds a bottom margin to create space between the title image and the authentication card.
(iii) Authentication Card (<div>)
Bootstrap classes used here are
- card is a Bootstrap component that creates a clean container with rounded corners for displaying the Sign In and Sign Up forms.
- shadow-sm is a Bootstrap utility class that adds a subtle shadow around the card, giving it a slightly elevated appearance.
- w-100 is a Bootstrap width utility class that allows the card to occupy the full available width of its parent container while respecting the maximum width specified by the inline style.
- max-width: 400px is an inline CSS style that limits the maximum width of the card to 400 pixels, ensuring a compact and readable layout on larger screens.
(iv) HTMX Form Container (<div id="form-container">)
The HTMX attributes used here are the following:
- id="form-container" uniquely identifies the container where the Sign In and Sign Up forms are dynamically loaded.
- hx-get sends an asynchronous HTTP GET request to the specified Flask route to retrieve the required form.
- hx-trigger="load" automatically triggers the request when the page has finished loading, displaying the Sign In form by default.
-
hx-swap="innerHTML" replaces only the contents inside the
form-containerelement with the HTML returned by the Flask route, avoiding a full page refresh.
(v) Footer (<footer>)
Bootstrap/custom classes used here are
- footer is a custom CSS class that styles the footer's position, spacing, and appearance.
- footer-content is a custom CSS class that controls the alignment and layout of the footer content.
- text-body-secondary is a Bootstrap utility class that applies a secondary text colour, making the footer less visually dominant.
- small is a Bootstrap typography utility class that displays the footer text in a smaller font size.
- text-decoration-none is a Bootstrap utility class that removes the default underline from hyperlinks.
- text-body-secondary is also applied to the hyperlinks to ensure they match the footer's text colour and maintain a consistent appearance.
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms import validators
class SigninForm(FlaskForm):
username = StringField('Username',
[validators.DataRequired('Username')])
password = PasswordField('Password',
[validators.DataRequired('Password')])
submit = SubmitField('Sign In')
class SignupForm(FlaskForm):
username = StringField("Username", [validators.DataRequired('Username')])
email = StringField("Email", [validators.DataRequired('Email')])
password = PasswordField('Password', [validators.DataRequired('Password')])
submit = SubmitField("Sign Up")
Why use Flask-WTF?
Flask-WTF simplifies form handling by combining WTForms with Flask-specific features. Instead of manually retrieving values using code, developers can define forms as Python classes, automatically validate user input, generate HTML form fields, and benefit from built-in CSRF protection. This approach results in cleaner, more secure, and easier-to-maintain code.
StringField creates a single-line text input field that allows users to enter textual information, such as a username or email address.
PasswordField creates a password input field. Unlike a StringField, the characters entered by the user are automatically masked in the browser to improve security and privacy.
SubmitField creates a submit button that allows the user to send the completed form to the Flask application for processing.
The DataRequired() validator ensures that a form field is not left empty before the form is submitted. If the user submits an empty field, validation fails, and the specified error message is displayed.
(v) SigninForm
- username – accepts the user's username.
- password – accepts the user's password and masks the input.
- submit – creates the Sign In button.
- username – allows the user to choose a username.
- email – accepts the user's email address.
- password – accepts the user's password and masks the input.
- submit – creates the Sign Up button.
login_manager.init_app(app)
login_manager.login_view = "signin"
@app.route("/signin/form")
def signin_form():
form = SigninForm()
return render_template(
"partials/auth_box.html",
active_tab="signin",
form_template="partials/signin_form.html",
form=form
)
@app.route("/signup/form")
def signup_form():
form = SignupForm()
return render_template(
"partials/auth_box.html",
active_tab="signup",
form_template="partials/signup_form.html",
form=form
)
This method initialises Flask-Login and attaches it to the Flask application.
-
login_manageris an instance that manages user authentication, login sessions, and user loading. -
init_app(app)registers the userLoginManagerwith the Flask application so that Flask-Login can manage user sessions and authentication throughout the application.
Without this line, Flask-Login is not connected to your Flask application, and features such as login_user(), logout_user(), current_user, and @login_required will not function correctly.
This property specifies the login page that unauthenticated users are redirected to when they attempt to access a protected route. login_view stores the endpoint name of the login route.
(iii) @app.route("/signin/form")
This decorator defines a Flask route that responds to HTTP GET requests sent to /signin/form. In this project, the route is called by HTMX to dynamically load the Sign In form into the authentication card without refreshing the entire webpage.
This function handles requests made to the /signin/form route. Its purpose is to create a new Sign In form and return the HTML required to display it.
This statement creates an instance of the SigninForm class. The form object contains all the input fields, validation rules, and CSRF protection required for the Sign In page.
This decorator defines a Flask route that responds to HTTP GET requests sent to /signup/form. In this project, the route is called by HTMX to dynamically load the sign-up form without refreshing the entire webpage.
This statement creates an instance of the SignupForm class. The form object contains all the fields, validation rules, and CSRF protection required for the user registration form.
(viii) render_template()
- active_tab="signin" or "signup" identifies the Sign In and Sign Up tabs, respectively, as the currently active tabs.
- The template uses this value to apply the appropriate Bootstrap classes so that the active tab is visually highlighted.
- form_template="partials/signin_form.html" or "partials/signup_form.html" specifies the partial template that contains the sign-in or sign-up form.
- The auth_box.html template dynamically includes this file using Jinja's {% include %} statement.
How to switch between the sign-in and sign-up tabs?
<ul class="nav nav-tabs nav-fill">
<li class="nav-item">
<a class="nav-link
{% if active_tab == 'signin' %}
active bg-dark text-white
{% endif %}"
hx-get="{{ url_for('signin_form') }}"
hx-target="#form-container"
hx-swap="innerHTML">
Sign In
</a>
</li>
style.css
.nav-tabs .nav-link {
color: #6c757d;
border-radius: 8px 8px 0 0;
transition: all 0.3s ease;
}
.nav-tabs .nav-link:hover {
background-color: #f1f1f1;
color: #000;
}
.nav-tabs .nav-link.active-tab {
background-color: #212529; /* dark background */
color: white;
font-weight: 600;
border-color: #212529;
}
(i) Nav-Link and Active State () - nav-link: A Bootstrap class that styles the anchor tag as a clean navigation tab item.
- {% if active_tab == 'signin' %} active bg-dark text-white {% endif %}: This Jinja checks whether the variable active_tab is set to "signin". If true, it dynamically appends the Bootstrap classes active, bg-dark (dark background), and text-white (white text) to make this specific tab stand out as the currently selected one.
- hx-get="{{ url_for('signin_form') }}": When a user clicks this "Sign In" tab, HTMX intercepts the click and sends an asynchronous AJAX GET request directly to my Flask view function, signin_form.
- hx-target="#form-container": Tells HTMX exactly where to put the server's response. In this case, it targets the wrapper element with the ID form-container.
- hx-swap="innerHTML": Instructs HTMX to replace everything inside that target container with the partial HTML returned by the Flask route (partials/signup_form.html inside my auth_box.html layout), giving me a smooth transition without triggering a hard browser reload.
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-warning alert-dismissible
fade show" role="alert">
{{ message }}
<button type="button"
class="btn-close"
data-bs-dismiss="alert">
</button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form
hx-post="{{ url_for('signup') }}"
hx-target="#form-container"
hx-swap="innerHTML">
{{ form.hidden_tag() }}
{{ form.username.label }}
{{ form.username(class="form-control") }}
<br>
{{ form.email.label }}
{{ form.email(class="form-control") }}
<br>
{{ form.password.label }}
{{ form.password(class="form-control") }}
<br>
{{ form.submit(class="btn btn-success w-100") }}
</form>
(ii) signin.html
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-warning alert-dismissible fade show">
{{ message }}
<button class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
<form
hx-post="{{ url_for('signin') }}"
hx-target="#form-container"
hx-swap="innerHTML">
{{ form.hidden_tag() }}
{{ form.username.label }}
{{ form.username(class="form-control") }}
<br>
{{ form.password.label }}
{{ form.password(class="form-control") }}
<br>
{{ form.submit(class="btn btn-primary w-100") }}
</form>Their Similarities
- Both are partial templates that are dynamically loaded into the authentication card using HTMX.
-
Both display Flask flash messages using
get_flashed_messages()and Bootstrap's dismissible Alert component. -
Both submit the form asynchronously using the
hx-postattribute, eliminating the need for a full page refresh. -
Both are used
hx-target="#form-container"to specify that only the authentication card should be updated. -
Both use
hx-swap="innerHTML"to replace the existing form with the server's response. -
Both include
{{ form.hidden_tag() rendersthe hidden CSRF token, protecting the application against Cross-Site Request Forgery (CSRF) attacks. - Both use Flask-WTF to automatically render form fields and perform server-side validation.
- Both use Bootstrap classes to create responsive, modern-looking forms.
| Sign-In Form | Sign-Up Form |
|---|---|
|
|
|
|
|
|
|
|
|
|
🎁 Get Your FREE Flask Cheat Sheet
Get more Flask, Python automation, Docker, and HTMX tutorials delivered to your inbox.
✓ Practical coding tutorials
✓ Automation tips for SMEs
✓ New project ideas and templates
Download my FREE Flask Cheat Sheet (PDF)
@app.route("/signup", methods=["POST"])
def signup():
# Create an instance of the registration form
form = SignupForm()
# Process the form only if validation succeeds
if form.validate_on_submit():
# Check whether the username already exists
existing = users_table.get(UserQuery.username == form.username.data)
if existing:
# Username is already taken
flash("Username already exists.")
return render_template(
"partials/auth_box.html",
active_tab="signup",
form_template="partials/signup_form.html",
form=form
)
# Save the new user with a hashed password
users_table.insert({
"username": form.username.data,
"email": form.email.data,
"password_hash": generate_password_hash(form.password.data)
})
# Registration successful
flash("Account created successfully! Please sign in.")
# Display the Sign In form
return render_template(
"partials/auth_box.html",
active_tab="signin",
form_template="partials/signin_form.html",
form=SigninForm()
)
# Form validation failed
return render_template(
"partials/auth_box.html",
active_tab="signup",
form_template="partials/signup_form.html",
form=form
)The registration flow
Signup route (app.py)
(i) Create Signup Form Instance- Creates an instance of the
SignupFormclass. - This object contains the username, email, password fields, CSRF token, and validation rules.
- It allows Flask-WTF to process and validate the submitted data.
(ii) Validate Submitted Form
- Checks whether:
- The request method is
POST. - All form fields pass validation rules.
- The CSRF token is valid.
- The request method is
- If validation succeeds, the registration process continues.
- If validation fails, the user remains on the Sign Up form.
- Searches the TinyDB users_table to check whether the submitted username already exists.
- form.username.data retrieves the username entered by the user.
- UserQuery.username is used by TinyDB to perform the database search.
(iv) Handle Duplicate Username
If the username is already registered:
- Creates a Flask flash message.
- Informs the user that they cannot use the same username.
- UserQuery.username is used by TinyDB to perform the database search.
- auth_box.html acts as the wrapper containing the tab navigation and form area.
- active_tab="signup" highlights the Sign Up tab.
- form_template="partials/signup_form.html" tells Jinja to display the sign-up form.
- form=form passes the existing form object back so validation errors can be displayed
- Inserts a new user record into TinyDB.
- Stores:
- Username entered by the user.
- Email address entered by the user.
- Hashed password.
The password is not stored as plain text. This improves security because the original password cannot easily be recovered.
(vi) Display Success Message
- Creates a success notification.
- Informs the user that registration has completed.
- Guides the user to log in using the newly created account.
(vii) Switch to Sign In Form
After successful registration:
- The authentication card is refreshed.
- The active tab changes from Sign Up to Sign In.
- A new empty
SigninForm()is created. - The user can immediately enter their username and password.
(viii) Handle Validation Failure
This handles cases such as the following:
- Username is empty.
- Email is empty.
- Password is empty.
- CSRF validation fails.
The user stays on the Sign Up tab, and the entered data is preserved where possible.
Step 5: Authentication of existing user
The underlying logic
@app.route('/signin', methods=['GET', 'POST'])
def signin():
# Create an instance of the Sign In form
form = SigninForm()
# Validate the submitted form data
if form.validate_on_submit():
# Search for the user in TinyDB using the entered username
user_data = users_table.get(
UserQuery.username == form.username.data
)
# Check whether the user exists and verify the password hash
if user_data and check_password_hash(
user_data["password_hash"],
form.password.data
):
# Create a logged-in user session using Flask-Login
login_user(User(user_data))
# Use HTMX redirect to navigate to the dashboard page
response = make_response("")
response.headers["HX-Redirect"] = url_for("dashboard")
return response
else:
# Display an error message if username or password is incorrect
flash("Invalid username or password.")
# Reload the authentication box while keeping the Sign In tab active
return render_template(
"partials/auth_box.html",
active_tab="signin",
form_template="partials/signin_form.html",
form=form
)
# Display the Sign In form when the page is first loaded
# or when form validation fails
return render_template(
"partials/signin_form.html",
form=form
)
@app.route('/dashboard', methods=['GET', 'POST'])
@login_required
def dashboard():
# Create the Sign Out form
# This form sends a POST request to the signout route
form = SignoutForm()
# Render the protected dashboard page
# Only authenticated users can access this page
return render_template(
'dashboard.html',
form=form
)
@app.route('/signout', methods=['POST'])
@login_required
def signout():
# Remove the user's session and log them out
logout_user()
# Redirect the user back to the home page
return redirect(url_for("home"))
Sign In Route (app.py)
(i) @app.route('/signin', methods=['GET', 'POST'])
-
Defines the
/signin route.
-
Accepts both GET and POST HTTP requests.
-
A GET request displays the Sign In form, while a POST request processes the submitted login credentials.
/signin route.
(ii) def signin():
-
Defines the view function that handles all requests made to the
/signin route.
-
It is responsible for displaying the Sign In form, validating user credentials, and creating a login session.
/signin route.
(iii) Create the sign-in form.
- Creates an instance of the SigninForm class.
- The form contains the username field, password field, submit button, validation rules, and CSRF protection.
- Checks whether:
- The request method is POST.
- All required fields have been completed.
- The CSRF token is valid.
- If validation succeeds, the authentication process continues.
- Otherwise, the Sign In form is redisplayed.
(v) Retrieve the User from TinyDB
- Searches the TinyDB users_table for a user whose username matches the one entered in the form.
- form.username.data retrieves the username submitted by the user.
- If a matching record is found, it is stored in user_data.
(vi) Verify the Password
- First checks whether the user exists.
- If the user exists, check_password_hash() compares the stored hashed password with the password entered by the user.
- The user is authenticated only if both the username and password are correct.
(vii) Create a Login Session
- Creates a login session using Flask-Login.
-
Converts the TinyDB record into a
Userobject. - Stores the user's ID in the session so that Flask-Login can recognise the user on subsequent requests.
-
After this step, the user can access routes protected by
@login_required.
(viii) Redirect Using HTMX (If password matches - a full reload to dashboard page)
- Creates an empty HTTP response.
- Adds the HX-Redirect response header, instructing HTMX to redirect the browser to the dashboard page.
- This approach enables navigation without requiring a traditional HTTP redirect.
(ix) Handle Invalid Credentials (If password mismatch)
- Creates a flash message if authentication fails.
- The message is displayed to the user using the Bootstrap alert component in the Sign In form.
(x) Reload the Sign In Form
- Redisplays the authentication card.
- Keeps the Sign In tab active.
- Reloads the Sign In form with the flash message visible.
(xi) Display the Initial Sign-In Form
- Handles the initial GET request to
/signin. - Renders the Sign In form when the page is first loaded or when form validation fails before authentication is attempted.
Dashboard Route (app.py)
(i) @app.route('/dashboard', methods=['GET', 'POST'])
-
Defines the
/dashboardroute. - Accepts both GET and POST HTTP requests.
- This route displays the dashboard page after a user has successfully signed in.
(ii) @login_required
- Protects the dashboard route so that only authenticated users can access it.
-
If an unauthenticated user attempts to access
/dashboard, Flask-Login automatically redirects them to the login page
(iii) form = SignoutForm()
-
Creates an instance of the
SignoutForm. - The form contains the Sign Out button and a hidden CSRF token to protect the logout request.
(iv) return render_template('dashboard.html', form=form)
-
Renders the
dashboard.htmltemplate. -
Passes the
SignoutFormobject to the template so that Jinja can render the Sign Out button.
Sign Out Route (app.py)
(i) @app.route('/signout', methods=['POST'])
- Defines the
/signoutroute. - Accepts only POST requests.
- Using POST for logout is more secure than using GET because it prevents accidental logouts through bookmarked URLs or hyperlinks.
(ii) @login_required
- Ensures that only authenticated users can access the sign-out route.
- Prevents unauthenticated users from sending logout requests.
(iii) logout_user()
- Ends the current user's authenticated session.
- Removes the user's login information stored by Flask-Login.
- After calling this function, the user is no longer authenticated.
(iv) return redirect(url_for("home"))
- Redirects the user to the home page after successfully signing out.
url_for("home")generates the URL for thehome()route dynamically.
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-warning alert-dismissible
fade show" role="alert">
{{ message }}
<button type="button" class="btn-close"
data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
{% extends 'base.html' %}
{% block title %}Dashboard{% endblock %}
{% block content %}
<div class="container d-flex justify-content-center
align-items-center min-vh-100"</p>
<div class="card shadow-lg border-0 w-100"
style="max-width: 500px; border-radius:15px;</p>
<div class="card-header bg-primary text-white text-center py-3">
<h3 class="mb-0">Dashboard</h3>
</div>
<div class="card-body p-4 text-center">
<h5 class="mb-3">Welcome 🎉</h5>
<p class="text-muted mb-4">You have successfully logged in.</p>
<form method="POST" action="{{ url_for('signout') }}">
{{ form.hidden_tag() }}
{{ form.submit(class="btn btn-danger w-100 py-2 fw-semibold") }}
</form>
</div>
<div class="card-footer text-muted text-center small">
Flask + Bootstrap Dashboard
</div>
</div>
</div>
{% endblock %}(i) Flash Messages- Retrieves flash messages created by Flask using the
flash()function. - Displays messages such as successful login or logout using Bootstrap's dismissible alert component.
- container provides responsive horizontal padding.
- d-flex enables Flexbox.
- justify-content-center centers the dashboard horizontally.
- align-items-center centers the dashboard vertically.
- min-vh-100 makes the container occupy at least the full height of the browser viewport.
- Creates a Bootstrap card to display the dashboard content.
-
shadow-lgadds a large shadow. -
border-0removes the default border. -
w-100allows the card to use the available width of its container.
- Displays the dashboard title.
- bg-primary applies Bootstrap's primary background colour.
- text-white changes the text colour to white.
- text-center centers the heading.
- py-3 adds vertical padding.
- Contains the main dashboard content.
- Displays a welcome message after successful authentication.
- p-4 adds padding around the content.
- text-center centers the text.
- Creates the logout form.
- Submits a POST request to the /signout route.
- Uses url_for() to generate the logout URL dynamically.
- Renders the hidden CSRF token to protect against Cross-Site Request Forgery (CSRF) attacks.
- Renders a Bootstrap-styled Sign Out button.
- btn-danger gives the button a red appearance.
- w-100 makes the button span the full width of the card.
- py-2 adds vertical padding.
- fw-semibold applies a semi-bold font weight.
@app.route('/privacy')
def privacy():
return render_template('privacy.html')
- Defines the
/privacyroute. - Renders the privacy.html template.
- Returns the completed HTML page to the user's browser.
- Since the Privacy page contains only static information, no additional data needs to be passed to the template.
privacy.html
{% extends 'base.html' %}
{% block title %}Privacy{% endblock %}
{% block content %}
<div class="container d-flex justify-content-center align-items-center min-vh-100">
<div class="card shadow-lg border-0 w-100" style="max-width: 600px;
border-radius: 16px;">
<div class="card-header bg-primary text-white text-center py-3">
<h1 class="h3 mb-0">Privacy</h1>
</div>
<div class="card-body p-4 text-center text-muted">
<p>Lorem ipsum dolor sit amet consectetur adipiscing elit.
Quisque faucibus ex sapien vitae pellentesque sem placerat.
In id cursus mi pretium tellus duis convallis.
Tempus leo eu aenean sed diam urna tempor.
Pulvinar vivamus fringilla lacus nec metus bibendum egestas.
Iaculis massa nisl malesuada lacinia integer nunc posuere.
Ut hendrerit semper vel class aptent taciti sociosqu.
Ad litora torquent per conubia nostra inceptos himenaeos.</p>
<p class="mb-0">Lorem ipsum dolor sit amet consectetur adipiscing elit.
Quisque faucibus ex sapien vitae pellentesque sem placerat.
In id cursus mi pretium tellus duis convallis.
Tempus leo eu aenean sed diam urna tempor.
Pulvinar vivamus fringilla lacus nec metus bibendum egestas.
Iaculis massa nisl malesuada lacinia integer nunc posuere.
Ut hendrerit semper vel class aptent taciti sociosqu.
Ad litora torquent per conubia nostra inceptos himenaeos.</p>
</div>
<div class="card-footer bg-transparent border-0 d-flex
justify-content-center pb-4">
<a href="{{ url_for('home') }}" class="btn btn-success px-4 py-2
fw-semibold">Back to Home</a>
</div>
</div>
</div>
{% endblock %}
Privacy Template (privacy.html)
Bootstrap classes used here are:
- container creates a responsive container that automatically adjusts its width based on the screen size.
- d-flex enables the Flexbox layout.
- justify-content-center horizontally centers the privacy card.
- align-items-center vertically centers the privacy card.
- min-vh-100 ensures the container occupies at least the full height of the browser viewport.
(ii) Privacy Card
Bootstrap/custom styles used here are:
- card creates a Bootstrap card to display the Privacy Policy.
- shadow-lg applies a large shadow to give the card a raised appearance.
- border-0 removes the default card border.
- w-100 allows the card to occupy the full available width of its container.
- max-width: 600px limits the maximum width for improved readability.
- border-radius: 16px rounds the card corners to create a modern appearance.
(iii) Card Body
Bootstrap classes used here are:
- card-body contains the main Privacy Policy content.
- p-4 adds padding around the text for better readability.
- text-center centers the text within the card.
- text-muted applies a lighter text colour to improve visual appearance.
(iii) Card Footer
Bootstrap classes used here are:
- card-footer creates the footer section of the card.
- bg-transparent removes the default footer background colour.
- border-0 removes the footer border.
- d-flex enables Flexbox.
- justify-content-center centers the button horizontally.
- pb-4 adds bottom padding to create spacing below the button.
(ix) Back to Home Button
Bootstrap classes used here are:
- btn styles the hyperlink as a Bootstrap button.
- btn-success applies the Bootstrap success (green) colour scheme.
- px-4 adds horizontal padding to increase the button width.
- py-2 adds vertical padding to improve the button height.
- fw-semibold displays the button text with a semi-bold font weight.
---------------------------------------------------------------------------------------------------------------------------------------------------









Comments