목록으로
Jinja2 템플릿과 htmx을 사용해 로그인 페이지 구현
Jinja2 템플릿 사용
(1) Jinja2 소개
Jinja2는 유명한 템플릿 엔진이예요. Django 템플릿 문법과 비슷하지만 더 유연하고 표현력이 좋죠. Flask를 사용하셨다면 친숙하실 거예요. FastAPI에서 공식(?) 템플릿 엔진은 Jinja2인데, FastAPI의 애플리케이션 서버 축인 Starlette이 Jinja2 템플릿을 통합해(intgrated) 지원하거든요.
Jinja2는 비동기 렌더링을 지원하는데, 이 컨텐츠에서 다루기엔 범위를 벗어나므로 더 자세히 다루진 않을게요.
저는 Django를 사용하는 프로젝트에도 Django 템플릿 대신 Jinja2를 사용하는 편이예요. 🙂
(2) FastHX 소개
FastHX는 FastAPI에서 htmx를 편하게 사용하는 데 유용한 패키지예요. 그렇지만 htmx도 결국 서버측 렌더링(Server Side Rendering)을 기반으로 하기 때문에 자연스레 Jinja2를 사용하는 데 돠움을 줘요. htmx는 다음 편에서 살펴보기로 하고, 이번 편에서는 FastAPI에서 Jinja2를 편하게 다루는 도구로 사용할게요.
(3) 설정
SQLAlchemy가 그랬듯이, Jinja2도 FastAPI에서 사용하는 데 필요한 FastAPI용 설정이 따로 있진 않아요. 한 가지 조치를 더 취해주면 되는데, Jinja2 템플릿 인스턴스 객체를 만든 후 이 객체를 Starlette에서 제공하는 Jinja2 관련 템플릿 객체에서 사용할 수 있게 전달해주는 거죠. 설정해보죠!
먼저 패키지들을 설치하고요. Jinja 객체를 생성해요.
Jinja2 템플릿 안에서 템플릿 이름을 가리키면 해당 이름을 가진 템플릿 파일을 찾을 경로를 FileSystemLoader의 searchpath인자로 전달해 지정했어요. base_dir = Path()는 FastAPI 애플리케이션 서버를 구동한 경로를 뜻해요. 이 경로는 pudding_todo 디렉터리의 상위, 그러니까 pyproject.toml 파일이 있는 경로이고, 이 경로를 기준으로 템플릿 파일이 있는 경로를 지정한 거죠.
Jinja2Templates 클래스는 Starlette에서 제공하는 클래스인데, Jinja2 동작 환경 정보를(Environment) 전달하면 이 정보를 기반으로 템플릿을 렌더링해 Starlette의 Response 객체를 생성해 반환해주죠. FastAPI가 Starlette을 기반으로 하기도 하고, fastapi.templating.Jinja2Templates 자체가 starlette.templating.Jinja2Templates예요.
마지막으로 FastHX에 있는 Jinja 클래스에 Jinja2Templates 인스턴스 객체를 전달하며 객체를 생성했어요. 이 객체를 이용해 종단점(Endpoint) 함수에서 편리하게 템플릿 렌더링을 선언할 수 있어요.
로그인 페이지
(1) 로그인 페이지 템플릿
할 일 그룹과 할 일 종단점엔 로그인한 사용자만 접근할 수 있어요. 그러니 로그인 페이지를 먼저 구현할게요.
account 앱의 템플릿 경로는 apps/account/templates로 설정했으니 이 경로를 만들고, 그 안에 pages 디렉터리를 만들어요. 그리고 그 안에 login.jinja2 파일을 만듭니다.
url_for()는 템플릿에서 사용할 수 있는 함수로 Starlette의 Jinja2Templates 객체에서 주입해줘요. Request 객체인 request의 url_for() 메서드를 사용하여 URL 이름으로 URL 경로를 문자열로 생성해요.
템플릿을 만들었으니 로그인 종단점 함수를 만들어 이 템플릿을 사용해 화면을 출력할게요.
기존에 구현한 종단점 함수와 다른 점이 있어요. 바로 templates.py에 구현해 정의한 tpl 객체를 장식자(Decorator)로 사용해 사용할 템플릿 파일을 지정한 거죠. 이 기능은 FastHX의 Jinja 객체예요. 빈 사전형 객체를 반환하는데, 반환하는 이 사전형 객체를 템플릿의 컨텍스트, 간단히 말해서 템플릿 내 변수로 사용해요.
FastHX의 page 장식자를 사용하지 않고 Starlette의 Jinja2Templates 객체를 사용하면 다음과 같이 코드를 작성해요.
FastHX를 사용하는 게 조금 더 간결하고 편하죠.
(2) JSON 요청 처리
웹브라우저를 열고 http://localhost:8000/login URL에 접속해보면 로그인 화면이 나와요. Admin에서 생성한 계정의 계정명과 비밀번호를 입력하고 로그인하면, 예상치 못한 응답을 받아요. 바로 다음과 같은 JSON 응답을 받는 거죠.
우리는 HTML 폼 데이터로 전송하는데, FastAPI는 기본적으로 데이터의 컨텐츠 유형을 application/json으로 간주해요.
우리가 구현한 로그인 종단점 함수는 이와 같이 사용자 전송 적재물을 받으므로 username과 password를 JSON 형식으로 전송해야 해요. 물론 FastAPI도 Form데이터를 받을 순 있지만, 기존 LoginSchema를 그대로 사용하도록 하고, 그 대신 클라이언트에서 application/json 형식으로 데이터를 전송하도록 할게요. 바로 htmx를 이용해서요.
(3) htmx 적용하기
코드가 조금 달라졌지요? htmx에 관한 부분은 HTML form 태그에 있는 속성 중 hx- 접두사가 붙은 속성들이예요. 폼에 submit 이벤트가 발생하면(hx-trigger) 지정한 URL로 HTTP POST 방식으로 데이터를 전송하는데(hx-post), 서버로부터 받은 응답 본문을 현 HTML 요소(element) 중 어느 것과도 바꿔치지 않겠다(hx-swap)는 뜻이지요. 더불어 데이터 전송 시 JSON 형식으로 인코딩하도록 htmx 확장기능을 사용(hx-ext)했고요.
{% extends %}는 템플릿을 확장하는 템플릿 태그예요. layout.jinja2에 템플릿 내용을 끼워넣어 확장하도록 했어요. layout.jinja2는 아직 작성하지 않았는데, pudding_todo/templates 디렉터리를 만들고, 이곳에 layout.jinja2 파일을 생성해 다음 코드를 작성하세요. login.jinja2 파일은 pudding_todo/apps/account/templates 디렉터리 안에 있고, layout.jinja2 파일은 pudding_todo/templates 디렉터리 안에 있는 거죠.