【第10回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【ブログ完成!!】
IT技術
第10回~
連載「Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた」も第10回目です。
前回は、「ブログトップの作成」を行いました。
ブログを完成させよう
最終回となる今回は、ブログにとって重要なページを作ったり、システムの微調整をしていきましょう。
具体的には、以下の3点を実装していきます。
- カテゴリごとのページ
- sitemap.xml の自動生成
- フッター整備
カテゴリ(タグ)ごとのページ
まずは、「カテゴリごとのページ」を作っていきましょう。
カテゴリごとのページとは、「/category/{category}」のことです。
表示するものは、前回作成した「index.html」を流用します。
コントローラの作成
では、コントローラを作成します。
やることは以下の2つだけなので、とても簡単です!
- URL からカテゴリを抽出
- ページに反映
スラッグからカテゴリ名を取得する関数を作る
ここで、「スラッグからカテゴリ名を取得する関数」を作りましょう。
これを使うと、ページに『「技術」の記事一覧』のような表示が出来るようになります。
1# models.py
2def get_category_name(slug):
3 # slugからIDを取得
4 c_id = [cat.id for cat in db.collection('categories').where('slug', '==', slug).stream()][0]
5
6 # IDを用いてデータを取得
7 category = db.collection('categories').document(c_id).get().to_dict()
8
9 return category['name'] # nameだけ返す
ルーティング
そうしたら、ルーティングしましょう。
1# controllers.py
2@api.route('/category/{category}')
3def category(req, resp, *, category):
4 articles = get_articles(unreleased=False) # 公開済みのものだけ
5
6 # カテゴリ抽出
7 articles = [
8 art for art in articles
9 if art['category'] == category
10 ]
11
12 md = markdown.Markdown(extensions=['tables'])
13 thumbnails = [
14 md.convert(art['thumbnail']) for art in articles
15 ]
16
17 resp.html = api.template('index.html',
18 title=f'「{get_category_name(category)}」の記事一覧',
19 thumbnails=thumbnails,
20 articles=articles, )
ほとんどトップページと同じですね。
ビューも一部変更
ビューは、以下の部分だけ変更します。
1<!-- <h2>ブログ一覧</h2> -->
2<h2>{{ title }}</h2>
忘れずに、「/」のコントローラも変更しておきましょうね。
1# controllers.py
2@api.route('/')
3def index(req, resp):
4 #~~~ 省略 ~~~#
5
6 resp.html = api.template('index.html',
7 title='ブログ一覧', # new
8 thumbnails=thumbnails,
9 articles=articles,)
これで良さそうです!
動作確認
それでは、動作確認してみましょう。
良さそうですね!
細かい部分を整える
先ほど、「スラッグからカテゴリ名を取得する関数」を作成しましたね。
さっそく、「記事個別ページ」や「記事一覧」に反映させてみましょう。
記事個別ページ
1resp.html = api.template('single.html',
2 title=article['title'],
3 cat_name=get_category_name(article['category']), # new
4 article=article,
5 whats_new=whats_new,
6 categories=categories)
ここで、article['category'] = get_category_name(article['category']) をやってしまうのは、絶対に NG です!
カテゴリの URL も同じになってしまうので、注意してくださいね。
記事一覧
こちらも同様です。
1@api.route('/')
2def index(req, resp):
3 articles = get_articles(unreleased=False) # 公開済みのものだけ
4
5 # new
6 art_categories = [
7 get_category_name(art['category'])
8 for art in articles
9 ]
10
11 md = markdown.Markdown(extensions=['tables'])
12 thumbnails = [
13 md.convert(art['thumbnail']) for art in articles
14 ]
15
16 resp.html = api.template('index.html',
17 title='ブログ一覧',
18 thumbnails=thumbnails,
19 art_categories=art_categories, # new
20 articles=articles,)
「/category/{category}」のコードを最適化
「/category/{category}」も同様ですが、こちらは全て同じカテゴリ名なので、少しだけコードを最適化できます。
1# controllers.py
2@api.route('/category/{category}')
3def category(req, resp, *, category):
4 articles = get_articles(unreleased=False) # 公開済みのものだけ
5
6 # カテゴリ抽出
7 articles = [
8 art for art in articles
9 if art['category'] == category
10 ]
11
12 md = markdown.Markdown(extensions=['tables'])
13 thumbnails = [
14 md.convert(art['thumbnail']) for art in articles
15 ]
16
17 # カテゴリ名 呼び出すのは一度だけ
18 cat_name = get_category_name(category)
19
20 resp.html = api.template('index.html',
21 title=f'「{cat_name}」の記事一覧',
22 art_categories=[cat_name for _ in articles], # 全て同じ
23 thumbnails=thumbnails,
24 articles=articles)
関数の呼び出しは少なめに!
今回の関数は、データベースとのやり取りを行うため、重めの仕様になっています。
そのため、できるだけ関数の呼び出しが少なくなるよう心がけましょう。
ちなみに、「タグページ」もこれとほとんど同じなので、割愛します。
sitemap.xml の自動生成
サイトマップは、ブログの SEO 的に超重要です。
理想としては、記事が追加・更新・削除されるたびに、「sitemap.xml」を更新するのが良いですね。
では、さっそく実装していきましょう!
ルーティング
「/sitemap.xml」でサイトマップをしっかり取得できないと、サイトマップを送信してもクロールされません。
基本的には、以下のような仕様にすれば OK です。
- テキストとして「sitemap.xml」を開く
- ヘッダーの Content-type を「application/xml」にする
1# controllers.py
2@api.route('/sitemap.xml')
3def sitemap(req, resp):
4 resp.text = open('sitemap.xml').read()
5 resp.headers['Content-type'] = 'application/xml'
自動生成する関数を作る
何かのファイルと一緒でも良いのですが、筆者はsitemap_generator.py を新たに作り実装しました。
「sitemap.xml」の基本は、以下の通りです。
1<?xml version="1.0" encoding="UTF-8"?>
2<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
3 <url>
4 <loc>https://sample.com/</loc>
5 <lastmod>2020-06-10</lastmod>
6 <changefreq>always</changefreq>
7 <priority>1.0</priority>
8 </url>
9</urlset>
記事の URL を追加
これにプラスして、記事の URL を追加していきます。
少しゴリ押しですが、以下のような感じでファイルを上書きしていきます。
1from models import get_articles
2from datetime import datetime
3
4
5DOMAIN = 'https://sample.com' # 適宜変える
6
7
8def update_sitemap(changefreq='monthly', priority=0.7):
9 """
10 sitemapを全て更新する
11 カテゴリページは含めない
12 :param changefreq: 記事の更新頻度
13 :param priority: 記事の優先度
14 :return:
15 """
16 # 公開済みのものだけ取得
17 articles = get_articles(False)
18
19 # URL
20 locs = [
21 '{}/category/{}/{}'.format(DOMAIN, article['category'], article['slug'])
22 for article in articles
23 ]
24
25 # 最終更新日
26 lastmods = [
27 article['last_update'].strftime('%Y-%m-%d')
28 for article in articles
29 ]
30
31 # 上書きする
32 sitemap = open('sitemap.xml', 'w')
33 sitemap.write('<?xml version="1.0" encoding="UTF-8"?>\n')
34 sitemap.write('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n')
35 sitemap.write(' <url>\n')
36 sitemap.write(' <loc>{}/</loc>\n'.format(DOMAIN))
37 sitemap.write(' <lastmod>{}</lastmod>\n'.format(datetime.now().strftime('%Y-%m-%d'))) # topは今日
38 sitemap.write(' <changefreq>always</changefreq>\n')
39 sitemap.write(' <priority>1.0</priority>\n')
40 sitemap.write(' </url>\n')
41
42 # 記事のサイトマップ
43 for loc, lastmod in zip(locs, lastmods):
44 sitemap.write(' <url>\n')
45 sitemap.write(' <loc>{}/</loc>\n'.format(loc))
46 sitemap.write(' <lastmod>{}</lastmod>\n'.format(lastmod))
47 sitemap.write(' <changefreq>{}</changefreq>\n'.format(changefreq))
48 sitemap.write(' <priority>{}</priority>\n'.format(priority))
49 sitemap.write(' </url>\n')
50
51 sitemap.write('</urlset>\n')
52 sitemap.close()
「更新」「新規追加」「削除」部分に追記
そうしたら、上記ファイルを各所で呼びます。
例として、記事の更新をするコントローラでは以下のような感じ。
1@api.route('/admin/article/update')
2async def article_update(req, resp):
3 #~~~ 省略 ~~~#
4
5 # update
6 update_article_by_slug(
7 original_slug,
8 title,
9 thumbnail,
10 description,
11 slug,
12 contents,
13 category,
14 tags,
15 released
16 )
17
18 if released: # 公開になった時に更新
19 update_sitemap()
20
21 api.redirect(resp, '/admin')
22 return
他にも、新規追加・削除の部分に追記しておきましょう。
なお、カテゴリページを含めるかは、みなさん次第です。
動作チェック
試しに、何か記事を更新してみます。
上手く反映できていそうですね!
フッターを作る
最後に、フッターを作成しましょう。
これは Python も Firestore も関係ありませんが、ブログの見た目的には大事です。
「footer.html」を作ってインクルード
「footer.html」を作成し、「layout.html」にインクルードします。
1<footer>
2 <br>
3 <ul>
4 <li><a href="/">Top</a></li>
5 <li><a href="/about">About</a></li>
6 <li><a href="/profile">Profile</a></li>
7 <li><a href="https://twitter.com">Twitter</a></li>
8 <li><a href="/contact">Contact</a></li>
9 <li><a href="/policy">プライバシーポリシー</a></li>
10
11 </ul>
12 <div class="logo"><a href="/"><img src="/static/theme/footer.png" alt="My Blog Name"></a></div>
13 <p>Copyright© My Blog Name all rights reserved.</p>
14 <br>
15</footer>
1<!--
2templates/layout.html
3-->
4
5<!DOCTYPE html>
6<html lang="ja">
7<head>
8 <title>My Blog Name {{ title }}</title>
9 <meta charset="UTF-8">
10 <meta http-equiv="X-UA-Compatible" content="IE=edge">
11 <meta name="format-detection" content="telephone=no">
12 <meta name="viewport" content="width=device-width">
13
14 <link rel="stylesheet" href="/static/style.css">
15</head>
16<body>
17<div class="container">
18 {% block content %}
19 <!-- メインコンテンツ -->
20 {% endblock %}
21</div>
22{% include 'footer.html' %}
23</body>
24</html>
ロゴと CSS を用意
デザインは、白色のロゴと、CSS を用意します。
1footer{
2 background-color: #1e366a;
3 color: white;
4 text-align: center;
5}
6footer .logo{
7 display: block;
8 width: 25%;
9 margin: auto;
10}
11footer ul{
12 display: flex;
13 justify-content: center;
14}
15footer ul li{
16 display: table-cell;
17 color: white;
18}
19footer ul li a{
20 display: block;
21 color: #fff;
22}
完成!
こんな感じでどうでしょう!
あくまでサンプルなので、みなさんは自由にこだわってくださいね!
プライバシーポリシーは、アドセンスなどで必要になってくるので、しっかり作りましょうね。
番外編へつづく!
お疲れ様でした!
「Python Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみる」これにて完結です!
全10回にもなる長期連載でしたが、ようやくブログシステムの構築が終わりました。
この記事を通して、「Responder (Python)」と「Cloud Firestore」「Firebase Auth」との連携について、しっかり理解できたと思います。
オリジナルのブログシステムを作ってみよう
この連載で作成したシステムはとてもシンプルなので、自分好みにカスタマイズするのがおすすめです。
参考までに、いくつかアイディアを挙げておきますね。
- 記事の CSS デザイン (code, quote, ...) などを整える
- 関連記事を表示する機能を実装してみる (キーワードを指定したり、タイトルの類似度を計算する)
- SNS シェアボタンを作る
- 月別アーカイブを作る
ぜひ、この連載を参考にして、自分色のブログシステムを構築してみてくださいね!
最後になりますが、第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
おすすめ記事
immichを知ってほしい
2024.10.31