Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Integrating Jinja2 Templates and Handling Form Requests in FastAPI

Tech 3

Jinja2 Template Engine

FastAPI, as a Python web framework, does not include a built-in HTML template engine. This flexibility allows developers to use any template engine, with Jinja2 being the officially recommended choice.

pip install jinja2

Basic Setup

from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates

app = FastAPI()

# Serve static files from the 'static' directory
app.mount("/static", StaticFiles(directory="static"), name="static")

# Initialize Jinja2 templates from the 'templates' directory
templates = Jinja2Templates(directory="templates")

@app.get("/items/{item_id}", response_class=HTMLResponse)
def read_item(request: Request, item_id: str):
    return templates.TemplateResponse(
        "item_detail.html", 
        {"request": request, "item_id": item_id}
    )

Important Notes:

  1. You can also import Jinja2Templates from starlette.templating. FastAPI provides the same functionality for developer convenience.
  2. In TemplateResponse, the first parameter is the HTML file path, and the second is a dictionary of data to pass to the template. The dictionary must include the request object.

Response Class Fundamentals

By default, FastAPI uses JSONResponse for responses. You can specify different response types using the response_class parameter.

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()

@app.get("/simple/{item_id}", response_class=HTMLResponse)
def simple_html(item_id: str):
    return f"""
    <html>
        <head>
            <title>Item Details</title>
        </head>
        <body>
            <h1>Item ID: {item_id}</h1>
        </body>
    </html>
    """

Template Data Passing

@app.get("/students", response_class=HTMLResponse)
def display_students(request: Request):
    single_student = {
        'name': 'Alex', 
        'age': 25, 
        'gender': 'Male'
    }

    student_roster = [
        {'name': 'Alex', 'age': 25, 'gender': 'Male'},
        {'name': 'Jamie', 'age': 22, 'gender': 'Female'},
        {'name': 'Taylor', 'age': 23, 'gender': 'Non-binary'}
    ]

    student_directory = {
        'stu_001': {'name': 'Alex', 'age': 25, 'gender': 'Male'},
        'stu_002': {'name': 'Jamie', 'age': 22, 'gender': 'Female'},
        'stu_003': {'name': 'Taylor', 'age': 23, 'gender': 'Non-binary'}
    }
    
    # Pass single student using dictionary unpacking
    return templates.TemplateResponse(
        "single_student.html", 
        {"request": request, **single_student}
    )
    
    # Pass list of students
    return templates.TemplateResponse(
        "student_list.html", 
        {"request": request, 'students': student_roster}
    )
    
    # Pass dictionary of students
    return templates.TemplateResponse(
        "student_dict.html", 
        {"request": request, 'student_data': student_directory}
    )

Jinja2 Template Syntax

Jinja2 uses three main syntax elements:

  1. Control Structures (logic): {% %}
  2. Variable Output: {{ }}
  3. Comments: {# #}

Template Expressions

  • Variables passed from FastAPI (e.g., {{ name }})
  • Python basic types: strings, numbers, lists, tuples, dictionaries, booleans
  • Operations:
    • Arithmetic: {{ 2 + 3 }}
    • Comparison: {{ 2 > 1 }}
    • Logical: {{ False and True }}
  • Filters (|) and tests (is)
  • Function calls: {{ current_time() }}
  • List indexing: {{ items[1] }}
  • Membership: {{ 1 in [1,2,3] }}
  • String concatenation (~): {{ "Hello " ~ name ~ "!" }}
  • None handling: {{ name or "" }}

Control Statements

Conditional Statements:

{% if name and name == 'admin' %}
    <h1>Admin Console</h1>
{% elif name %}
    <h1>Welcome {{ name }}!</h1>
{% else %}
    <h1>Please Login</h1>
{% endif %}

Loop Statemetns:

{% for student in students %}
    {{ student }}
{% endfor %}

Filter Functions

Filters are transformation functions that modify variables. They can be thought of as Jinja2's built-in functions.

Common Filters

Filter Name Description
safe Render value without escaping
capitalize Convert first character to uppercase, others to lowercase
lower Convert to lowercase
upper Convert to uppercase
title Capitalize each word
trim Remove leading/trailing whitespace
striptags Remove all HTML tags before rendering
join Join multiple values into a string
replace Replace string values
round Round numbers (default) or control with parameters
int Convert value to integer

String Filters

<body>
{# Display default string when variable is undefined #}
<p>{{ name | default('No name') }}</p>

{# Capitalize first letter #}
<p>{{ 'hello world' | capitalize }}</p>

{# Convert to lowercase #}
<p>{{ 'XML' | lower }}</p>

{# Trim whitespace #}
<p>{{ '  hello  ' | trim }}</p>

{# Reverse string #}
<p>{{ 'hello' | reverse }}</p>

{# Format output #}
<p>{{ '%s is %d' | format("Number", 99) }}</p>

{# Disable HTML auto-escaping #}
<p>{{ '<em>name</em>' | safe }}</p>

{% autoescape false %}
{# HTML escape #}
<p>{{ '<em>name</em>' | escape }}</p>
{% endautoescape %}
</body>

Numeric Filters

{# Round to nearest integer #}
<p>{{ 12.98 | round }}</p>

{# Round down to 2 decimal places #}
<p>{{ 12.8888 | round(2, 'floor') }}</p>

{# Absolute value #}
<p>{{ -12 | abs }}</p>

List Filters

{# First element #}
<p>{{ [1,2,3] | first }}</p>

{# Last element #}
<p>{{ [1,2,3] | last }}</p>

{# List length #}
<p>{{ [1,2,3,4,5] | length }}</p>

{# Sum of list #}
<p>{{ [1,2,3,4,5] | sum }}</p>

{# Sort list (ascending by default) #}
<p>{{ [3,2,1,5,4] | sort }}</p>

{# Join list elements #}
<p>{{ [1,2,3,4,5] | join(' | ') }}</p>

{# Convert all elements to uppercase #}
<p>{{ ['alex','bob','ada'] | upper }}</p>

Dicsionary Filters

{% set user_data=[{'name':'Tom','gender':'M','age':20},
                 {'name':'John','gender':'M','age':18},
                 {'name':'Mary','gender':'F','age':24},
                 {'name':'Bob','gender':'M','age':31},
                 {'name':'Lisa','gender':'F','age':19}] %}

{# Sort by attribute in descending order #}
<ul>
{% for user in user_data | sort(attribute='age', reverse=true) %}
     <li>{{ user.name }}, {{ user.age }}</li>
{% endfor %}
</ul>

{# Group by attribute #}
<ul>
{% for group in user_data|groupby('gender') %}
    <li>{{ group.grouper }}<ul>
    {% for user in group.list %}
        <li>{{ user.name }}</li>
    {% endfor %}</ul></li>
{% endfor %}
</ul>

{# Extract and join specific attributes #}
<p>{{ user_data | map(attribute='name') | join(', ') }}</p>

Test Functions

Test functions return boolean values and are used with the is keyword to test variables or expressions.

{# Check if variable is defined #}
{% if name is defined %}
    <p>Name is: {{ name }}</p>
{% endif %}

{# Check if all characters are uppercase #}
{% if name is upper %}
  <h2>"{{ name }}" is all uppercase.</h2>
{% endif %}

{# Check if variable is None #}
{% if name is none %}
  <h2>Variable is None.</h2>
{% endif %}

{# Check if variable is a string #}
{% if name is string %}
  <h2>{{ name }} is a string.</h2>
{% endif %}

{# Check if number is even #}
{% if 2 is even %}
  <h2>Variable is an even number.</h2>
{% endif %}

{# Check if variable is iterable #}
{% if [1,2,3] is iterable %}
  <h2>Variable is iterable.</h2>
{% endif %}

{# Check if variable is a dictionary #}
{% if {'name':'test'} is mapping %}
  <h2>Variable is a dictionary.</h2>
{% endif %}

Template Inheritance

Template inheritance helps avoid code duplication for common elements like headers, footers, and navigation.

Parent Template (base.html):

<body>
Header Content
{% block header %}
{% endblock %}

Main Content Area
{% block content %}
{% endblock %}

Footer Content
{% block footer %}
{% endblock %}
</body>

Child Template:

{% extends "base.html" %}

{% block content %}
{{ super() }}
    <h1>Custom Content</h1>
{% endblock %}

{% block header %}
{{ super() }}
    <h1>Custom Header</h1>
{% endblock %}

Handling Form Requests

1. Processing Employee Information Form

@app.get("/employee-form", response_class=HTMLResponse)
def show_employee_form(request: Request):
    return templates.TemplateResponse(
        "employee_form.html", 
        {"request": request}
    )


@app.post("/submit-employee")
def submit_employee(
    name: str = Form(),
    gender: str = Form(),
    phone: str = Form(),
    email: str = Form()
):
    print(f"Name: {name}")
    print(f"Gender: {gender}")
    print(f"Phone: {phone}")
    print(f"Email: {email}")
    return 'Submission Successful'

2. Handling File Uploads with Form Data

from typing import Union
from fastapi import UploadFile

@app.post("/submit-employee-with-photo")
def submit_employee_with_photo(
    name: str = Form(),
    gender: str = Form(),
    phone: str = Form(),
    email: str = Form(),
    photo: Union[UploadFile, None] = None
):
    print(f"Name: {name}")
    print(f"Gender: {gender}")
    print(f"Phone: {phone}")
    print(f"Email: {email}")
    
    if photo:
        print(f"Uploaded file: {photo.filename}")
        # Save the uploaded file
        with open('employee_photo.jpg', 'wb') as file_buffer:
            file_buffer.write(photo.file.read())
    
    return 'Submission Successful'

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.