Chapter 36 Exercises: Automated Report Generation
These exercises build from foundational Jinja2 concepts up to full report pipelines. Work through each tier in order — later tiers assume the skills from earlier ones.
Tier 1 — Foundations
Get comfortable with the core tools before combining them.
Exercise 1.1 — Your First Jinja2 Template
Create a Jinja2 template string (not a file) that displays a simple invoice header. The template should use {{ }} to display:
- Company name
- Invoice number
- Invoice date
- Client name
Write the Python code to render the template with sample data and print the output.
Expected output (approximate):
ACME CORP
Invoice #1042
Date: January 15, 2025
Billed to: Sandra Chen
Exercise 1.2 — Loops in Templates
You have a list of sales data for five products. Write a Jinja2 template that renders an HTML table with columns for Product Name, Units Sold, and Revenue. Use a {% for %} loop in the template.
The Python side should provide a list of dictionaries. Render the template and print the resulting HTML.
Exercise 1.3 — Jinja2 Conditionals
Extend Exercise 1.2's template to add a fourth column: "Status". If a product's revenue is above $10,000, show "Strong". If between $5,000 and $10,000, show "Moderate". If below $5,000, show "Needs Attention".
Use {% if %}, {% elif %}, and {% else %} inside the table row.
Exercise 1.4 — Custom Filters
Register three custom Jinja2 filters:
1. currency — formats a number as $1,234.56
2. thousands — formats an integer with comma separators: 12,345
3. change_arrow — takes a float and returns "▲ 5.2%" for positive or "▼ 2.1%" for negative values
Test each filter in a short template that displays one sample value through each filter.
Exercise 1.5 — Template Files
Move Exercise 1.3's template from a Python string into an actual file (templates/product_table.html). Use FileSystemLoader to load it. The Python script should:
1. Create the templates/ directory if it doesn't exist
2. Write the template to the file
3. Use Environment(loader=FileSystemLoader("templates")) to render it
4. Print the output
Tier 2 — Applied
Build real, working report components.
Exercise 2.1 — HTML Report with Embedded CSS
Build a complete, styled HTML report file for a fictional company's quarterly sales summary. Requirements:
- Must have a proper <!DOCTYPE html> structure
- Inline CSS that makes the report look professional (at minimum: a dark header bar, alternating row colors in tables, positive numbers in green, negative in red)
- One table with at least five rows
- A section showing three KPI values prominently displayed
Save the output as quarterly_report.html.
Exercise 2.2 — Matplotlib Chart to Base64
Write a function sales_bar_chart(categories, values, title) that:
1. Creates a matplotlib bar chart
2. Sets matplotlib.use("Agg") so it works without a display
3. Converts the figure to a base64-encoded PNG string
4. Returns the data URI string (data:image/png;base64,...)
Then embed the result in an HTML <img> tag and save to a file. Verify it opens in a browser with the chart visible.
Exercise 2.3 — Excel Report with openpyxl
Create an Excel workbook with two sheets: - Sheet 1 ("Summary"): Company name in a large bold font, five KPI values with currency or percentage formatting, and a total row - Sheet 2 ("Detail"): A table of at least 8 rows with headers, alternating row fills (white and light blue), and column widths auto-sized to content
Save as business_report.xlsx and verify it opens correctly.
Exercise 2.4 — Excel Chart
Extend Exercise 2.3 to add a bar chart on Sheet 2 that visualizes one of the numeric columns. The chart should: - Have a title - Be positioned next to the data (not on top of it) - Use column chart type (vertical bars)
Exercise 2.5 — Report Parameterization
Write a ReportParameters dataclass with the following fields:
- report_title: str
- start_date: date
- end_date: date
- include_charts: bool = True
- output_format: Literal["html", "excel", "both"] = "both"
Add a period_label property that returns a human-readable string like "January 2025" (for single-month ranges) or "Q1 2025" (for quarter ranges — detect this if start is Jan 1, Apr 1, Jul 1, or Oct 1 and end is 3 months later).
Write three test cases showing the property returning correct values for different date ranges.
Tier 3 — Integration
Connect reports to data sources and delivery systems.
Exercise 3.1 — Data Pipeline to Report
Write a complete script that: 1. Reads sales data from a CSV file (create the sample CSV as part of the exercise) 2. Aggregates it (total revenue per region, average order value) 3. Passes the aggregated data to a Jinja2 template 4. Renders and saves an HTML report
The CSV should have columns: date, region, product, units, revenue.
Exercise 3.2 — Auto-Generated Executive Summary
Write a function generate_executive_summary(data: dict) -> str that takes a report data dictionary and returns an HTML string with:
- Whether revenue hit target (and by how much)
- The best-performing region/product
- The most concerning metric (whatever is furthest from its target)
- A forward-looking sentence based on trends (e.g., if revenue increased 3 months in a row, say so)
Test it with three different data scenarios: exceeding targets, meeting targets, and missing targets.
Exercise 3.3 — Multi-Format Report Generator
Write a class ReportGenerator with methods:
- to_html(params, data) -> Path
- to_excel(params, data) -> Path
- to_pdf(params, data) -> Path (use WeasyPrint if installed, otherwise save HTML with a .pdf placeholder extension)
All three methods should use the same underlying data and produce consistent reports in different formats.
Exercise 3.4 — Report Email Assembly
Write a function build_report_email(report_data: dict, pdf_path: Path) -> MIMEMultipart that:
1. Takes report data and a PDF file path
2. Builds an HTML email body that includes the executive summary and a link to the full report
3. Attaches the PDF file
4. Returns the complete MIMEMultipart object (don't send it — just build and return it)
Write a test that calls the function and verifies the MIME object has the correct parts (HTML body and PDF attachment).
Exercise 3.5 — Template Inheritance
Create a base template (base.html) with the overall report structure and three child templates:
- executive_report.html — Just the summary and KPIs
- operational_report.html — Full tables and charts
- client_report.html — Stripped down, friendly language for external clients
Each child template overrides at least two {% block %} sections. Render all three with the same data and compare the outputs.
Tier 4 — Challenge
Harder problems that require design thinking and multiple components.
Exercise 4.1 — Scheduled Monthly Report
Write a complete report pipeline that:
1. Detects the current date and calculates the prior month's date range
2. Loads data (from a SQLite database or CSV files — your choice)
3. Generates both HTML and PDF versions
4. Saves them to an organized directory structure: reports/YYYY/MM/
5. Logs each run to a file with timestamp, status, and file paths
Include error handling so that if any step fails, the error is logged and (if on a non-first day of the month) the script exits gracefully without generating a partial report.
Exercise 4.2 — Comparison Report
Build a report that shows two periods side by side: this month vs. last month, or this year vs. last year. Requirements: - Each metric shows both values and the change (absolute and percentage) - Green/red color coding based on whether the change is favorable - A trend sparkline (tiny chart) for each metric - The report correctly handles edge cases like division by zero (when the prior period value was 0)
Exercise 4.3 — Dynamic Report Sections
Build a report system where the sections that appear depend on the data. Specifically: - If any region has YoY revenue decline > 5%, add a "Regions Requiring Attention" section - If new customer acquisition exceeded target by > 20%, add a "Growth Spotlight" callout box - If gross margin improved vs. prior month, show a margin trend section; if it declined, show a margin analysis section with more detail
Use Jinja2 conditionals and Python logic working together to control what appears.
Exercise 4.4 — Excel Dashboard Sheet
Build an Excel report that includes a "Dashboard" sheet designed to print on one page (A4 landscape). Requirements: - Company logo placeholder (a colored rectangle with text) - Three KPI value boxes with large fonts and color coding - Two charts on the same sheet - A small summary table - Print area set to the Dashboard sheet only - Page setup configured for one-page landscape printing
Tier 5 — Stretch
Open-ended problems for learners who want to go further.
Exercise 5.1 — Report Template Library
Design and implement a ReportTemplateLibrary class that manages a collection of report templates. It should:
- Store templates as Jinja2 template strings or file paths
- Support template versioning (keep prior versions, render any version by number)
- Allow templates to be rendered with validation (required context keys checked before rendering)
- Export the full library to a JSON manifest for documentation
Exercise 5.2 — Automated Report Audit Trail
Every time a report is generated, write a JSON record to a log file with: timestamp, report type, parameters, data hash (MD5 of the data dictionary), output files, and generation time in milliseconds. Build a ReportAuditViewer that reads the log and produces a summary: how many reports were generated, average generation time, which parameters were most common.
Exercise 5.3 — Self-Service Report Builder
Build a command-line interface (using argparse) for report generation with the following options:
python generate_report.py --type monthly --year 2024 --month 11 --region northeast --format pdf,excel --email priya@acmecorp.com
All parameters should have sensible defaults. The script should validate inputs before attempting generation and provide helpful error messages for invalid combinations.
Exercise 5.4 — Report Diff
Write a function compare_reports(data_this_period: dict, data_prior_period: dict) -> dict that produces a "diff dictionary" showing what changed between two reports. Then write a Jinja2 template that renders this diff as a compact "What Changed" summary — useful for stakeholders who already read last month's report and only want the delta.