Login and User Registration using Flask

Overview

This Flask application is a simple user authentication system using Flask, Flask-WTF, WTForms, MySQL, and bcrypt for password hashing. The application allows users to register, log in, and access a dashboard page after successful login.

Features:

  • User Registration: Allows users to sign up with their name, email, and password.
  • Login: Users can log in with their email and password.
  • Dashboard: Authenticated users can access a personal dashboard.
  • Logout: Users can log out and end their session.

Requirements:

Before running the application, you need to install the necessary dependencies.

  1. Install Python 3.x (if not already installed).
  2. Installing and setting Flask.
    • python -m venv vevn
    • .\venv\Scripts\activate (these is to activate your virtual environment, if error occur you can manually open them usind cd command in the terminal)
    • Select python interpretor (if you are using VScose à select view à command palette à python select interpreter à enter interpreter path à. à \your_project\venv\Scripts\python.exe)
    • python -m pip install –upgrade pip (if needed to upgrade)
    • python -m pip install flask (to install flask)
    • create new app.py
    • python -m flask –app .\app.py run OR python -m flask run (for running the flask application)
  3. Install the following packages using pip:
    • pip install Flask
    • pip install Flask-WTF
    • pip install flask-mysqldb
    • pip install bcrypt

Ensure that MySQL is installed and running on your local machine, and you have to created a database called mydatabase with a table users containing the following fields:

				
					CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100),
    email VARCHAR(100) UNIQUE,
    password VARCHAR(255));

				
			

Application Structure

app.py

This is the main Python file that contains the Flask application and route definitions.

These file contains the below code

				
					from flask import Flask, render_template, redirect, url_for, session, flash
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField
from wtforms.validators import DataRequired, Email, ValidationError
import bcrypt
from flask_mysqldb import MySQL

app = Flask(__name__)

# MySQL Configuration
app.config['MYSQL_HOST'] = 'localhost'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = ''
app.config['MYSQL_DB'] = 'mydatabase'
app.secret_key = 'your_secret_key_here'

mysql = MySQL(app)

class RegisterForm(FlaskForm):
    name = StringField("Name",validators=[DataRequired()])
    email = StringField("Email",validators=[DataRequired(), Email()])
    password = PasswordField("Password",validators=[DataRequired()])
    submit = SubmitField("Register")

    def validate_email(self,field):
        cursor = mysql.connection.cursor()
        cursor.execute("SELECT * FROM users where email=%s",(field.data,))
        user = cursor.fetchone()
        cursor.close()
        if user:
            raise ValidationError('Email Already Taken')

class LoginForm(FlaskForm):
    email = StringField("Email",validators=[DataRequired(), Email()])
    password = PasswordField("Password",validators=[DataRequired()])
    submit = SubmitField("Login")


@app.route('/')
def index():
    return render_template('index.html')

@app.route('/register',methods=['GET','POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        name = form.name.data
        email = form.email.data
        password = form.password.data

        hashed_password = bcrypt.hashpw(password.encode('utf-8'),bcrypt.gensalt())

        # store data into database 
        cursor = mysql.connection.cursor()
        cursor.execute("INSERT INTO users (name,email,password) VALUES (%s,%s,%s)",(name,email,hashed_password))
        mysql.connection.commit()
        cursor.close()

        return redirect(url_for('login'))

    return render_template('register.html',form=form)

@app.route('/login',methods=['GET','POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        email = form.email.data
        password = form.password.data

        cursor = mysql.connection.cursor()
        cursor.execute("SELECT * FROM users WHERE email=%s",(email,))
        user = cursor.fetchone()
        cursor.close()
        if user and bcrypt.checkpw(password.encode('utf-8'), user[3].encode('utf-8')):
            session['user_id'] = user[0]
            return redirect(url_for('dashboard'))
        else:
            flash("Login failed. Please check your email and password")
            return redirect(url_for('login'))

    return render_template('login.html',form=form)

@app.route('/dashboard')
def dashboard():
    if 'user_id' in session:
        user_id = session['user_id']

        cursor = mysql.connection.cursor()
        cursor.execute("SELECT * FROM users where id=%s",(user_id,))
        user = cursor.fetchone()
        cursor.close()

        if user:
            return render_template('dashboard.html',user=user)
            
    return redirect(url_for('login'))

@app.route('/logout')
def logout():
    session.pop('user_id', None)
    flash("You have been logged out successfully.")
    return redirect(url_for('login'))

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

				
			

Detailed Explanation for above code-

1. Flask Configuration

				
					app = Flask(__name__)

# MySQL Configuration
app.config['MYSQL_HOST'] = 'localhost'
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = ''
app.config['MYSQL_DB'] = 'mydatabase'
app.secret_key = 'your_secret_key_here'

mysql = MySQL(app)

				
			
  1. Flask Configuration: Initializes a Flask application with a secret key used for session management.
  2. MySQL Configuration: Configures the app to connect to a MySQL database (mydatabase) using the flask_mysqldb extension.

2. User Registration Form (RegisterForm)

				
					class RegisterForm(FlaskForm):
    name = StringField("Name", validators=[DataRequired()])
    email = StringField("Email", validators=[DataRequired(), Email()])
    password = PasswordField("Password", validators=[DataRequired()])
    submit = SubmitField("Register")
    
    def validate_email(self, field):
        cursor = mysql.connection.cursor()
        cursor.execute("SELECT * FROM users where email=%s", (field.data,))
        user = cursor.fetchone()
        cursor.close()
        if user:
            raise ValidationError('Email Already Taken')

				
			
  1. Form Fields:
    • name: Required field for the user’s name.
    • email: Required field for the user’s email with email validation.
    • password: Required field for the user’s password.
    • submit: Submit button to submit the registration form.
  2. Email Validation: Checks if the provided email already exists in the database. If the email is taken, a validation error is raised.

3. User Login Form (LoginForm)

				
					class LoginForm(FlaskForm):
    email = StringField("Email", validators=[DataRequired(), Email()])
    password = PasswordField("Password", validators=[DataRequired()])
    submit = SubmitField("Login")

				
			

Form Fields:

  • email: Required field for the user’s email with email validation.
  • password: Required field for the user’s password.
  • submit: Submit button to submit the login form.

4. Routes

4.1. Home Route (/)
				
					@app.route('/')
def index():
    return render_template('index.html')

				
			

Form Fields:

  • Purpose: Renders the home page (index.html), which can be a simple welcome page or landing page.
4.2. Registration Route (/register)
				
					@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        name = form.name.data
        email = form.email.data
        password = form.password.data

        # Hash password
        hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt())

        # Store data in MySQL
        cursor = mysql.connection.cursor()
        cursor.execute("INSERT INTO users (name, email, password) VALUES (%s, %s, %s)", (name, email, hashed_password))
        mysql.connection.commit()
        cursor.close()

        return redirect(url_for('login'))

    return render_template('register.html', form=form)

				
			

Form Fields:

  • Purpose: Renders the registration form. On successful form submission, it hashes the password using bcrypt and stores the user’s name, email, and hashed password in the users table in the MySQL database.
  • Redirect: After successful registration, it redirects to the login page.
4.3. Login Route (/login)
				
					@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        email = form.email.data
        password = form.password.data

        # Fetch user from database
        cursor = mysql.connection.cursor()
        cursor.execute("SELECT * FROM users WHERE email=%s", (email,))
        user = cursor.fetchone()
        cursor.close()

        # Check if user exists and password matches
        if user and bcrypt.checkpw(password.encode('utf-8'), user[3].encode('utf-8')):
            session['user_id'] = user[0]
            return redirect(url_for('dashboard'))
        else:
            flash("Login failed. Please check your email and password")
            return redirect(url_for('login'))

    return render_template('login.html', form=form)

				
			
  1. Purpose: Renders the login form. On successful form submission, it checks if the user exists and if the provided password matches the hashed password stored in the database.
  2. Session Management: If login is successful, the user’s ID is stored in the session, and they are redirected to the dashboard page.
4.4. Dashboard Route (/dashboard)
				
					@app.route('/dashboard')
def dashboard():
    if 'user_id' in session:
        user_id = session['user_id']

        cursor = mysql.connection.cursor()
        cursor.execute("SELECT * FROM users WHERE id=%s", (user_id,))
        user = cursor.fetchone()
        cursor.close()

        if user:
            return render_template('dashboard.html', user=user)

    return redirect(url_for('login'))

				
			

Purpose: Displays the dashboard for authenticated users. If the user is not logged in (i.e., user_id not in session), they are redirected to the login page.

4.5. Logout Route (/logout)
				
					@app.route('/logout')
def logout():
    session.pop('user_id', None)
    flash("You have been logged out successfully.")
    return redirect(url_for('login'))

				
			

Purpose: Logs out the user by removing their user_id from the session and then redirects them to the login page with a flash message indicating successful logout.

Running the Application

To run the application, execute the following command in your terminal:

				
					python app.py
				
			

This will start a local development server, and you can visit the application in your browser at :

http://127.0.0.1:5000/

Notes

  1. Password Hashing: The passwords are securely hashed using bcrypt before being stored in the database. This ensures that plain-text passwords are not stored.
  2. Session Management: The session object is used to manage user authentication. When a user logs in, their user_id is saved in the session. On logout, the session is cleared.

\templates-

The templates in this Flask application provide the structure for the user interface, using Bootstrap for styling and Flask-WTF for handling forms and validation. The app.html template serves as the base layout, while the other templates (index.html, login.html, register.html, and dashboard.html) extend it to display specific content for the home page, login, registration, and user dashboard respectively. Flash messages and form validation are included for better user experience and interaction.

1. app.html – Base Template

File Location: /templates/app.html

Purpose:

This is the base HTML layout that other templates will extend. It contains the common structure and includes external resources like Bootstrap for styling, and jQuery and Popper.js for Bootstrap’s interactive features. The {% block content %} is used as a placeholder where child templates can inject their specific content.

Structure:
				
					<!DOCTYPE html>
<html lang="en">
<head>
  <title>Flask MySQL</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"> <script type="litespeed/javascript" data-src="https://cdn.jsdelivr.net/npm/jquery@3.6.4/dist/jquery.slim.min.js"></script> <script type="litespeed/javascript" data-src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"></script> <script type="litespeed/javascript" data-src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.bundle.min.js"></script> </head>
<body>

  
  {% block content %} 
  {% endblock %} <script data-no-optimize="1">!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).LazyLoad=e()}(this,function(){"use strict";function e(){return(e=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n,a=arguments[e];for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(t[n]=a[n])}return t}).apply(this,arguments)}function i(t){return e({},it,t)}function o(t,e){var n,a="LazyLoad::Initialized",i=new t(e);try{n=new CustomEvent(a,{detail:{instance:i}})}catch(t){(n=document.createEvent("CustomEvent")).initCustomEvent(a,!1,!1,{instance:i})}window.dispatchEvent(n)}function l(t,e){return t.getAttribute(gt+e)}function c(t){return l(t,bt)}function s(t,e){return function(t,e,n){e=gt+e;null!==n?t.setAttribute(e,n):t.removeAttribute(e)}(t,bt,e)}function r(t){return s(t,null),0}function u(t){return null===c(t)}function d(t){return c(t)===vt}function f(t,e,n,a){t&&(void 0===a?void 0===n?t(e):t(e,n):t(e,n,a))}function _(t,e){nt?t.classList.add(e):t.className+=(t.className?" ":"")+e}function v(t,e){nt?t.classList.remove(e):t.className=t.className.replace(new RegExp("(^|\\s+)"+e+"(\\s+|$)")," ").replace(/^\s+/,"").replace(/\s+$/,"")}function g(t){return t.llTempImage}function b(t,e){!e||(e=e._observer)&&e.unobserve(t)}function p(t,e){t&&(t.loadingCount+=e)}function h(t,e){t&&(t.toLoadCount=e)}function n(t){for(var e,n=[],a=0;e=t.children[a];a+=1)"SOURCE"===e.tagName&&n.push(e);return n}function m(t,e){(t=t.parentNode)&&"PICTURE"===t.tagName&&n(t).forEach(e)}function a(t,e){n(t).forEach(e)}function E(t){return!!t[st]}function I(t){return t[st]}function y(t){return delete t[st]}function A(e,t){var n;E(e)||(n={},t.forEach(function(t){n[t]=e.getAttribute(t)}),e[st]=n)}function k(a,t){var i;E(a)&&(i=I(a),t.forEach(function(t){var e,n;e=a,(t=i[n=t])?e.setAttribute(n,t):e.removeAttribute(n)}))}function L(t,e,n){_(t,e.class_loading),s(t,ut),n&&(p(n,1),f(e.callback_loading,t,n))}function w(t,e,n){n&&t.setAttribute(e,n)}function x(t,e){w(t,ct,l(t,e.data_sizes)),w(t,rt,l(t,e.data_srcset)),w(t,ot,l(t,e.data_src))}function O(t,e,n){var a=l(t,e.data_bg_multi),i=l(t,e.data_bg_multi_hidpi);(a=at&&i?i:a)&&(t.style.backgroundImage=a,n=n,_(t=t,(e=e).class_applied),s(t,ft),n&&(e.unobserve_completed&&b(t,e),f(e.callback_applied,t,n)))}function N(t,e){!e||0<e.loadingCount||0<e.toLoadCount||f(t.callback_finish,e)}function C(t,e,n){t.addEventListener(e,n),t.llEvLisnrs[e]=n}function M(t){return!!t.llEvLisnrs}function z(t){if(M(t)){var e,n,a=t.llEvLisnrs;for(e in a){var i=a[e];n=e,i=i,t.removeEventListener(n,i)}delete t.llEvLisnrs}}function R(t,e,n){var a;delete t.llTempImage,p(n,-1),(a=n)&&--a.toLoadCount,v(t,e.class_loading),e.unobserve_completed&&b(t,n)}function T(o,r,c){var l=g(o)||o;M(l)||function(t,e,n){M(t)||(t.llEvLisnrs={});var a="VIDEO"===t.tagName?"loadeddata":"load";C(t,a,e),C(t,"error",n)}(l,function(t){var e,n,a,i;n=r,a=c,i=d(e=o),R(e,n,a),_(e,n.class_loaded),s(e,dt),f(n.callback_loaded,e,a),i||N(n,a),z(l)},function(t){var e,n,a,i;n=r,a=c,i=d(e=o),R(e,n,a),_(e,n.class_error),s(e,_t),f(n.callback_error,e,a),i||N(n,a),z(l)})}function G(t,e,n){var a,i,o,r,c;t.llTempImage=document.createElement("IMG"),T(t,e,n),E(c=t)||(c[st]={backgroundImage:c.style.backgroundImage}),o=n,r=l(a=t,(i=e).data_bg),c=l(a,i.data_bg_hidpi),(r=at&&c?c:r)&&(a.style.backgroundImage='url("'.concat(r,'")'),g(a).setAttribute(ot,r),L(a,i,o)),O(t,e,n)}function D(t,e,n){var a;T(t,e,n),a=e,e=n,(t=It[(n=t).tagName])&&(t(n,a),L(n,a,e))}function V(t,e,n){var a;a=t,(-1<yt.indexOf(a.tagName)?D:G)(t,e,n)}function F(t,e,n){var a;t.setAttribute("loading","lazy"),T(t,e,n),a=e,(e=It[(n=t).tagName])&&e(n,a),s(t,vt)}function j(t){t.removeAttribute(ot),t.removeAttribute(rt),t.removeAttribute(ct)}function P(t){m(t,function(t){k(t,Et)}),k(t,Et)}function S(t){var e;(e=At[t.tagName])?e(t):E(e=t)&&(t=I(e),e.style.backgroundImage=t.backgroundImage)}function U(t,e){var n;S(t),n=e,u(e=t)||d(e)||(v(e,n.class_entered),v(e,n.class_exited),v(e,n.class_applied),v(e,n.class_loading),v(e,n.class_loaded),v(e,n.class_error)),r(t),y(t)}function $(t,e,n,a){var i;n.cancel_on_exit&&(c(t)!==ut||"IMG"===t.tagName&&(z(t),m(i=t,function(t){j(t)}),j(i),P(t),v(t,n.class_loading),p(a,-1),r(t),f(n.callback_cancel,t,e,a)))}function q(t,e,n,a){var i,o,r=(o=t,0<=pt.indexOf(c(o)));s(t,"entered"),_(t,n.class_entered),v(t,n.class_exited),i=t,o=a,n.unobserve_entered&&b(i,o),f(n.callback_enter,t,e,a),r||V(t,n,a)}function H(t){return t.use_native&&"loading"in HTMLImageElement.prototype}function B(t,i,o){t.forEach(function(t){return(a=t).isIntersecting||0<a.intersectionRatio?q(t.target,t,i,o):(e=t.target,n=t,a=i,t=o,void(u(e)||(_(e,a.class_exited),$(e,n,a,t),f(a.callback_exit,e,n,t))));var e,n,a})}function J(e,n){var t;et&&!H(e)&&(n._observer=new IntersectionObserver(function(t){B(t,e,n)},{root:(t=e).container===document?null:t.container,rootMargin:t.thresholds||t.threshold+"px"}))}function K(t){return Array.prototype.slice.call(t)}function Q(t){return t.container.querySelectorAll(t.elements_selector)}function W(t){return c(t)===_t}function X(t,e){return e=t||Q(e),K(e).filter(u)}function Y(e,t){var n;(n=Q(e),K(n).filter(W)).forEach(function(t){v(t,e.class_error),r(t)}),t.update()}function t(t,e){var n,a,t=i(t);this._settings=t,this.loadingCount=0,J(t,this),n=t,a=this,Z&&window.addEventListener("online",function(){Y(n,a)}),this.update(e)}var Z="undefined"!=typeof window,tt=Z&&!("onscroll"in window)||"undefined"!=typeof navigator&&/(gle|ing|ro)bot|crawl|spider/i.test(navigator.userAgent),et=Z&&"IntersectionObserver"in window,nt=Z&&"classList"in document.createElement("p"),at=Z&&1<window.devicePixelRatio,it={elements_selector:".lazy",container:tt||Z?document:null,threshold:300,thresholds:null,data_src:"src",data_srcset:"srcset",data_sizes:"sizes",data_bg:"bg",data_bg_hidpi:"bg-hidpi",data_bg_multi:"bg-multi",data_bg_multi_hidpi:"bg-multi-hidpi",data_poster:"poster",class_applied:"applied",class_loading:"litespeed-loading",class_loaded:"litespeed-loaded",class_error:"error",class_entered:"entered",class_exited:"exited",unobserve_completed:!0,unobserve_entered:!1,cancel_on_exit:!0,callback_enter:null,callback_exit:null,callback_applied:null,callback_loading:null,callback_loaded:null,callback_error:null,callback_finish:null,callback_cancel:null,use_native:!1},ot="src",rt="srcset",ct="sizes",lt="poster",st="llOriginalAttrs",ut="loading",dt="loaded",ft="applied",_t="error",vt="native",gt="data-",bt="ll-status",pt=[ut,dt,ft,_t],ht=[ot],mt=[ot,lt],Et=[ot,rt,ct],It={IMG:function(t,e){m(t,function(t){A(t,Et),x(t,e)}),A(t,Et),x(t,e)},IFRAME:function(t,e){A(t,ht),w(t,ot,l(t,e.data_src))},VIDEO:function(t,e){a(t,function(t){A(t,ht),w(t,ot,l(t,e.data_src))}),A(t,mt),w(t,lt,l(t,e.data_poster)),w(t,ot,l(t,e.data_src)),t.load()}},yt=["IMG","IFRAME","VIDEO"],At={IMG:P,IFRAME:function(t){k(t,ht)},VIDEO:function(t){a(t,function(t){k(t,ht)}),k(t,mt),t.load()}},kt=["IMG","IFRAME","VIDEO"];return t.prototype={update:function(t){var e,n,a,i=this._settings,o=X(t,i);{if(h(this,o.length),!tt&&et)return H(i)?(e=i,n=this,o.forEach(function(t){-1!==kt.indexOf(t.tagName)&&F(t,e,n)}),void h(n,0)):(t=this._observer,i=o,t.disconnect(),a=t,void i.forEach(function(t){a.observe(t)}));this.loadAll(o)}},destroy:function(){this._observer&&this._observer.disconnect(),Q(this._settings).forEach(function(t){y(t)}),delete this._observer,delete this._settings,delete this.loadingCount,delete this.toLoadCount},loadAll:function(t){var e=this,n=this._settings;X(t,n).forEach(function(t){b(t,e),V(t,n,e)})},restoreAll:function(){var e=this._settings;Q(e).forEach(function(t){U(t,e)})}},t.load=function(t,e){e=i(e);V(t,e)},t.resetStatus=function(t){r(t)},Z&&function(t,e){if(e)if(e.length)for(var n,a=0;n=e[a];a+=1)o(t,n);else o(t,e)}(t,window.lazyLoadOptions),t});!function(e,t){"use strict";function a(){t.body.classList.add("litespeed_lazyloaded")}function n(){console.log("[LiteSpeed] Start Lazy Load Images"),d=new LazyLoad({elements_selector:"[data-lazyloaded]",callback_finish:a}),o=function(){d.update()},e.MutationObserver&&new MutationObserver(o).observe(t.documentElement,{childList:!0,subtree:!0,attributes:!0})}var d,o;e.addEventListener?e.addEventListener("load",n,!1):e.attachEvent("onload",n)}(window,document);</script><script data-no-optimize="1">var litespeed_vary=document.cookie.replace(/(?:(?:^|.*;\s*)_lscache_vary\s*\=\s*([^;]*).*$)|^.*$/,"");litespeed_vary||fetch("/wp-content/plugins/litespeed-cache/guest.vary.php",{method:"POST",cache:"no-cache",redirect:"follow"}).then(e=>e.json()).then(e=>{console.log(e),e.hasOwnProperty("reload")&&"yes"==e.reload&&(sessionStorage.setItem("litespeed_docref",document.referrer),window.location.reload(!0))});</script><script data-optimized="1" type="litespeed/javascript" data-src="https://techamplifiers.com/wp-content/litespeed/js/be529c62fb16cbfcc597ca661eac5183.js?ver=a31ce"></script><script>const litespeed_ui_events=["mouseover","click","keydown","wheel","touchmove","touchstart"];var urlCreator=window.URL||window.webkitURL;function litespeed_load_delayed_js_force(){console.log("[LiteSpeed] Start Load JS Delayed"),litespeed_ui_events.forEach(e=>{window.removeEventListener(e,litespeed_load_delayed_js_force,{passive:!0})}),document.querySelectorAll("iframe[data-litespeed-src]").forEach(e=>{e.setAttribute("src",e.getAttribute("data-litespeed-src"))}),"loading"==document.readyState?window.addEventListener("DOMContentLoaded",litespeed_load_delayed_js):litespeed_load_delayed_js()}litespeed_ui_events.forEach(e=>{window.addEventListener(e,litespeed_load_delayed_js_force,{passive:!0})});async function litespeed_load_delayed_js(){let t=[];for(var d in document.querySelectorAll('script[type="litespeed/javascript"]').forEach(e=>{t.push(e)}),t)await new Promise(e=>litespeed_load_one(t[d],e));document.dispatchEvent(new Event("DOMContentLiteSpeedLoaded")),window.dispatchEvent(new Event("DOMContentLiteSpeedLoaded"))}function litespeed_load_one(t,e){console.log("[LiteSpeed] Load ",t);var d=document.createElement("script");d.addEventListener("load",e),d.addEventListener("error",e),t.getAttributeNames().forEach(e=>{"type"!=e&&d.setAttribute("data-src"==e?"src":e,t.getAttribute(e))});let a=!(d.type="text/javascript");!d.src&&t.textContent&&(d.src=litespeed_inline2src(t.textContent),a=!0),t.after(d),t.remove(),a&&e()}function litespeed_inline2src(t){try{var d=urlCreator.createObjectURL(new Blob([t.replace(/^(?:<!--)?(.*?)(?:-->)?$/gm,"$1")],{type:"text/javascript"}))}catch(e){d="data:text/javascript;base64,"+btoa(t.replace(/^(?:<!--)?(.*?)(?:-->)?$/gm,"$1"))}return d}</script></body>
</html>

				
			
Key Points:
  1. Bootstrap Integration: Bootstrap’s CSS and JS are included through CDN links for responsive layout and UI components.
  2. {% block content %}: This is where the content from child templates will be inserted.
  3. Reusable Structure: Other templates can extend this base and only focus on the unique content for each page.
2. dashboard.html – Dashboard Page

File Location: /templates/dashboard.html

Purpose:

This template is used to display the user’s dashboard after a successful login. It provides the user’s information (like name and email) and includes a “Logout” button. If the user is not logged in, the page will not render the user information.

Structure:
				
					{% extends 'app.html' %}

{% block content %}
  <div class="container">
    <h2>Welcome to the Dashboard</h2>

    {% if user %}
      <p>User Information:</p>
      <ul>
        <li>Name: {{ user[1] }}</li>
        <li>Email: {{ user[2] }}</li>
      </ul>
      <a href="/logout" class="btn btn-dark">Logout</a>
    {% endif %}
  </div>
{% endblock %}

				
			
Key Points:
  1. Extends app.html: Inherits the layout and structure from app.html.
  2. Conditional Rendering: The user information is only displayed if a valid user object is passed to the template.
  3. User Details: The user’s name and email are accessed via user[1] and user[2] respectively (assuming the query returns the user in a tuple with id, name, and email).
  4. Logout Link: Provides a button that logs the user out by redirecting them to /logout.
3. index.html – Home Page

File Location: /templates/index.html

Purpose:

This template represents the home page of the application. It provides links to navigate to the login page and possibly other parts of the app.

Structure:
				
					{% extends 'app.html' %}

{% block content %}
  <h2>Home Page</h2>
  <a href="/login" class="btn btn-secondary mt-4">Go to Login Page</a>
{% endblock %}

				
			
Key Points:
  1. Extends app.html: Inherits the common layout from the base template.
  2. Simple Welcome Page: Displays a heading and a link to the login page (/login).
  3. Navigation: Offers a straightforward navigation path for users who have not logged in yet.
4. login.html – Login Form Page

File Location: /templates/login.html

Purpose:

This template contains the login form. It allows users to log in by providing their email and password. It also handles form validation errors and displays any flashed messages (like invalid credentials or form errors).

Structure:
				
					{% extends 'app.html' %}

{% block content %}
  <div class="container">
    <h2>Login Form</h2>

    
    {% with messages = get_flashed_messages() %}
      {% if messages %}
        <div class="alert alert-danger">
          <ul>
            {% for message in messages %}
              <li>{{ message }}</li>
            {% endfor %}
          </ul>
        </div>
      {% endif %}
    {% endwith %}

    
    <form action="/login" method="POST">
      {{ form.hidden_tag() }}
      
      <div class="form-group">
        {{ form.email.label(for="email") }}
        {{ form.email(id="email", class="form-control") }}
        {% if form.email.errors %}
          <ul>
            {% for error in form.email.errors %}
              <li>{{ error }}</li>
            {% endfor %}
          </ul>
        {% endif %}
      </div>

      <div class="form-group">
        {{ form.password.label(for="password") }}
        {{ form.password(id="password", class="form-control") }}
        {% if form.password.errors %}
          <ul>
            {% for error in form.password.errors %}
              <li>{{ error }}</li>
            {% endfor %}
          </ul>
        {% endif %}
      </div>

      {{ form.submit(class="btn btn-dark mt-4") }}
      <a href="/register" class="btn btn-secondary mt-4">Register Page</a>
    </form>
  </div>
{% endblock %}

				
			
Key Points:
  1. Flash Messages: Displays any error messages (like invalid email or password) using Flask’s get_flashed_messages() function.
  2. Form Handling: The form is rendered using WTForms and includes proper validation. Errors are shown next to each form field if they occur.
  3. Form Submission: On successful form submission, the form posts back to /login for authentication.
5. register.html – Registration Form Page

File Location: /templates/register.html

Purpose:

This template provides the registration form where users can sign up by entering their name, email, and password. Like the login form, this template also includes validation and error handling.

				
					{% extends 'app.html' %}

{% block content %}
  <div class="container">
    <h2>Register Form</h2>
    
    <form action="/register" method="POST">
      {{ form.hidden_tag() }}
      
      <div class="form-group">
        {{ form.name.label(for="name") }}
        {{ form.name(id="name", class="form-control") }}
        {% if form.name.errors %}
          <ul class="text-danger">
            {% for error in form.name.errors %}
              <li>{{ error }}</li>
            {% endfor %}
          </ul>
        {% endif %}
      </div>

      <div class="form-group">
        {{ form.email.label(for="email") }}
        {{ form.email(id="email", class="form-control") }}
        {% if form.email.errors %}
          <ul class="text-danger">
            {% for error in form.email.errors %}
              <li>{{ error }}</li>
            {% endfor %}
          </ul>
        {% endif %}
      </div>

      <div class="form-group">
        {{ form.password.label(for="password") }}
        {{ form.password(id="password", class="form-control") }}
        {% if form.password.errors %}
          <ul class="text-danger">
            {% for error in form.password.errors %}
              <li>{{ error }}</li>
            {% endfor %}
          </ul>
        {% endif %}
      </div>

      {{ form.submit(class="btn btn-dark mt-4") }}
      <a href="/login" class="btn btn-secondary mt-4">Login Page</a>
    </form>
  </div>
{% endblock %}

				
			
Key Points:
  1. Form Fields: Similar to the login form, this template includes fields for name, email, and password.
  2. Error Handling: If any validation errors occur (such as invalid email or password format), they are displayed below the corresponding input fields.
  3. Form Submission: The form submits to /register for processing.

General Notes for All Templates:

  1. Bootstrap Classes: All templates utilize Bootstrap’s grid system and utility classes for styling and responsive design.
  2. form.hidden_tag(): This is used to include any hidden CSRF token required for form submissions, as part of Flask-WTF security.
  3. Messages & Error Handling: Flash messages and validation errors are displayed to ensure a good user experience.
Tech Amplifier Final Logo