掲示板アプリケーションのトップページにおいて、投稿一覧を表示するためのバックエンドのデータモデル関連付けと、フロントエンドのレイアウト実装について解説します。ユーザーと投稿データを紐付け、トップページで一覧としてレンダリングする流れを構築します。
1. データモデルの拡張と関連付け
まず、投稿(スレッド)とユーザー(メンバー)を関連付けるために、SQLAlchemyのモデル定義を更新します。外部キーとリレーションシップを設定し、投稿データからユーザー情報へ簡単にアクセスできるようにします。
class Thread(db.Model):
__tablename__ = 'threads'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
subject = db.Column(db.String(255), nullable=False)
body = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
board_id = db.Column(db.Integer, db.ForeignKey('boards.id'))
board = db.relationship('Board', backref='threads')
member_id = db.Column(db.String(64), db.ForeignKey('members.id'), nullable=False)
member = db.relationship('Member', backref='threads')
モデルを変更した後は、データベースのマイグレーションを適用します。既存のテストデータは外部キー制約に違反する可能性があるため、一度テーブルをクリアしてから flask db migrate および flask db upgrade を実行し、新しいスキーマを反映させます。
2. ビュー関数(ルート)の更新
投稿作成時の処理にユーザー情報の紐付けを追加し、トップページのルートで必要なデータをクエリしてテンプレートに渡します。
@blueprint.route('/create_thread/', methods=['GET', 'POST'])
@login_required
def create_thread():
if request.method == 'POST':
# フォームのバリデーション処理...
new_thread = Thread(
subject=form.subject.data,
body=form.body.data,
board_id=form.board_id.data,
member_id=current_user.id # ログインユーザーを著者として設定
)
db.session.add(new_thread)
db.session.commit()
return redirect(url_for('front.home'))
return render_template('front/create_thread.html')
@blueprint.route('/')
def home():
top_banners = Banner.query.order_by(Banner.weight.desc()).limit(4).all()
all_boards = Board.query.all()
recent_threads = Thread.query.order_by(Thread.created_at.desc()).all()
context = {
'banners': top_banners,
'boards': all_boards,
'threads': recent_threads
}
return render_template('front/home.html', **context)
3. フロントエンドテンプレートの構築
Jinja2テンプレートを使用して、トップページに投稿一覧のHTML構造を構築します。ソート用のタブと、各投稿のサムネイル・メタデータを表示するリストを実装します。
<div class="thread-tabs">
<a href="#" class="tab-link active">新着順</a>
<a href="#" class="tab-link">人気投稿</a>
<a href="#" class="tab-link">高評価順</a>
<a href="#" class="tab-link">コメント順</a>
</div>
<ul class="thread-list">
{% for thread in threads %}
<li class="thread-item">
<div class="avatar-wrapper">
<img src="{{ thread.member.avatar_url or url_for('static', filename='img/default_avatar.png') }}" alt="Avatar">
</div>
<div class="thread-details">
<h3 class="thread-subject">{{ thread.subject }}</h3>
<div class="thread-meta">
<span class="meta-author">{{ thread.member.nickname }}</span>
<span class="meta-date">{{ thread.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
<span class="meta-comments">コメント: 0</span>
<span class="meta-views">閲覧: 0</span>
</div>
</div>
</li>
{% endfor %}
</ul>
4. CSSスタイリングの適用
Flexboxを活用して、アバターとテキスト情報を横並びに配置し、モダンでクリーンなリストデザインを適用します。
.thread-container {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 15px;
margin-top: 24px;
background-color: #fff;
}
.thread-tabs {
display: flex;
gap: 10px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.tab-link {
text-decoration: none;
color: #555;
padding: 6px 12px;
border-radius: 4px;
font-size: 14px;
}
.tab-link.active {
background-color: #f0f0f0;
color: #333;
font-weight: bold;
}
.thread-list {
list-style: none;
margin-top: 16px;
padding: 0;
}
.thread-item {
display: flex;
align-items: flex-start;
padding: 12px 0;
border-bottom: 1px solid #f5f5f5;
}
.thread-item:last-child {
border-bottom: none;
}
.avatar-wrapper img {
width: 48px;
height: 48px;
border-radius: 50%;
object-fit: cover;
margin-right: 16px;
}
.thread-details {
flex: 1;
}
.thread-subject {
font-size: 16px;
font-weight: 500;
color: #222;
margin: 0 0 8px 0;
}
.thread-meta {
display: flex;
gap: 16px;
font-size: 12px;
color: #999;
}