FastAPIで完璧なブログを構築:コメントシステム
Grace Collins
Solutions Engineer · Leapcell

前の記事では、FastAPIブログのユーザーログインとセッション管理を完全に実装しました。これで、サーバーはユーザーのログイン状態を「記憶」し、認証が必要なページを保護できるようになりました。
ログインユーザーとゲストを区別できるようになったので、ブログにインタラクティブな機能を追加するのに最適な時期です。
この記事では、ブログの基本的かつ重要な機能であるコメントシステムを追加します。
具体的には、以下の機能を実装します。
- 各投稿の下にコメントリストを表示する。
- ログインしたユーザーが記事にコメントを投稿できるようにする。
ステップ 1: コメントのデータモデルを作成する
Post
およびUser
モデルと同様に、Comment
モデルにも独自のデータモデルとPost
およびUser
との関係性が必要です。
1. コメントモデルの作成
models.py
ファイルを開き、Comment
モデルを追加し、双方向の関係性を確立するためにUser
およびPost
モデルを更新します。
# models.py import uuid from datetime import datetime from typing import Optional, List from sqlmodel import Field, SQLModel, Relationship class User(SQLModel, table=True): id: Optional[uuid.UUID] = Field(default_factory=uuid.uuid4, primary_key=True) username: str = Field(unique=True, index=True) password: str # Commentとのone-to-manyリレーションシップを追加 comments: List["Comment"] = Relationship(back_populates="user") class Post(SQLModel, table=True): id: Optional[uuid.UUID] = Field(default_factory=uuid.uuid4, primary_key=True) title: str content: str createdAt: datetime = Field(default_factory=datetime.utcnow, nullable=False) # Commentとのone-to-manyリレーションシップを追加 comments: List["Comment"] = Relationship(back_populates="post") class Comment(SQLModel, table=True): id: Optional[uuid.UUID] = Field(default_factory=uuid.uuid4, primary_key=True) content: str createdAt: datetime = Field(default_factory=datetime.utcnow, nullable=False) # PostおよびUserテーブルにリンクする外部キーを定義 postId: uuid.UUID = Field(foreign_key="post.id") userId: uuid.UUID = Field(foreign_key="user.id") # many-to-oneリレーションシップを定義 post: Post = Relationship(back_populates="comments") user: User = Relationship(back_populates="comments")
ここでは3つのことを行いました。
content
、createdAt
、およびpost
テーブルとuser
テーブルを指す外部キーpostId
とuserId
を含むComment
モデルを作成しました。Comment
モデルでRelationship
を使用して、Post
およびUser
との「many-to-one」リレーションシップを定義しました。User
およびPost
モデルに逆の「one-to-many」リレーションシップcomments
を追加しました。これにより、Post
オブジェクトのすべてのコメントやUser
オブジェクトが作成したすべてのコメントを簡単に取得できます。
main.py
でcreate_db_and_tables
関数を構成したため、アプリケーション起動時にSQLModelモデルを自動的に検出し、対応するデータベーステーブルを作成または更新します。SQLを手動で実行する必要はありません。
手動でSQLを実行する必要がある場合、データベースがLeapcellで作成されている場合は、
そのグラフィカルインターフェイスを通じて簡単にSQLステートメントを実行できます。ウェブサイトのデータベース管理ページにアクセスし、上記のステートメントをSQLインターフェイスに貼り付けて実行します。
ステップ 2: コメントのビジネスロジックを実装する
次に、コメントの作成とクエリを処理する関数を作成します。
プロジェクトのルートディレクトリに新しいファイルcomments_service.py
を作成し、コメントに関連するビジネスロジックを格納します。
# comments_service.py import uuid from typing import List from sqlmodel import Session, select from models import Comment def get_comments_by_post_id(post_id: uuid.UUID, session: Session) -> List[Comment]: """指定された投稿IDのすべてのコメントを検索し、作成時間で昇順にソートします。""" statement = select(Comment).where(Comment.postId == post_id).order_by(Comment.createdAt) comments = session.exec(statement).all() return comments def create_comment(content: str, user_id: uuid.UUID, post_id: uuid.UUID, session: Session) -> Comment: """新しいコメントを作成します。""" new_comment = Comment(content=content, userId=user_id, postId=post_id) session.add(new_comment) session.commit() session.refresh(new_comment) return new_comment
get_comments_by_post_id
関数は、投稿の下にあるすべてのコメントを取得するために使用されます。create_comment
は、新しいコメントをデータベースに保存するために使用されます。モデルでRelationship
を正しく設定したため、後でテンプレートでcomment.user.username
を使用して、コメント投稿者のユーザー名を簡単にアクセスできます。
ステップ 3: コメントの送信と表示のためのルートを作成する
次に、コメント機能を投稿ページに統合する必要があります。これには、ユーザーが送信したコメントを受け取るバックエンドルートと、コメントを表示するために投稿詳細ページルートの更新の2つの部分が必要です。
1. コメントルートの作成
routrsフォルダに新しいファイル
comments.py`を作成します。
# routers/comments.py import uuid from fastapi import APIRouter, Depends, Form from fastapi.responses import RedirectResponse from sqlmodel import Session from database import get_session import comments_service from auth_dependencies import login_required router = APIRouter() @router.post("/posts/{post_id}/comments") def create_comment_for_post( post_id: uuid.UUID, content: str = Form(...), user: dict = Depends(login_required), # ユーザーがログインしていることを確認するための依存性注入 session: Session = Depends(get_session) ): # セッションからのユーザーIDは文字列なので、UUIDタイプに変換する必要があります user_id = uuid.UUID(user["id"]) comments_service.create_comment( content=content, user_id=user_id, post_id=post_id, session=session ) # コメントが正常に投稿された後、投稿ページにリダイレクトします return RedirectResponse(url=f"/posts/{post_id}", status_code=302)
このルートはPOST
リレーションシップのみを処理します。以前に作成したlogin_required
依存関係を使用して保護し、ログインユーザーのみがコメントを投稿できるようにします。コメントが正常に作成された後、ページは元の投稿詳細ページにリダイレクトされます。
2. メインアプリケーションおよび投稿ルートの更新
まず、作成したコメントルーターをmain.py
にマウントしましょう。
# main.py # ... その他のインポート from routers import posts, users, auth, comments # commentsルーターをインポート # ... app = FastAPI(lifespan=lifespan) # ... # ルーターを含める app.include_router(posts.router) app.include_router(users.router) app.include_router(auth.router) app.include_router(comments.router) # commentsルーターをマウント
次に、routers/posts.py
のget_post_by_id
関数を修正して、投稿詳細ページをレンダリングする際に、その投稿のすべてのコメントを取得して渡すようにします。
# routers/posts.py # ... インポート ... from auth_dependencies import get_user_from_session import comments_service # commentサービスをインポート # ... ルーターとテンプレートの定義 ... # ... その他のルート ... @router.get("/posts/{post_id}", response_class=HTMLResponse) def get_post_by_id( request: Request, post_id: uuid.UUID, session: Session = Depends(get_session), user: dict | None = Depends(get_user_from_session) ): post = session.get(Post, post_id) # この投稿のすべてのコメントを取得 comments = comments_service.get_comments_by_post_id(post_id, session) # 投稿、ユーザー、コメントをまとめてテンプレートに渡す return templates.TemplateResponse( "post.html", { "request": request, "post": post, "title": post.title, "user": user, "comments": comments # 追加 } )
ステップ 4: フロントエンドビューを更新する
最後のステップは、コメントリストとコメントフォームを表示するようにテンプレートファイルを変更することです。
templates/post.html
を開き、投稿コンテンツの下、バックリンクの上に次のコードを追加します。
<div class="post-content">{{ post.content | replace('\n', '<br>') | safe }}</div> </article> <section class="comments-section"> <h3>Comments</h3> <div class="comment-list"> {% if comments %} {% for comment in comments %} <div class="comment-item"> <p class="comment-content">{{ comment.content }}</p> <small> By <strong>{{ comment.user.username }}</strong> on {{ comment.createdAt.strftime('%Y-%m-%d') }} </small> </div> {% endfor %} {% else %} <p>No comments yet. Be the first to comment!</p> {% endif %} </div> {% if user %} <form action="/posts/{{ post.id }}/comments" method="POST" class="comment-form"> <h4>Leave a Comment</h4> <div class="form-group"> <textarea name="content" rows="4" placeholder="Write your comment here..." required></textarea> </div> <button type="submit">Submit Comment</button> </form> {% else %} <p><a href="/auth/login">Login</a> to leave a comment.</p> {% endif %} </section> <a href="/" class="back-link">← Back to Home</a> {% include "_footer.html" %}
{% for comment in comments %}
を使用して、すべてのコメントをループして表示します。comment.user.username
を通じて、コメント投稿者のユーザー名を直接表示できます。{% if user %}
を使用してユーザーがログインしているかどうかを確認します。ログインしている場合はコメントフォームが表示され、そうでない場合はログインリンクが表示されます。
ページをより見栄え良くするには、public/css/style.css
にスタイルを追加できます。
/* ... その他のスタイル ... */ .comments-section { margin-top: 3rem; border-top: 1px solid #eee; padding-top: 2rem; } .comment-list .comment-item { background: #f9f9f9; border: 1px solid #ddd; padding: 1rem; border-radius: 5px; margin-bottom: 1rem; } .comment-content { margin-top: 0; } .comment-item small { color: #666; } .comment-form { margin-top: 2rem; } .comment-form textarea { width: 100%; padding: 0.5rem; margin-bottom: 1rem; border: 1px solid #ccc; border-radius: 4px; }
実行とテスト
アプリケーションを再起動します。
uvicorn main:app --reload
ブラウザを開き、いずれかの投稿の詳細ページに移動します。新しいコメントセクションが表示されます。
コメントボックスに何か入力して送信します。ページがリフレッシュされた後、コメントリストに投稿したばかりのコメントが表示されます。
おめでとうございます。ブログにコメントシステムを正常に追加しました!
もちろん、現在のコメント機能はまだ非常に基本的です。次の記事では、作者がコメントに返信するロジックを実装して、この機能をさらに強化し、ブログのインタラクティブ性を次のレベルに引き上げます。