03. HTML Templates
Now that we have a working application, we can start creating our templates. Actually we will start writing html
codes for desinging the layout of our page. We will be using bootstrap
for styling our templates.
By default Flask server serve all the html
files in the templates
folder and all the css
and js
files in the static
folder. So let's create those two folders along with a index.html
file in the templates
folder.
Layout
grocery-app/
├── .env
├── application
└── file1.py
...
├── instance
└── project.sqlite3
├── static
└── assets
└── image1.jpg
...
└── custom.js
├── templates
└── base.html
...
├── .gitignore
├── local_run.
├── setup_run.
├── main.py
├── README.md
├── .requirements.txt
Creating Boilerplate
html filename=templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Home</title>
</head>
<body>
<div>{{ msg }}</div>
</body>
</html>
Adding Bootstrap CSS and JS
We will be using CDN links for Bootstrap CSS and JS.
html filename=templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Home</title>
<link href="https:/cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" /> <!-- [tl! add:start] -->
<script src="https:/cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script> <!-- [tl! add:end] -->
</head>
<body>
<div>{{ msg }}</div>
</body>
</html>
Basic Design
We create a simple desing of the page. At the top we flash messages, then we have main content and at the bottom I will have all my navigation buttons or links.
For Navigation button or links I will use Google Material Iconsfonts.google.com/icons
. Again to add icons we will use CDN links.
html filename=templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{% endblock %} - My Webpage</title>
<link href="https:/cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Nabla&display=swap" rel="stylesheet">
<script src="https:/cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<style>
/* Custom CSS for the left sidebar */
.material-symbols-outlined {
font-variation-settings:
'FILL' 0,
'wght' 400,
'GRAD' 0,
'opsz' 200
}
</style>
</head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class=flashes>
{% for message in messages %}
<div class="alert alert-info alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% if query_by_word %}
<h2>Search result for {{query_by_word}}</h2>
{% endif %}
<div class="container d-flex justify-content-center mt-2" style="margin-bottom: 100px;">
<div class="row gap-2">
{% block content %}{% endblock %}
</div>
</div>
<nav class="navbar navbar-expand-lg bg-body-tertiary fixed-bottom">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
{% block button %}{% endblock %}
</div>
</nav>
{% block extra %}{% endblock %}
</body>
</html>
Note Here I am not only using Google Material Icons, but also I am using Nabla
fonts.google.com/specimen/Nabla
font. You can style your font as well.
I have also created different blocks for different parts of the page. We will write them in different html files which will extend this base html file.
Bootstrap Card
To show category and products we will use cards from bootstrap. You can find more information heregetbootstrap.com/docs/5.3/components/card/
.
So we will update the main content of the index.html
file with these lines of codes.
html filename=templates/index.html
...
<div class="container d-flex justify-content-center mt-2" style="margin-bottom: 100px;">
<div class="row gap-2"> <!-- [tl! add:start] -->
{% if items|length >0 %}
{% for item in items %}
<!-- card -->
{% if item.view_type=='card' %}
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 18rem;">
<img src="{{url_for('static', filename='assets/' + item.img_name)}}" class="card-img-top img-thumbnail" alt="...">
<div class="card-body">
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">{{item.description}}</p>
{% if item.quantity > 0 %}
<a href="/login">Add to Cart</a>
{% else %}
<button class="btn btn-danger" disabled >Out of Stock</button>
{% endif %}
</div>
</div>
<!-- category -->
{% elif item.view_type=='category' %}
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 18rem;">
<div class="card-body">
<!-- <img src="readymade.webp" class="d-block w-100 img-thumbnail" alt="..."> -->
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<a href="{{url_for('index', query_by_category=item.id)}}" class="btn btn-danger">Show more</a>
</div>
</div>
<!-- wider_card -->
{% elif item.view_type=='wider_card' %}
<div class="card w-50 shadow p-3 mb-5 bg-body-tertiary rounded">
<div class="card-body">
<img src="{{url_for('static', filename='assets/' + item.img_name)}}" class="card-img-top img-thumbnail" alt="...">
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">{{item.description}}</p>
{% if item.quantity > 0 %}
<a href="/login">Add to Cart</a>
{% else %}
<button class="btn btn-danger" disabled >Out of Stock</button>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
{% else %}
<h1>Nothing there</h1>
{% endif %}
</div> <!-- [tl! add:end] -->
</div>
...
From the above cards section you can see that I am using card and wider card to have two different type of look of the products. You can keep it same.
Note To adjust the width of the cards we are using inline css. That is
style="width: 18rem;"
andstyle="width: 50rem;"
in thewider_card
card.
Welcome Page
We will create a welcome page or landing page for application in index.html
file. You can find the code below.
html filename=templates/index.html
{% extends "base.html" %} {% block title %}Welcome{% endblock %} {% block
content %} {% if items|length >0 %} {% for item in items %}
<!-- card -->
{% if item.view_type=='card' %}
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 18rem">
<img
src="{{url_for('static', filename='assets/' + item.img_name)}}"
class="card-img-top img-thumbnail"
alt="..."
/>
<div class="card-body">
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">{{item.description}}</p>
{% if item.quantity > 0 %}
<a href="/login">Add to Cart</a>
{% else %}
<button class="btn btn-danger" disabled>Out of Stock</button>
{% endif %}
</div>
</div>
<!-- category -->
{% elif item.view_type=='category' %}
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 18rem">
<div class="card-body">
<!-- <img src="readymade.webp" class="d-block w-100 img-thumbnail" alt="..."> -->
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">
With supporting text below as a natural lead-in to additional content.
</p>
<a
href="{{url_for('index', query_by_category=item.id)}}"
class="btn btn-danger"
>Show more</a
>
</div>
</div>
<!-- wider_card -->
{% elif item.view_type=='wider_card' %}
<div class="card w-50 shadow p-3 mb-5 bg-body-tertiary rounded">
<div class="card-body">
<img
src="{{url_for('static', filename='assets/' + item.img_name)}}"
class="card-img-top img-thumbnail"
alt="..."
/>
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">{{item.description}}</p>
{% if item.quantity > 0 %}
<a href="/login">Add to Cart</a>
{% else %}
<button class="btn btn-danger" disabled>Out of Stock</button>
{% endif %}
</div>
</div>
{% endif %} {% endfor %} {% else %}
<h1>Nothing there</h1>
{% endif %} {% endblock %} {% block button %}
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class="col">
<h4 style="font-family: 'Nabla', cursive">GroceIt</h4>
</div>
<div class="col">
<a href="{{url_for('index')}}">
<span class="material-symbols-outlined"> home </span>
</a>
<p class="fw-bold">Home</p>
</div>
<div class="col">
<a href="/login">
<span class="material-symbols-outlined"> login </span>
</a>
<p class="fw-bold">Login</p>
</div>
<div class="col">
<a href="/register">
<span class="material-symbols-outlined"> login </span>
</a>
<p class="fw-bold">Register</p>
</div>
<div class="col">
<a href="/login">
<span class="material-symbols-outlined"> shopping_bag </span>
</a>
<p class="fw-bold">Your cart</p>
</div>
<div class="col">
<a
style="cursor: pointer"
data-bs-toggle="modal"
data-bs-target="#searchModal"
>
<span class="material-symbols-outlined"> search </span>
</a>
<p class="fw-bold">search</p>
</div>
<div class="col">
{% if not query_by_category %}
<a
style="cursor: pointer"
data-bs-toggle="modal"
data-bs-target="#searchByCategoryModal"
>
<span class="material-symbols-outlined"> category </span>
</a>
<p class="fw-bold">See Categories</p>
{% else %}
<p class="fw-bold">Only: {{query_by_category}}</p>
{% endif %}
</div>
</div>
{% endblock %}
{% block extra %}
<div class="row">
<!-- Modal -->
<div
class="modal fade"
id="searchModal"
tabindex="-1"
aria-labelledby="searchModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="searchModalLabel">Searching for</h1>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="GET" action="{{url_for('index')}}" class="mt-4">
<div class="form-group">
<label for="name">Enter and click on the search button</label>
<input
type="text"
class="form-control"
id="name"
name="query_by_word"
placeholder="Enter category name"
required
/>
</div>
<button type="submit" class="btn btn-primary">search</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div
class="modal fade"
id="searchByCategoryModal"
tabindex="-1"
aria-labelledby="searchByCategoryModalLabel"
aria-hidden="true"
>
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="searchByCategoryModalLabel">
Select Category
</h1>
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
{% for category in categories %}
<a
href="{{url_for('index', query_by_category=category.id)}}"
class="btn btn-outline-info"
aria-current="true"
>
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{category.name}}</h5>
<small>3 days ago</small>
</div>
<p class="mb-1">Some placeholder content in a paragraph.</p>
<small>And some small print.</small>
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
Note I am using jinja2 template inheritance. You can find the complete setup in the flask
flask.palletsprojects.com/en/2.2.x/quickstart/#template-inheritance
website.
I am using bootstrap modal for the search feature. You can find more information heregetbootstrap.com/docs/5.3/components/modal/
.
Manager Dashboard
We will have the same manager dashboard and we will be using bootstrap modal for any data/form submission from the manager. For example CRUD for categories and products.
html filename=templates/manager.html
{% extends "base.html" %}
{% block title %}Manager{% endblock %}
{% block content %}
{% if items|length >0 %}
{% for item in items %}
<!-- card -->
{% if item.view_type=='card' %}
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 18rem;">
<img src="{{url_for('static', filename='assets/' + item.img_name)}}" class="card-img-top img-thumbnail" alt="...">
<div class="card-body">
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">{{item.description}}</p>
<div class="d-flex">
<a data-bs-toggle="modal" data-bs-target="#productUpdateModal" class="btn btn-warning">update</a>
<a data-bs-toggle="modal" data-bs-target="#confirmModal" class="btn btn-danger">delete</a>
</div>
</div>
</div>
<div class="modal fade" id="confirmModal" tabindex="-1" aria-labelledby="confirmModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="confirmModalLabel">Deleting Product</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<p>Are you sure to delete {{item.name}}</p>
<a href="{{url_for('add_product_delete', id=item.id)}}" class="btn btn-danger">Confirm</a>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="productUpdateModal" tabindex="-1" aria-labelledby="productUpdateModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="productUpdateModalLabel">Update Product</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="POST" action="{{url_for('add_product_update', id=item.id)}}" class="mt-4">
<div class="form-group">
<label for="name">Product Name:</label>
<input type="text" class="form-control" id="name" name="name" value="{{item.name}}"
required>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea class="form-control" id="description" name="description"
placeholder="{{item.description}}"></textarea>
</div>
<div class="form-group">
<label for="mgf_date">mgf date:</label>
<input type="date" class="form-control" id="mgf_date" name="mgf_date"
value="{{item.mgf_date}}"></input>
</div>
<div class="form-group">
<label for="exp_date">exp date:</label>
<input type="date" class="form-control" id="exp_date" name="exp_date"
value="{{item.exp_date}}"></input>
</div>
<div class="form-group">
<label for="price">Price:</label>
<input type="number" class="form-control" id="price" name="price" value="{{item.price}}"
step="0.01" min="0" required>
</div>
<div class="form-group">
<label for="quantity">quantity:</label>
<input type="number" class="form-control" id="quantity" name="quantity"
value="{{item.quantity}}" min="1" required>
</div>
<div class="form-group">
<label for="myfile">Select a imgae file:</label>
<input class="form-control" type="file" id="myfile" name="myfile" value="{{item.img_name}}">
</div>
<div class="form-group">
<label for="view_type">View:</label>
<select class="form-control" id="view_type" name="view_type">
<option selected>{{item.view_type}}</option>
<option value="card">card</option>
<option value="wider_card">wider card</option>
</select>
</div>
<div class="form-group">
<label for="category_id">Category:</label>
<select class="form-control" id="category_id" name="category_id">
{% for category in categories %}
<option value="{{category.id}}">{{category.name}}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Update Product</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- category -->
{% elif item.view_type=='category' %}
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 18rem;">
<div class="card-body">
<!-- <img src="readymade.webp" class="d-block w-100 img-thumbnail" alt="..."> -->
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<div class="d-flex">
<a data-bs-toggle="modal" data-bs-target="#categoryUpdateModal" class="btn btn-warning">update</a>
<a data-bs-toggle="modal" data-bs-target="#confirmModal1" class="btn btn-danger">delete</a>
</div>
</div>
</div>
<div class="modal fade" id="confirmModal1" tabindex="-1" aria-labelledby="confirmModal1Label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="confirmModal1Label">Deleting Category</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<p>Are you sure to delete {{item.name}}</p>
<a href="{{url_for('add_category_delete', id=item.id)}}" class="btn btn-danger">Confirm</a>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="categoryUpdateModal" tabindex="-1" aria-labelledby="categoryUpdateModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="categoryUpdateModalLabel">Update Category</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="POST" action="{{url_for('add_category_update', id=item.id)}}" class="mt-4">
<div class="form-group">
<label for="name">Category Name:</label>
<input type="text" class="form-control" id="name" name="name" value="{{ item.name }}"
required>
</div>
<button type="submit" class="btn btn-primary">Update Category</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- wider_card -->
{% elif item.view_type=='wider_card' %}
<div class="card w-50 shadow p-3 mb-5 bg-body-tertiary rounded">
<div class="card-body">
<img src="{{url_for('static', filename='assets/' + item.img_name)}}" class="card-img-top img-thumbnail"
alt="...">
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">{{item.description}}</p>
<div class="d-flex">
<a data-bs-toggle="modal" data-bs-target="#productUpdateModal" class="btn btn-warning">update</a>
<a data-bs-toggle="modal" data-bs-target="#confirmModal2" class="btn btn-danger">delete</a>
</div>
</div>
</div>
<div class="modal fade" id="confirmModal2" tabindex="-1" aria-labelledby="confirmModal2Label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="confirmModal2Label">Deleting Product</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<p>Are you sure to delete {{item.name}}</p>
<a href="{{url_for('add_product_delete', id=item.id)}}" class="btn btn-danger">Confirm</a>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="productUpdateModal" tabindex="-1" aria-labelledby="productUpdateModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="productUpdateModalLabel">Update Product</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="POST" action="{{url_for('add_product_update', id=item.id)}}" class="mt-4">
<div class="form-group">
<label for="name">Product Name:</label>
<input type="text" class="form-control" id="name" name="name" value="{{item.name}}"
required>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea class="form-control" id="description" name="description"
placeholder="{{item.description}}"></textarea>
</div>
<div class="form-group">
<label for="mgf_date">mgf date:</label>
<input type="date" class="form-control" id="mgf_date" name="mgf_date"
value="{{item.mgf_date}}"></input>
</div>
<div class="form-group">
<label for="exp_date">exp date:</label>
<input type="date" class="form-control" id="exp_date" name="exp_date"
value="{{item.exp_date}}"></input>
</div>
<div class="form-group">
<label for="price">Price:</label>
<input type="number" class="form-control" id="price" name="price" value="{{item.price}}"
step="0.01" min="0" required>
</div>
<div class="form-group">
<label for="quantity">quantity:</label>
<input type="number" class="form-control" id="quantity" name="quantity"
value="{{item.quantity}}" min="1" required>
</div>
<div class="form-group">
<label for="myfile">Select a imgae file:</label>
<input class="form-control" type="file" id="myfile" name="myfile" value="{{item.img_name}}">
</div>
<div class="form-group">
<label for="view_type">View:</label>
<select class="form-control" id="view_type" name="view_type">
<option selected>{{item.view_type}}</option>
<option value="card">card</option>
<option value="wider_card">wider card</option>
</select>
</div>
<div class="form-group">
<label for="category_id">Category:</label>
<select class="form-control" id="category_id" name="category_id">
{% for category in categories %}
<option value="{{category.id}}">{{category.name}}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Update Product</button>
</form>
</div>
</div>
</div>
</div>
</div>
{% endif %}
{% endfor %}
{% else %}
<h1>Nothing there</h1>
{% endif %}
{% endblock %}
{% block button %}
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class="col">
<h4 style="font-family: 'Nabla', cursive;">GroceIt</h4>
</div>
<div class="col">
<a href="{{url_for('dashboard_admin')}}">
<span class="material-symbols-outlined">
home
</span>
</a>
<p class="fw-bold">Home</p>
</div>
<div class="col">
<a style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#productCreateModal">
<span class="material-symbols-outlined">
add_circle
</span>
</a>
<p class="fw-bold">Add Product</p>
</div>
<div class="col">
<a style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#categoryCreateModal">
<span class="material-symbols-outlined">
add_circle
</span>
</a>
<p class="fw-bold">Add Category</p>
</div>
<div class="col">
<a style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#searchModal">
<span class="material-symbols-outlined">
search
</span>
</a>
<p class="fw-bold">search</p>
</div>
<div class="col">
{% if not query_by_category %}
<a style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#searchByCategoryModal">
<span class="material-symbols-outlined">
category
</span>
</a>
<p class="fw-bold">See Categories</p>
{% else %}
<p class="fw-bold">Only: {{query_by_category}}</p>
{% endif %}
</div>
<div class="col">
<a href="/logout">
<span class="material-symbols-outlined">
logout
</span>
</a>
<p class="fw-bold">Logout</p>
</div>
</div>
{% endblock %}
{% block extra %}
<div class="row">
<!-- Modal -->
<div class="modal fade" id="searchModal" tabindex="-1" aria-labelledby="searchModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="searchModalLabel">Searching for</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="GET" action="{{url_for('dashboard_admin')}}" class="mt-4">
<div class="form-group">
<label for="name">Enter and click on the search button</label>
<input type="text" class="form-control" id="name" name="query_by_word"
placeholder="Enter category name" required>
</div>
<button type="submit" class="btn btn-primary">search</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="searchByCategoryModal" tabindex="-1" aria-labelledby="searchByCategoryModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="searchByCategoryModalLabel">Select Category</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
{% for category in categories %}
<a href="{{url_for('dashboard_admin', query_by_category=category.id)}}"
class="btn btn-outline-info" aria-current="true">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{category.name}}</h5>
<small>3 days ago</small>
</div>
<p class="mb-1">Some placeholder content in a paragraph.</p>
<small>And some small print.</small>
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="categoryCreateModal" tabindex="-1" aria-labelledby="categoryCreateModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="categoryCreateModalLabel">Create Category</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="POST" action="{{url_for('add_category')}}" class="mt-4">
<div class="form-group">
<label for="name">Category Name:</label>
<input type="text" class="form-control" id="name" name="name"
placeholder="Enter category name" required>
</div>
<button type="submit" class="btn btn-primary">Create Category</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="productCreateModal" tabindex="-1" aria-labelledby="productCreateModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="productCreateModalLabel">Add Product</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="POST" action="{{url_for('add_product')}}" class="mt-4">
<div class="form-group">
<label for="name">Product Name:</label>
<input type="text" class="form-control" id="name" name="name"
placeholder="Enter product name" required>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea class="form-control" id="description" name="description"
placeholder="Enter product description"></textarea>
</div>
<div class="form-group">
<label for="mgf_date">mgf date:</label>
<input type="date" class="form-control" id="mgf_date" name="mgf_date"
placeholder="Enter product mgf_date"></input>
</div>
<div class="form-group">
<label for="exp_date">exp date:</label>
<input type="date" class="form-control" id="exp_date" name="exp_date"
placeholder="Enter product exp_date"></input>
</div>
<div class="form-group">
<label for="price">Price:</label>
<input type="number" class="form-control" id="price" name="price"
placeholder="Enter product price" step="0.01" min="0" required>
</div>
<div class="form-group">
<label for="quantity">quantity:</label>
<input type="number" class="form-control" id="quantity" name="quantity"
placeholder="Enter product price" min="1" required>
</div>
<div class="form-group">
<label for="myfile">Select a imgae file:</label>
<input class="form-control" type="file" id="myfile" name="myfile" required>
</div>
<div class="form-group">
<label for="view_type">View:</label>
<select class="form-control" id="view_type" name="view_type" required>
<option value="card">card</option>
<option value="wider_card">wider card</option>
</select>
</div>
<div class="form-group">
<label for="category_id">Category:</label>
<select class="form-control" id="category_id" name="category_id" required>
{% for category in categories %}
<option value="{{category.id}}">{{category.name}}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Create Product</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
Note I am using jinja2 if-else, for loop etc for the dynamic content.
User Dashboard
Again we will have same approach for user dashboard desinging.
html filename=templates/user.html
{% extends "base.html" %}
{% block title %}User{% endblock %}
{% block content %}
{% if items|length >0 %}
{% for item in items %}
<!-- card -->
{% if item.view_type=='card' %}
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 18rem;">
<img src="{{url_for('static', filename='assets/' + item.img_name)}}" class="card-img-top img-thumbnail" alt="...">
<div class="card-body">
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">{{item.description}}</p>
{% if item.quantity > 0 %}
<a style="cursor: pointer;" href="{{url_for('add_to_cart', id=item.id)}}" class="btn btn-outline-success">Add to
Cart</a>
{% else %}
<button class="btn btn-danger" disabled>Out of Stock</button>
{% endif %}
</div>
</div>
<div class="modal fade" id="productUpdateModal" tabindex="-1" aria-labelledby="productUpdateModalLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="productUpdateModalLabel">Update Product</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="POST" action="{{url_for('add_product_update', id=item.id)}}" class="mt-4">
<div class="form-group">
<label for="name">Product Name:</label>
<input type="text" class="form-control" id="name" name="name" value="{{item.name}}"
required>
</div>
<div class="form-group">
<label for="description">Description:</label>
<textarea class="form-control" id="description" name="description"
placeholder="{{item.description}}"></textarea>
</div>
<div class="form-group">
<label for="mgf_date">mgf date:</label>
<input type="date" class="form-control" id="mgf_date" name="mgf_date"
value="{{item.mgf_date}}"></input>
</div>
<div class="form-group">
<label for="exp_date">exp date:</label>
<input type="date" class="form-control" id="exp_date" name="exp_date"
value="{{item.exp_date}}"></input>
</div>
<div class="form-group">
<label for="price">Price:</label>
<input type="number" class="form-control" id="price" name="price" value="{{item.price}}"
step="0.01" min="0" required>
</div>
<div class="form-group">
<label for="myfile">Select a imgae file:</label>
<input class="form-control" type="file" id="myfile" name="myfile" value="{{item.img_name}}">
</div>
<div class="form-group">
<label for="view_type">View:</label>
<select class="form-control" id="view_type" name="view_type">
<option selected>{{item.view_type}}</option>
<option value="card">card</option>
<option value="wider_card">wider card</option>
</select>
</div>
<div class="form-group">
<label for="category_id">Category:</label>
<select class="form-control" id="category_id" name="category_id">
{% for category in categories %}
<option value="{{category.id}}">{{category.name}}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary">Update Product</button>
</form>
</div>
</div>
</div>
</div>
</div>
<!-- category -->
{% elif item.view_type=='category' %}
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 18rem;">
<div class="card-body">
<!-- <img src="readymade.webp" class="d-block w-100 img-thumbnail" alt="..."> -->
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">With supporting text below as a natural lead-in to additional content.</p>
<a href="{{url_for('dashboard_user', query_by_category=item.id)}}" class="btn btn-danger">Show more</a>
</div>
</div>
<!-- wider_card -->
{% elif item.view_type=='wider_card' %}
<div class="card w-50 shadow p-3 mb-5 bg-body-tertiary rounded">
<div class="card-body">
<img src="{{url_for('static', filename='assets/' + item.img_name)}}" class="card-img-top img-thumbnail"
alt="...">
<h5 class="card-title">{{item.name}}</h5>
<p class="card-text">{{item.description}}</p>
{% if item.quantity > 0 %}
<a style="cursor: pointer;" href="{{url_for('add_to_cart', id=item.id)}}" class="btn btn-outline-success">Add to
Cart</a>
{% else %}
<button class="btn btn-danger" disabled>Out of Stock</button>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
{% else %}
<h1>Nothing there</h1>
{% endif %}
{% endblock %}
{% block button %}
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<div class="col">
<h4 style="font-family: 'Nabla', cursive;">GroceIt</h4>
</div>
<div class="col">
<a href="{{url_for('dashboard_user')}}">
<span class="material-symbols-outlined">
home
</span>
</a>
<p class="fw-bold">Home</p>
</div>
<div class="col">
<a style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#userCartModal">
<span class="material-symbols-outlined">
shopping_bag
</span>
{% if current_user.cart|length > 0 %}
<span>
{{ current_user.cart|length }}
</span>
{% endif %}
</a>
<p class="fw-bold">Your cart</p>
</div>
<div class="col">
<a style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#searchModal">
<span class="material-symbols-outlined">
search
</span>
</a>
<p class="fw-bold">search</p>
</div>
<div class="col">
{% if not query_by_category %}
<a style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#searchByCategoryModal">
<span class="material-symbols-outlined">
category
</span>
</a>
<p class="fw-bold">See Categories</p>
{% else %}
<p class="fw-bold">Only: {{query_by_category}}</p>
{% endif %}
</div>
<div class="col">
<a style="cursor: pointer;" data-bs-toggle="modal" data-bs-target="#userProfileModal">
<span class="material-symbols-outlined">
person
</span>
</a>
<p class="fw-bold">Profile</p>
</div>
<div class="col">
<a href="/logout">
<span class="material-symbols-outlined">
logout
</span>
</a>
<p class="fw-bold">logout</p>
</div>
</div>
{% endblock %}
{% block extra %}
<div class="row">
<!-- Modal -->
<div class="modal fade" id="searchModal" tabindex="-1" aria-labelledby="searchModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="searchModalLabel">Searching for</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
<form method="GET" action="{{url_for('dashboard_user')}}" class="mt-4">
<div class="form-group">
<label for="name">Enter and click on the search button</label>
<input type="text" class="form-control" id="name" name="query_by_word"
placeholder="Enter category name" required>
</div>
<button type="submit" class="btn btn-primary">search</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="searchByCategoryModal" tabindex="-1" aria-labelledby="searchByCategoryModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="searchByCategoryModalLabel">Select Category</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body d-flex justify-content-center">
<div class="card-body">
{% for category in categories %}
<a href="{{url_for('dashboard_user', query_by_category=category.id)}}"
class="btn btn-outline-info" aria-current="true">
<div class="d-flex w-100 justify-content-between">
<h5 class="mb-1">{{category.name}}</h5>
<small>3 days ago</small>
</div>
<p class="mb-1">Some placeholder content in a paragraph.</p>
<small>And some small print.</small>
</a>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="userCartModal" tabindex="-1" aria-labelledby="userCartModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="userCartModalLabel">Your Cart</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="container d-flex justify-content-center mt-2" style="margin-bottom: 100px;">
<div class="row gap-2">
{% if current_user.cart|length >0 %}
{% for item in current_user.cart %}
<!-- card -->
<div class="card shadow p-3 mb-5 bg-body-tertiary rounded" style="width: 12rem;">
<img src="{{url_for('static', filename='assets/' + item.img_name)}}"
class="card-img-top img-thumbnail" alt="...">
<div class="card-body">
<p class="card-title">Name: {{item.name}}</p>
<p class="card-text">Price: {{item.price}}</p>
{% for userproduct in userproducts %}
{% if userproduct.product_id==item.id %}
<p class="card-text">Quantity: {{userproduct.quantity}}</p>
<p class="card-text">Amount: {{ userproduct.quantity * item.price }}</p>
{% endif %}
{% endfor %}
<a style="cursor: pointer;" href="{{url_for('remove_to_cart', id=item.id)}}"
class="btn btn-outline-dark">Remove</a>
</div>
</div>
{% endfor %}
{% else %}
<h1>There is nothing in your cart</h1>
{% endif %}
</div>
</div>
</div>
{% if current_user.cart|length >0 %}
<div class="modal-footer">
<button type="button" class="btn btn-secondary" disabled>{{ current_user.total_value }}</button>
<a href="{{url_for('user_payment')}}" class="btn btn-primary">Make payment</a>
</div>
{% endif %}
</div>
</div>
</div>
<div class="modal fade" id="userProfileModal" tabindex="-1" aria-labelledby="userProfileModalLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="userProfileModalLabel">User Profile</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="container">
<div class="row mb-1">
<div class="card">
<div class="card-header">
Basic Information
</div>
<div class="card-body">
<p><strong>Name:</strong> {{ current_user.username }}</p>
<p><strong>Email:</strong> {{ current_user.email }}</p>
</div>
</div>
</div>
<div class="row">
<div class="card">
<div class="card-header">
Order History
</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>Order ID</th>
<th>name</th>
<th>Total Amount</th>
<th>Order Date</th>
</tr>
</thead>
<tbody>
{% for order in current_user.orders %}
<tr>
<td>{{ order.id }}</td>
<td>{{ order.name }}</td>
<td>{{ order.amount }}</td>
<td>{{ order.order_date }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
Now you can complete your templates and your project.
Let's commit the changes to our local git repository.
git add .
git commit -m "created flask server"