Extracting data from jinja for a dashboard
Source: Dev.to
Understanding Data Flow in a Flask‑Jinja Application
A key feature of modern web applications is the ability to retrieve data from a backend system and display it dynamically on a webpage. In a Flask application, this process involves interaction between three main components:
- Database – e.g., MySQL, where the data lives.
- Flask backend – extracts data and passes it to the template.
- Jinja template engine – renders the data into HTML.
1. Retrieving Data in Flask
When a user accesses a specific route, Flask executes the associated view function. Inside that function a database query is performed, e.g.:
@app.route('/products')
def products():
# Query the database for all product records
product_rows = db.session.execute(
text("SELECT id, name, price, description FROM products")
).fetchall()
# Convert rows to a list of dictionaries (optional but convenient)
products = [dict(row) for row in product_rows]
return render_template('products.html', products=products)- The query transforms stored rows into a Python‑friendly format (list, tuple, dict).
- This list is then supplied to the template as a variable (
products).
2. Passing Variables to the Template
Flask’s render_template lets you inject any number of variables into a Jinja template:
return render_template('dashboard.html',
user=current_user,
orders=order_list,
stats=summary_stats)Each variable receives a name that the template will use to reference the data, creating a clear bridge between backend and frontend.
3. Rendering with Jinja
Jinja processes the variables and produces HTML. Common patterns include:
a. Loops
{% for product in products %}
- {{ product.name }} – ${{ product.price }}
{% endfor %}
Loops let you display collections without repetitive markup.
b. Conditionals
{% if user.is_authenticated %}
Welcome, {{ user.name }}!
{% else %}
[Log in]({{ url_for('login') }})
{% endif %}Conditionals enable dynamic UI changes based on the data.
c. Accessing Data Structures
- Dictionaries:
{{ product['name'] }}or{{ product.name }} - Tuples/Lists:
{{ product[0] }}(index‑based)
A well‑structured dataset makes the template cleaner and easier to maintain.
4. Separation of Concerns
- Flask – handles data retrieval, business logic, and preparation of context variables.
- Jinja – focuses solely on presentation.
This division allows you to modify the UI without touching data‑access code, and vice‑versa, improving maintainability and scalability.
5. Security
Jinja automatically escapes HTML‑unsafe characters, protecting against XSS. Nevertheless, you should:
- Only pass the data the template truly needs.
- Validate and sanitize any user‑generated content before storing it in the database.
- Use
|safesparingly and only when you’re certain the content is trustworthy.
6. Real‑Time Updates
Whenever the underlying database changes (e.g., a new product is added), the next page load will fetch the latest data and render it, keeping the UI in sync with the backend.
7. Summary
- Extract data from the database in a Flask view.
- Pass the data as named variables to a Jinja template.
- Render the variables with loops, conditionals, and proper data‑structure access.
This workflow is the backbone of full‑stack Flask development and enables interactive, data‑driven web applications.
Example Jinja Template (Producer Dashboard)
{% extends 'base.html' %}
{% block title %}Producer Dashboard{% endblock %}
{% block extra_style %}
.dashboard {
padding: 2rem 6%;
}
.header {
margin-bottom: 2rem;
}
.header h1 {
font-size: 1.8rem;
color: var(--gd);
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 1.5rem;
}
/* CARDS */
.card {
background: var(--w);
border-radius: 14px;
padding: 1.4rem;
box-shadow: 0 6px 18px rgba(0,0,0,.08);
}
.card h2 {
margin-bottom: 1rem;
font-size: 1.2rem;
}
/* TABLE */
.table {
width: 100%;
border-collapse: collapse;
font-size: .85rem;
}
.table th {
text-align: left;
padding: .5rem;
border-bottom: 2px solid var(--b);
}
.table td {
padding: .5rem;
border-bottom: 1px solid var(--b);
}
/* BADGES */
.badge {
padding: .25rem .6rem;
border-radius: 20px;
font-size: .75rem;
font-weight: 700;
}
.pending { background:#ffe7b3; }
.completed { background:#c8f7d4; }
.cancelled { background:#ffd2d2; }
/* INPUT */
.input {
width: 70px;
padding: .3rem;
border: 1px solid var(--b);
border-radius: 6px;
}
/* SEARCH */
.search {
margin-bottom: 1rem;
padding: .5rem;
width: 100%;
border-radius: 8px;
border: 1px solid var(--b);
}
/* BUTTON SMALL */
.btn-small {
padding: .3rem .7rem;
font-size: .75rem;
border-radius: 6px;
background: var(--g);
color: white;
border: none;
cursor: pointer;
}
{% endblock %}
{% block content %}
{% endblock %}The template retains the original structure and styling while being presented in a clean, readable markdown format.
## Welcome back, {{ producer_name }} 👋
Manage your products, stock, and orders
## Your Products
Name
Price
Available
{% for p in products %}
{{ p[1] }}
£{{ p[2] }}
{{ 'Yes' if p[4] else 'No' }}
{% endfor %}
## Stock Management
Product
Qty
Update
{% for s in stock %}
{{ s[0] }}
{{ s[1] }}
Save
{% endfor %}
## Orders
ID
Customer
Product
Qty
Status
Update
{% for o in orders %}
#{{ o[0] }}
{{ o[1] }}
{{ o[2] }}
{{ o[3] }}
{{ o[4] }}
Pending
Completed
Cancelled
Update
{% endfor %}
/* SIMPLE TABLE FILTER */
function filterTable(input, tableId) {
const filter = input.value.toLowerCase();
const rows = document.querySelectorAll(`#${tableId} tbody tr`);
rows.forEach(row => {
const text = row.innerText.toLowerCase();
row.style.display = text.includes(filter) ? '' : 'none';
});
}
{% endblock %}