
이전 게시물에서는 블로그에 대한 태그 생성 및 표시 기능을 추가했습니다.
다음으로 태그 기능의 나머지 부분인 태그별 게시물 필터링을 완료할 것입니다.
사용자가 태그 링크를 클릭하면 해당 특정 태그에 속한 게시물만 표시하는 새 페이지로 이동합니다. 이를 위해 백엔드에 새 라우트와 처리 로직, 그리고 프론트엔드에 해당 뷰를 생성해야 합니다.
먼저 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 객체를 필터링하고 생성 시간 기준으로 내림차순으로 정렬합니다.이제 /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) # 태그 라우터 마운트
마지막 단계는 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, 웹 분석 및 웹 애플리케이션 구축에 필요한 모든 도구를 제공합니다.
X에서 저희를 팔로우하세요: @LeapcellKR
관련 글: