• トップ
  • ブログ一覧
  • 【第9回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【ブログトップを作る】
  • 【第9回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【ブログトップを作る】

    広告メディア事業部広告メディア事業部
    2020.08.17

    IT技術

    第9回~モダンなフレームワークの使い方を学びながらブログシステムを構築~

    連載「Python Responder+Firestoreでモダンでサーバーレスなブログシステムを作ってみる」第9回目です。

    前回は、「管理者ページの仕上げ」を行いました。

    featureImg2020.08.13【第8回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【管理者ページの仕上げ】第8回~モダンなフレームワークの使い方を学びながらブログシステムを構築~連載「Python Responder + F...

    ブログトップを作ろう

    今回は、「ユーザ (ブログ来訪者) が触れるビュー」を作っていきます。

    今までバックエンド部分が多かったのですが、フロント側もしっかり作っていきましょう!

    ブログトップページの構想を練る

    まずは、ブログのトップページに必要なものを考えてみましょう。

    1. 記事一覧 (必須!)
    2. ロゴとメニューバー (+各個別ページ)

    上記に加え、「ブログ本体のサムネ」もあれば、もっといいかもしれません。

    それでは、さっそく実装していきましょう!

    記事一覧を取得する

    はじめに、「記事一覧」を取得します。

    以前作った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 も関係ないので、サクサクいきますよ!

    メインメニューの内訳

    今回、メインメニューには、「ロゴ」と以下の「リンクメニュー」を作成しようと思います。

    1. Top: トップリンク
    2. About: このブログについて
    3. Profile: 運営者について
    4. Twitter
    5. 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回へつづく!

    今回は、「ブログのトップページの整備」を終わらせました。

    残るは、「カテゴリページ」のみ!

    その他デザインも整える必要はありますが、機能としてはこれでラストになります。

    ラストスパート、頑張っていきましょう!

    次回の記事はこちら

    featureImg2020.08.20【第10回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【ブログ完成!!】第10回~モダンなフレームワークの使い方を学びながらブログシステムを構築~連載「Responder + Firesto...

    第1回はこちら

    featureImg2020.07.17【第1回】Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた【初期セットアップ編】モダンなフレームワークの使い方を学びながらブログシステムを構築今回より、また新たに Python WebAPI 関連の...

    こちらの記事もオススメ!

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...

    featureImg2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...

    広告メディア事業部

    広告メディア事業部

    おすすめ記事