FastAPI에서 Jinja를 사용하는 방법

심준석·2024년 5월 7일
0

FastAPI

목록 보기
11/11

Jinja를 사용하려면 Jinja2 패키지를 설치하고 기존 작업 디렉터리에 templates이라는 신규 폴더를 만들어야 한다.

이 폴더에 모든 Jinja 관련 파일이 저장된다.

Jinja2 관련 패키지들을 먼저 설치하자

pip3 install jinja2 python-multipart

templates 폴더를 생성하자

mkdir templates

이제 templates 폴더 안에 home.html,todo.html 이라는 두 개의 파일을 만든다.

cd templates
touch {home,todo}.html
  • home.html : 애플리케이션 홈 페이지용
  • todo.html : todo 페이지용

이제 FastAPI 애플리케이션이 Jinja를 사용하도록 설정해보자

1. todo API 컴포넌트(todo.py)의 POST 라우트 수정

from fastapi import APIRouter, Path, HTTPException, status, Request, Depends
from fastapi.templating import Jinja2Templates
from model import Todo, TodoItem, TodoItems

todo_router = APIRouter()

todo_list = []

templates = Jinja2Templates(directory="templates/")

@todo_router.post("/todo")
async def add_todo(request: Request, todo: Todo = Depends(Todo.as_form)):
    todo.id = len(todo_list) + 1
    todo_list.append(todo)
    return templates.TemplateResponse("todo.html",
                                      {
                                          "request":request,
                                          "todos":todo_list
                                      })

2. GET 라우트 수정

@todo_router.get("/todo", response_model = TodoItems)
async def retreive_todos(request:Request):
    return templates.TemplateResponse("todo.html",
                                    {
                                        "request":request,
                                        "todos":todo_list
                                    })

@todo_router.get("/todo/{todo_id}")
async def get_single_todo(request:Request, todo_id: int =Path(..., title="The ID of the todo to retrieve")) -> dict:
    for todo in todo_list:
        if todo.id == todo_id:
            return templates.TemplateResponse("todo.html",
                                {
                                    "request":request,
                                    "todos":todo_list
                                })
    raise HTTPException(
        status_code=status.HTTP_404_NOT_FOUND,
        detail="Todo with supplied ID doesn't exist"
    )

이 코드는 Jinja가 template 폴더를 참조해서 그 안에 있는 특정 템플릿을 사용하도록 지정한다. 템플릿은 templates.TemplateResponse() 메서드를 통해 전달된다.
(todo를 추가하는 POST 메서드는 의존성을 사용해서 입력값을 전달한다.)

3. model.py의 Config 서브 클래스 추가

from pydantic import BaseModel
from typing import List, Optional
from fastapi import Form

class Item(BaseModel):
    item: str
    status: str

class Todo(BaseModel):
    id: Optional[int] = None
    item: str

    @classmethod
    def as_form(
        cls,
        item: str = Form(...)
    ):
        return cls(item=item)

4. home.html 파일의 문서 유형 선언

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta content="IE=edge" http-equiv="X-UA-Compatible">
    <meta content="width=device-width, initial-scale=1.0" name="viewport">
    <title>Packt Todo Application</title>
    <link crossorigin="anonymous" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
          integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" rel="stylesheet">
    <link crossorigin="anonymous" href="https://use.fontawesome.com/releases/v5.0.10/css/all.css"
          integrity="sha384-+d0P83n9kaQMCwj8F4RJB66tzIwOKmrdb46+porD/OvrJ+37WqIM7UoBtwHO6Nlg" rel="stylesheet">
</head>

5. 템플릿 바디를 작성

container-fluid Class에 block 태그를 사용해 todo_container를 추가한다.

<body>
<header>
    <nav class="navar">
        <div class="container-fluid">
            <center>
                <h1>Packt Todo Application</h1>
            </center>
        </div>
    </nav>
</header>
<div class="container-fluid">
    {% block todo_container %}{% endblock %}
</div>
</body>
</html>

6. todo.html을 열어서 다음과 같은 템플릿을 작성

{% extends "home.html" %}

{% block todo_container %}
<main class="container">
    <hr>
    <section class="container-fluid">
        <form method="post">
            <div class="col-auto">
                <div class="input-group mb-3">
                    <input aria-describedby="button-addon2" aria-label="Add a todo" class="form-control" name="item"
                           placeholder="Purchase Packt's Python workshop course" type="text"
                           value="{{ item }}"/>
                    <button class="btn btn-outline-primary" data-mdb-ripple-color="dark" id="button-addon2"
                            type="submit">
                        Add Todo
                    </button>
                </div>
            </div>
        </form>
    </section>
    {% if todo %}
    <article class="card container-fluid">
        <br/>
        <h4>Todo ID: {{ todo.id }} </h4>
        <p>
            <strong>
                Item: {{ todo.item }}
            </strong>
        </p>
    </article>
    {% else %}
    <section class="container-fluid">
        <h2 align="center">Todos</h2>
        <br>
        <div class="card">
            <ul class="list-group list-group-flush">
                {% for todo in todos %}
                <li class="list-group-item">
                    {{ loop.index }}. <a href="/todo/{{ loop.index }}"> {{ todo.item }} </a>
                </li>
                {% endfor %}
            </ul>
        </div>
        {% endif %}
    </section>
</main>
{% endblock %}

todo 템플릿이 home 템플릿을 상속한다. 또한 todo_container 블록을 정의해서 부모 템플릿이 이 템플릿의 콘텐츠를 표시할 수 있게 한다.

todo 템플릿은 모든 todo를 추출하는 라우트와 단일 todo 추출하는 라우트 모두에서 사용된다. 결과적으로 라우트에 따라 다른 콘텐츠를 렌더링하게 되는 것이다.

이제 웹 브라우저에 들어가 변경 내용이 반영되었는지 확인해보자.

홈 페이지가 제대로 작동하는지 확인하기 위해 todo 아이템을 하나 추가해보자

잘 작동된다.

profile
Developer & Publisher 심준석 입니다.

0개의 댓글