Adding cleaner layout, summaries, charts, and Excel download to your web report
Chapter Goal
From Web Demo to Practical Reporting App
Chapter 5 gave the report a browser form. That was a major step because users no longer needed to run commands.
But the app still felt basic.
In this chapter, we improve the Flask app so it starts feeling like something you could share with another developer
or demo to a business user.
FormUser selects filters
SummaryShow record counts
TableDisplay filtered data
ChartVisual pattern
DownloadExport Excel
Big idea: A useful tool does not only show data. It helps the user understand, trust, and reuse the result.
What We Are Building
We will keep the same material report, but add four practical improvements:
π
Summary Cards
Total filtered records, total stock, and selected filters.
π
Simple Chart
A visual count by material type or plant.
β¬οΈ
Excel Download
A browser button to download filtered results.
Expected browser preview
Material Report App
Generate ReportDownload Excel
2Filtered Records
112Total Stock
FERT / 1000Selected Filters
Material
Description
Type
Plant
Stock
1006
Demo Finished Product 3
FERT
1000
67
1001
Demo Finished Product
FERT
1000
45
Updated Project Structure
We will add a folder for generated reports and a static CSS file. This keeps the app cleaner.
think-like-python-flask-report/
β
βββ app.py
βββ materials.csv
βββ requirements.txt
β
βββ generated/
β βββ filtered_materials.xlsx β created by the app
β
βββ static/
β βββ style.css
β
βββ templates/
βββ index.html
ABAP comparison: This is like moving from one giant report include into a cleaner structure:
program logic, layout, output files, and reusable styling.
Step 1: Add the Generated Folder
The app needs a safe place to store the Excel file before downloading it.
generated/
We will also make the app create this folder automatically if it does not exist.
import os
GENERATED_FOLDER = "generated"
os.makedirs(GENERATED_FOLDER, exist_ok=True)
It makes the app more forgiving. If another developer downloads your project and forgets to create the folder,
the app still works.
Step 2: Add a Download Route
A Flask route can return a page, but it can also return a file. This is how we create the Excel download button.
Beginner caution: This simple version assumes the Excel file already exists. Later, you can add checks
so users get a friendly message if they try to download before generating a report.
Step 3: Add Summary Metrics
Summary metrics help the user quickly understand the report without scanning every row.
In many business reports, the first question is not βwhat are all the rows?β It is βhow many records are we talking about?β
or βdoes this output look reasonable?β Summary cards help establish that trust quickly.
Step 4: Add a Simple HTML Chart
In a full app, you might use Bokeh, Plotly, Chart.js, or another visualization library.
For this beginner web chapter, we will keep the chart simple and render basic bars using HTML and CSS.
Why not Bokeh here? We already introduced Bokeh earlier. This chapter focuses on Flask mechanics:
routes, templates, downloads, and passing data to HTML.
Full Updated app.py
Replace your previous app.py with this version.
import os
from flask import Flask, render_template, request, send_file
import pandas as pd
app = Flask(__name__)
DATA_FILE = "materials.csv"
GENERATED_FOLDER = "generated"
EXCEL_FILE = "filtered_materials.xlsx"
os.makedirs(GENERATED_FOLDER, exist_ok=True)
@app.route("/", methods=["GET", "POST"])
def index():
df = pd.read_csv(DATA_FILE)
material_type = "FERT"
plant = "1000"
records = []
chart_records = []
summary = None
message = ""
if request.method == "POST":
material_type = request.form.get("material_type", "FERT")
plant = request.form.get("plant", "1000")
filtered_df = df[
(df["mtart"] == material_type) &
(df["plant"].astype(str) == plant)
]
filtered_df = filtered_df.sort_values(by="stock", ascending=False)
if filtered_df.empty:
message = "No records found for the selected filters."
else:
records = filtered_df.to_dict(orient="records")
summary = {
"record_count": len(filtered_df),
"total_stock": int(filtered_df["stock"].sum()),
"selected_filters": f"{material_type} / {plant}"
}
chart_data = (
filtered_df["mtart"]
.value_counts()
.reset_index()
)
chart_data.columns = ["label", "value"]
chart_records = chart_data.to_dict(orient="records")
output_path = os.path.join(GENERATED_FOLDER, EXCEL_FILE)
filtered_df.to_excel(output_path, index=False)
message = f"Found {len(records)} record(s)."
return render_template(
"index.html",
material_type=material_type,
plant=plant,
records=records,
chart_records=chart_records,
summary=summary,
message=message
)
@app.route("/download")
def download():
file_path = os.path.join(GENERATED_FOLDER, EXCEL_FILE)
if not os.path.exists(file_path):
return "No Excel file has been generated yet. Please run a report first."
return send_file(file_path, as_attachment=True)
if __name__ == "__main__":
app.run(debug=True)
We added send_file for Excel downloads.
We added a generated folder for output files.
We calculated summary metrics.
We prepared chart data for the template.
We added a /download route.
Full Updated templates/index.html
Replace your previous templates/index.html with this version.
Generate a report first. The Excel file is created only after the app has filtered data.
In this beginner version, bar width is calculated with a simple multiplier:
style="width: {{ item.value * 25 }}%;"
Later, you can normalize values so chart widths scale more accurately.
Try This
Add a summary card for average stock.
Add a chart grouped by plant instead of material type.
Rename the downloaded file to include the selected plant.
Add a timestamp to the page showing when the report was generated.
Add a reset button that returns the form to default values.
Add this in app.py:
from datetime import datetime
generated_at = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
Then pass it into render_template() and display it in the HTML.
Chapter 6 Checkpoint
You can create generated output folders from Python.
You can create an Excel file from filtered data.
You can add a Flask download route.
You can pass summary metrics to an HTML template.
You can create a simple visual chart using HTML and CSS.
You can separate styling into a static CSS file.
Next: Chapter 7 can prepare this app for cloud deployment by introducing environment variables,
requirements, a Procfile, and a Cloud Foundry-ready structure.
Think Like Python: A Guide for ABAP Developers β Chapter 6