FastAPI로 완벽한 블로그 구축: 태그별 필터링
Wenhao Wang
Dev Intern · Leapcell

이전 게시물에서는 블로그에 대한 태그 생성 및 표시 기능을 추가했습니다.
다음으로 태그 기능의 나머지 부분인 태그별 게시물 필터링을 완료할 것입니다.
사용자가 태그 링크를 클릭하면 해당 특정 태그에 속한 게시물만 표시하는 새 페이지로 이동합니다. 이를 위해 백엔드에 새 라우트와 처리 로직, 그리고 프론트엔드에 해당 뷰를 생성해야 합니다.
단계 1: 서비스 로직 확장
먼저 tags_service.py
에 두 개의 새 메서드를 추가해야 합니다. 하나는 태그 ID로 태그를 찾아(이름을 얻기 위해) 다른 하나는 태그 ID별로 관련된 모든 게시물을 찾는 것입니다.
tags_service.py
파일을 열고 다음 내용을 추가합니다.
# tags_service.py import uuid from typing import List from sqlmodel import Session, select from models import Tag, Post, PostTagLink # Post 및 PostTagLink 가져오기 def find_or_create_tags(tag_names: List[str], session: Session) -> List[Tag]: """ 태그 이름 목록을 기반으로 태그 엔티티를 찾거나 생성합니다. """ # ... 이 함수는 변경되지 않습니다 ... tags = [] if not tag_names: return tags statement = select(Tag).where(Tag.name.in_(tag_names)) existing_tags = session.exec(statement).all() tags.extend(existing_tags) existing_tag_names = {tag.name for tag in existing_tags} new_tag_names = [name for name in tag_names if name not in existing_tag_names] for name in new_tag_names: new_tag = Tag(name=name) session.add(new_tag) tags.append(new_tag) session.commit() for tag in tags: if tag.id is None: session.refresh(tag) return tags def get_tag_by_id(tag_id: uuid.UUID, session: Session) -> Tag | None: """ID로 단일 태그 찾기""" return session.get(Tag, tag_id) def get_posts_by_tag_id(tag_id: uuid.UUID, session: Session) -> List[Post]: """태그 ID로 관련된 모든 게시물 찾기""" statement = ( select(Post) .join(PostTagLink, Post.id == PostTagLink.post_id) .where(PostTagLink.tag_id == tag_id) .order_by(Post.createdAt.desc()) ) posts = session.exec(statement).all() return posts
코드 설명:
get_tag_by_id
: 기본 키로 태그 객체를 가져오는 간단한 헬퍼 함수입니다.get_posts_by_tag_id
: 이것이 핵심 쿼리 로직입니다. SQLModel의select
및join
메서드를 사용하여PostTagLink
연결 테이블을 통해 제공된tag_id
와 관련된 모든Post
객체를 필터링하고 생성 시간 기준으로 내림차순으로 정렬합니다.
단계 2: 태그 라우트 생성
이제 /tags/{tag_id}
요청을 처리할 라우트를 구현해 보겠습니다.
먼저 routers
폴더 안에 tags.py
라는 새 파일을 만듭니다.
# routers/tags.py import uuid from fastapi import APIRouter, Request, Depends from fastapi.responses import HTMLResponse from fastapi.templating import Jinja2Templates from sqlmodel import Session from database import get_session from auth_dependencies import get_user_from_session import tags_service import tracking_service router = APIRouter() templates = Jinja2Templates(directory="templates") @router.get("/tags/{tag_id}", response_class=HTMLResponse) def get_posts_by_tag( request: Request, tag_id: uuid.UUID, session: Session = Depends(get_session), user: dict | None = Depends(get_user_from_session), ): # 1. 이 태그 아래의 모든 게시물 가져오기 posts = tags_service.get_posts_by_tag_id(tag_id, session) # 2. 페이지에 태그 이름을 표시하기 위해 태그 정보 가져오기 tag = tags_service.get_tag_by_id(tag_id, session) # 3. 홈페이지와 일관성을 유지하기 위해 게시물 목록의 조회 수를 일괄 가져오기 post_ids = [post.id for post in posts] view_counts = tracking_service.get_counts_by_post_ids(post_ids, session) for post in posts: post.view_count = view_counts.get(post.id, 0) tag_name = tag.name if tag else "Unknown" return templates.TemplateResponse( "posts-by-tag.html", { "request": request, "posts": posts, "user": user, "filter_name": tag_name, "title": f"Posts in {tag_name}", }, )
마지막으로 main.py
에 이 새 라우터 모듈을 포함시키는 것을 잊지 마세요.
# main.py # ... 다른 가져오기 from routers import posts, users, auth, comments, uploads, tags # tags 라우터 가져오기 # ... # 라우터 포함 app.include_router(posts.router) app.include_router(users.router) app.include_router(auth.router) app.include_router(comments.router) app.include_router(uploads.router) app.include_router(tags.router) # 태그 라우터 마운트
단계 3: 프론트엔드 뷰 생성
마지막 단계는 templates
폴더에 posts-by-tag.html
뷰 파일을 만드는 것입니다. 이 파일은 태그별로 필터링된 게시물 목록을 표시하는 데 사용되며 내용은 index.html
과 매우 유사합니다.
templates/posts-by-tag.html
파일을 만듭니다.
{% include "_header.html" %} <div class="filter-header"> <h2>Posts in Tag: <strong>{{ filter_name }}</strong></h2> </div> {% if posts %} <div class="post-list"> {% for post in posts %} <article class="post-item"> <h2><a href="/posts/{{ post.id }}">{{ post.title }}</a></h2> <p>{{ post.content[:150] }}...</p> <small>{{ post.createdAt.strftime('%Y-%m-%d') }} | Views: {{ post.view_count }}</small> </article> {% endfor %} </div> {% else %} <p>No posts found in this tag.</p> {% endif %} <a href="/" class="back-link" style="margin-top: 2rem;">← Back to Home</a> {% include "_footer.html" %}
이 템플릿은 동적으로 제목(예: "Posts in Tag: Python")과 해당 태그 아래의 게시물 목록을 표시합니다. 태그 아래에 게시물이 없으면 해당 메시지가 표시됩니다.
실행 및 테스트
애플리케이션을 다시 시작합니다.
uvicorn main:app --reload
브라우저를 열고 태그가 있는 게시물로 이동한 다음 게시물 아래의 아무 태그 링크나 클릭합니다.
해당 태그의 필터 페이지로 리디렉션되며 해당 태그 아래의 모든 게시물 목록을 볼 수 있습니다.
이 두 튜토리얼을 통해 블로그에 완전한 태그 시스템을 추가했습니다.
이 시점에서 FastAPI 블로그 프로젝트는 기본 인프라부터 핵심 기능, 콘텐츠 구성, 데이터 분석까지 모든 것을 다루었습니다.
블로그 기능의 잠재력은 무궁무진합니다. 현재 프레임워크를 기반으로 더 많은 기능을 계속 추가할 수 있습니다. 나머지는 여러분의 상상력에 달려 있습니다!
Leapcell에 배포하는 것을 잊지 마세요. FastAPI 지원, PostgreSQL 데이터베이스, Redis, 웹 분석 및 웹 애플리케이션 구축에 필요한 모든 도구를 제공합니다.