【第9回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【ブログトップを作る】
IT技術
第9回~
連載「Python Responder+Firestoreでモダンでサーバーレスなブログシステムを作ってみる」第9回目です。
前回は、「管理者ページの仕上げ」を行いました。
ブログトップを作ろう
今回は、「ユーザ (ブログ来訪者) が触れるビュー」を作っていきます。
今までバックエンド部分が多かったのですが、フロント側もしっかり作っていきましょう!
ブログトップページの構想を練る
まずは、ブログのトップページに必要なものを考えてみましょう。
- 記事一覧 (必須!)
- ロゴとメニューバー (+各個別ページ)
上記に加え、「ブログ本体のサムネ」もあれば、もっといいかもしれません。
それでは、さっそく実装していきましょう!
記事一覧を取得する
はじめに、「記事一覧」を取得します。
以前作ったdef get_articles(unreleased: bool) 関数を使って取得しましょう。
コント―ラ修正
そして、久々にルートのコントローラを修正していきます。
1# controllers.py
2@api.route('/')
3def index(req, resp):
4 articles = get_articles(unreleased=False) # 公開済みのものだけ
5
6 resp.html = api.template('index.html',
7 title='',
8 articles=articles)
ビューを書いて実行
動作確認がてらビューを適当に書いて実行してみると、
1<!--
2templates/index.html
3トップページ
4-->
5
6{% extends "layout.html" %}
7{% block content %}
8
9<br>
10<h1>My Blog Name</h1>
11<div class="main-container">
12 <div class="article-main">
13 {% for art in articles %}
14 <p>{{ art['title'] }}</p>
15 {% endfor %}
16 </div>
17</div>
エラーが発生...??
1google.api_core.exceptions.FailedPrecondition: 400 The query requires an index. You can create it here: https:// ******
どうやら、クエリが上手く取得できていない様子。
調べてみると、
とのこと。
つまり、「公開済みか否か」「最終更新日での並び替え」は別で行う必要があるのです。
修正の上、ふたたび挑戦!
そこで、以下のように修正してみました。
1def get_articles(unreleased: bool = True):
2 if unreleased:
3 pass # 省略
4
5 else:
6 articles = [art.to_dict()
7 for art in
8 db.collection('articles').order_by(
9 'last_update',
10 direction=Query.DESCENDING
11 ).stream()
12 ]
13 articles = [art for art in articles if art['released']] # 公開済みのものだけにする
14
15 return articles
これで、エラーもなく上手く動作しました!
気を取り直して実装
それでは、本格的に実装していきましょう。
トップページでは、弊社ブログのように「記事を取得」して、「ボックス状に記事を陳列」させます。
コントロ―ラ修正
まずは、コントローラを少し修正します。
1@api.route('/')
2def index(req, resp):
3 articles = get_articles(unreleased=False) # 公開済みのものだけ
4
5 # サムネは別で保持
6 md = markdown.Markdown()
7 thumbnails = [
8 md.convert(art['thumbnail']) for art in articles
9 ]
10
11 resp.html = api.template('index.html',
12 title='',
13 thumbnails=thumbnails,
14 articles=articles)
ビューをデザイン
続いて、ビューをデザインしていきます。
1<!--
2templates/index.html
3トップページ
4-->
5
6{% extends "layout.html" %}
7{% block content %}
8
9<br>
10<h1>My Blog Name</h1>
11<div class="main-container">
12 <div class="top">
13 <h2>ブログ一覧</h2>
14 <div class="articles">
15 {% for art in articles %}
16 <a href="/category/{{art['category']}}/{{art['slug']}}">
17 <div class="single">
18 <p>
19 {% autoescape false %}
20 <div class="thumbnail">{{ thumbnails[loop.index - 1] }}</div>
21 {% endautoescape %}
22 {{ art['title'] }}<br>
23 <span class="category">{{ art['category'] }}</span>
24 <div class="info">{{ art['last_update'].strftime('%Y.%m.%d') }}: {{ art['author'] }}</div>
25 </p>
26 </div>
27 </a>
28 {% endfor %}
29 </div>
30 </div>
31
32</div>
33
34{% endblock %}
1.top{
2 width: 100%;
3 margin: 2.0em 0;
4}
5.articles{
6 width: 100%;
7}
8.articles .single{
9 display: inline-block;
10 vertical-align: top;
11 width: 22.5%;
12 height: 300px;
13 color: #333333;
14 border: 1px solid #333333;
15 background-color: #fff;
16 border-radius: 5px;
17 padding: 10px 10px;
18 margin: 5px 1px;
19
20}
21.articles .single:hover{
22 opacity: 0.7;
23}
24.article .single .thumbnail{
25 text-align: center;
26}
27.articles .single .thumbnail img{
28 height: 150px;
29 width: 100%;
30 margin: auto;
31 object-fit: contain;
32}
33.articles .single .category{
34 background-color: #2b52cd;
35 color: #fff;
36 border-radius: 3px;
37 padding: 2px 5px;
38}
39.articles .single .info{
40 font-size: smaller;
41 color: #888888;
42}
完成!
こんな感じになりました!
メインメニューの作成
次に、「メインメニュー」を作っていきましょう。
これは Python も Firestore も関係ないので、サクサクいきますよ!
メインメニューの内訳
今回、メインメニューには、「ロゴ」と以下の「リンクメニュー」を作成しようと思います。
- Top: トップリンク
- About: このブログについて
- Profile: 運営者について
- Contact: お問い合わせ
なお、各個別ページの作成は割愛します。
ロゴとメニューのレイアウトを決める
「ロゴ」と「メインメニュー」を、一直線に並べるようなデザインにします。
コード
メニューはリストを使うので、コード自体は以下のような感じです。
1<header>
2 <div class="header">
3 <a href="/" class="logo">
4 <img src="/static/theme/logo.png" alt="My Blog Name">
5 </a>
6
7 <div class="main-menu">
8 <ul>
9 <li><a href="/">Top</a></li>
10 <li><a href="/about">About</a></li>
11 <li><a href="/profile">Profile</a></li>
12 <li><a href="https://twitter.com">Twitter</a></li>
13 <li><a href="/contact">Contact</a></li>
14 </ul>
15 </div>
16 </div>
17</header>
CSS
CSSの方は、こんな感じです。
最低限のシンプルなものなので、デザインはお好きなものを作ってみてくださいね。
1header{
2 width: 100%;
3 height: 100px;
4}
5.header{
6 display: flex;
7 margin: 10px 10px;
8}
9.header .logo{
10 display: inline-block;
11 vertical-align: top;
12 width: 25%;
13 height: auto;
14 margin: 0 0;
15 padding: 0 10px;
16}
17.header .main-menu{
18 width: 75%;
19 margin: 0 0;
20 text-align: center;
21 display: inline-block;
22 vertical-align: top;
23}
24.header .main-menu ul li{
25 float: left;
26 width: 15%;
27 display: inline;
28 background-color: #1e366a;
29 color: #fff;
30 border: 1px solid #1f3057;
31}
32.header .main-menu ul li:hover{
33 opacity: 0.7;
34}
35.header .main-menu ul li a{
36 display: block;
37 color: #fff;
38}
見た目
こんな感じになりました。
筆者が即席で作ったロゴのクオリティは置いておいて、やはりロゴとメニューが揃うとブログっぽいですね!
header.htmlとして共有する
そうしたら、これを共有ファイルにして、「index.html」と「single.html」にインクルードするようにします。
インクルードは{% include 'header.html' %} でOKです。
第10回へつづく!
今回は、「ブログのトップページの整備」を終わらせました。
残るは、「カテゴリページ」のみ!
その他デザインも整える必要はありますが、機能としてはこれでラストになります。
ラストスパート、頑張っていきましょう!
次回の記事はこちら
第1回はこちら
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit