
【第9回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【ブログトップを作る】
2021.12.20
第9回~モダンなフレームワークの使い方を学びながらブログシステムを構築~
連載「Python Responder+Firestoreでモダンでサーバーレスなブログシステムを作ってみる」第9回目です。
前回は、「管理者ページの仕上げ」を行いました。
ブログトップを作ろう
今回は、「ユーザ (ブログ来訪者) が触れるビュー」を作っていきます。
今までバックエンド部分が多かったのですが、フロント側もしっかり作っていきましょう!
ブログトップページの構想を練る
まずは、ブログのトップページに必要なものを考えてみましょう。
- 記事一覧 (必須!)
- ロゴとメニューバー (+各個別ページ)
上記に加え、「ブログ本体のサムネ」もあれば、もっといいかもしれません。
それでは、さっそく実装していきましょう!
記事一覧を取得する
はじめに、「記事一覧」を取得します。
以前作った def get_articles(unreleased: bool) 関数を使って取得しましょう。
コント―ラ修正
そして、久々にルートのコントローラを修正していきます。
1 2 3 4 5 6 7 8 | # controllers.py @api.route('/') def index(req, resp): articles = get_articles(unreleased=False) # 公開済みのものだけ resp.html = api.template('index.html', title='', articles=articles) |
ビューを書いて実行
動作確認がてらビューを適当に書いて実行してみると、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!-- templates/index.html トップページ --> {% extends "layout.html" %} {% block content %} <br> <h1>My Blog Name</h1> <div class="main-container"> <div class="article-main"> {% for art in articles %} <p>{{ art['title'] }}</p> {% endfor %} </div> </div> |
エラーが発生...??
1 | google.api_core.exceptions.FailedPrecondition: 400 The query requires an index. You can create it here: https:// ****** |
どうやら、クエリが上手く取得できていない様子。
調べてみると、

「並べ替えは同じフィールドで行う必要があります。」
とのこと。
つまり、「公開済みか否か」「最終更新日での並び替え」は別で行う必要があるのです。
修正の上、ふたたび挑戦!
そこで、以下のように修正してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | def get_articles(unreleased: bool = True): if unreleased: pass # 省略 else: articles = [art.to_dict() for art in db.collection('articles').order_by( 'last_update', direction=Query.DESCENDING ).stream() ] articles = [art for art in articles if art['released']] # 公開済みのものだけにする return articles |
これで、エラーもなく上手く動作しました!
気を取り直して実装
それでは、本格的に実装していきましょう。
トップページでは、弊社ブログのように「記事を取得」して、「ボックス状に記事を陳列」させます。
コントロ―ラ修正
まずは、コントローラを少し修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @api.route('/') def index(req, resp): articles = get_articles(unreleased=False) # 公開済みのものだけ # サムネは別で保持 md = markdown.Markdown() thumbnails = [ md.convert(art['thumbnail']) for art in articles ] resp.html = api.template('index.html', title='', thumbnails=thumbnails, articles=articles) |
ビューをデザイン
続いて、ビューをデザインしていきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <!-- templates/index.html トップページ --> {% extends "layout.html" %} {% block content %} <br> <h1>My Blog Name</h1> <div class="main-container"> <div class="top"> <h2>ブログ一覧</h2> <div class="articles"> {% for art in articles %} <a href="/category/{{art['category']}}/{{art['slug']}}"> <div class="single"> <p> {% autoescape false %} <div class="thumbnail">{{ thumbnails[loop.index - 1] }}</div> {% endautoescape %} {{ art['title'] }}<br> <span class="category">{{ art['category'] }}</span> <div class="info">{{ art['last_update'].strftime('%Y.%m.%d') }}: {{ art['author'] }}</div> </p> </div> </a> {% endfor %} </div> </div> </div> {% endblock %} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | .top{ width: 100%; margin: 2.0em 0; } .articles{ width: 100%; } .articles .single{ display: inline-block; vertical-align: top; width: 22.5%; height: 300px; color: #333333; border: 1px solid #333333; background-color: #fff; border-radius: 5px; padding: 10px 10px; margin: 5px 1px; } .articles .single:hover{ opacity: 0.7; } .article .single .thumbnail{ text-align: center; } .articles .single .thumbnail img{ height: 150px; width: 100%; margin: auto; object-fit: contain; } .articles .single .category{ background-color: #2b52cd; color: #fff; border-radius: 3px; padding: 2px 5px; } .articles .single .info{ font-size: smaller; color: #888888; } |
完成!
こんな感じになりました!

適当に記事を追加して確認
メインメニューの作成
次に、「メインメニュー」を作っていきましょう。
これは Python も Firestore も関係ないので、サクサクいきますよ!
メインメニューの内訳
今回、メインメニューには、「ロゴ」と以下の「リンクメニュー」を作成しようと思います。
- Top: トップリンク
- About: このブログについて
- Profile: 運営者について
- Contact: お問い合わせ
なお、各個別ページの作成は割愛します。
ロゴとメニューのレイアウトを決める
「ロゴ」と「メインメニュー」を、一直線に並べるようなデザインにします。
コード
メニューはリストを使うので、コード自体は以下のような感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <header> <div class="header"> <a href="/" class="logo"> <img src="/static/theme/logo.png" alt="My Blog Name"> </a> <div class="main-menu"> <ul> <li><a href="/">Top</a></li> <li><a href="/about">About</a></li> <li><a href="/profile">Profile</a></li> <li><a href="https://twitter.com">Twitter</a></li> <li><a href="/contact">Contact</a></li> </ul> </div> </div> </header> |
CSS
CSSの方は、こんな感じです。
最低限のシンプルなものなので、デザインはお好きなものを作ってみてくださいね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | header{ width: 100%; height: 100px; } .header{ display: flex; margin: 10px 10px; } .header .logo{ display: inline-block; vertical-align: top; width: 25%; height: auto; margin: 0 0; padding: 0 10px; } .header .main-menu{ width: 75%; margin: 0 0; text-align: center; display: inline-block; vertical-align: top; } .header .main-menu ul li{ float: left; width: 15%; display: inline; background-color: #1e366a; color: #fff; border: 1px solid #1f3057; } .header .main-menu ul li:hover{ opacity: 0.7; } .header .main-menu ul li a{ display: block; color: #fff; } |
見た目
こんな感じになりました。

ブログメインメニュー
筆者が即席で作ったロゴのクオリティは置いておいて、やはりロゴとメニューが揃うとブログっぽいですね!
header.htmlとして共有する
そうしたら、これを共有ファイルにして、「index.html」と「single.html」にインクルードするようにします。
インクルードは {% include 'header.html' %} でOKです。

記事ページもそれっぽくなった
第10回へつづく!
今回は、「ブログのトップページの整備」を終わらせました。
残るは、「カテゴリページ」のみ!
その他デザインも整える必要はありますが、機能としてはこれでラストになります。
ラストスパート、頑張っていきましょう!
次回の記事はこちら
第1回はこちら
こちらの記事もオススメ!
書いた人はこんな人

- 「好きを仕事にするエンジニア集団」の(株)ライトコードです!
ライトコードは、福岡、東京、大阪の3拠点で事業展開するIT企業です。
現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。
いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。
システム開発依頼・お見積もり大歓迎!
また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」「WEBディレクター」を積極採用中です!
インターンや新卒採用も行っております。
以下よりご応募をお待ちしております!
https://rightcode.co.jp/recruit
ITエンタメ10月 13, 2023Netflixの成功はレコメンドエンジン?
ライトコードの日常8月 30, 2023退職者の最終出社日に密着してみた!
ITエンタメ8月 3, 2023世界初の量産型ポータブルコンピュータを開発したのに倒産!?アダム・オズボーン
ITエンタメ7月 14, 2023【クリス・ワンストラス】GitHubが出来るまでとソフトウェアの未来