• トップ
  • ブログ一覧
  • 【番外編】Responderを使ってDjangoチュートリアルをやってみた【追加でアプリ改良】
  • 【番外編】Responderを使ってDjangoチュートリアルをやってみた【追加でアプリ改良】

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

    IT技術

    番外編~Responderを使ってDjangoチュートリアルをやってみた~

    今回の記事は、「【第7回】Responderを使ってDjangoチュートリアルをやってみた【adminページ改良編】」の続きにあたります。

    第1回から第7回まで読んでいない方は、ぜひ【第1回】から読んでくださいね!

    今回の記事は、「Djangoチュートリアル」には載っていませんが、私が第7回までに作ってきたアプリケーションを改良していきたいと思います!

    「Webアプリケーションとして、さらにユーザに満足してもらうには?」

    「Webアプリケーションに他に必要なものは?」

    など様々な視点で改良していきます。

    第1回はこちら

    featureImg2019.08.20【第1回】Responderを使ってDjangoチュートリアルをやってみた【プロジェクト作成編】第1回~Responderを使ってDjangoチュートリアルをやってみた~初期セットアップが、まだお済みでない方は前回...

    フッターの設定

    まず、最初はフッターの設定からやっていきましょう。

    これは、Responderとはあまり関係ありません。

    しかし、Webアプリケーションを作成する上で欠かせませんのでしっかりと設定しましょう。

    templates/layout.htmlのメインコンテンツ下部にフッターを作成

    1<!-- templates/layout.html -->
    2<!DOCTYPE html>
    3<html lang="ja">
    4<head>
    5    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    6    <link rel="stylesheet" href="{{ static + 'style.css' }}">
    7<!--    <link rel="stylesheet" href="{{ 'style.css' | static}}">-->
    8
    9    <meta charset="UTF-8">
    10    <title>Sample Polls Application</title>
    11</head>
    12<body style="background-color: #eeeeee">
    13<div class="container">
    14
    15    {% block content %}
    16    <!-- メインコンテンツ -->
    17    {% endblock %}
    18
    19</div>
    20<!-- New ここから-->
    21<br>
    22<hr>
    23<footer>
    24    <br>
    25    <div align="center">
    26        <p>Copyright &copy; RightCode Inc. All right reserved.</p>
    27        <br>
    28    </div>
    29<!-- New ここまで-->
    30</body>
    31</html>

    上のは例ですが、「コピーライト」や「サイトマップ」を入れるのが普通だと思います。

    ただ、これでは、質問が少ない時にフッターが上に位置してしまい、なんだかダサいですね・・・

    修正

    そこで、メインコンテンツに以下のようなスタイルを適用してあげます。

    1<!-- templates/layout.html -->
    2<!DOCTYPE html>
    3<html lang="ja">
    4<head>
    5    {% set static = '/static/' %}
    6    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    7    <link rel="stylesheet" href="{{ static + 'style.css' }}">
    8<!--    <link rel="stylesheet" href="{{ 'style.css' | static}}">-->
    9
    10    <meta charset="UTF-8">
    11    <title>Sample Polls Application</title>
    12</head>
    13<body style="background-color: #eeeeee">
    14<div style="display: flex; flex-direction: column; min-height: 80vh"> <!-- New -->
    15    <div class="container">
    16
    17    {% block content %}
    18    <!-- メインコンテンツ -->
    19    {% endblock %}
    20
    21    </div>
    22</div>  <!-- New -->
    23<br>
    24<hr>
    25<footer>
    26    <br>
    27    <div align="center">Copyright &copy; RightCode Inc. All right reserved.</div>
    28    <br>
    29</footer>
    30</body>
    31</html>

    <div style="display: flex; flex-direction: column; min-height: 80vh"> の部分です。

    確認

    これで、最低でも80vh分のメインコンテンツ領域が確保されます。

    (本当は直書きじゃなくてCSSに書いた方が良いです)

    投票結果をグラフ化して表示してみる

    今までの投票結果ページは、各選択肢に何票集まったかを表示させるだけの「質素なビュー」でした。

    しかし、せっかくPythonでWebアプリを開発しているのですから、matplotlibなど便利なモジュールを使ってスマートにしてみましょう!

    まず、投票結果から得たものを棒グラフとして描画します。

    棒グラフとして描画

    今回は、グラフをSVGとして保存し、それをビューに渡す、というような処理にします。

    (なぜPNGなどの拡張子にしないかというと、クッキーの関係で画像変更が、すぐにはブラウザ上で反映されないからです)

    1@api.route('/result/{q_id}')
    2class Result:
    3    async def on_get(self, req, resp, q_id):
    4        question = db.session.query(Question).filter(Question.id == q_id).first()
    5        choices = db.session.query(Choice).filter(Choice.question == q_id).all()
    6
    7        db.session.close()
    8
    9        """ ここから New """
    10        # ファイルの保存先とファイル名
    11        file = 'static/images/q_' + str(question.id)
    12
    13        choice_text_list = [choice.choice_text for choice in choices]
    14        choice_vote_list = [int(choice.votes) for choice in choices]
    15
    16        sum_votes = sum(choice_vote_list)
    17        vote_rates = [float(vote / sum_votes) for vote in choice_vote_list]
    18
    19        # タイトルと棒グラフ描画
    20        plt.title(question.question_text + ' (Total ' + str(sum_votes) + ' votes)')
    21        plt.bar(choice_text_list, choice_vote_list, color='skyblue')
    22
    23        # 割合と投票人数を表示する
    24        # 有効桁数は小数点第1位までにする。
    25        for x, y, v in zip(choice_text_list, vote_rates, choice_vote_list):
    26            plt.text(x, v, str(v)+' votes\n' + str(round(y*100, 1))+'%', ha='center', va='bottom')
    27
    28        # テキストが被らないように、y軸上限は常に最大投票数の1.2倍にしておく
    29        plt.ylim(0, round(max(choice_vote_list)*1.2))
    30
    31        plt.savefig(file + '.svg', format='svg')
    32        plt.close()
    33
    34        # matplotlibによって保存されたsvgファイルはHTMLで展開する際、冒頭4行はいらない (: あまりよくないコーディング)
    35        # いるのは<svg>タグ内のみ
    36        svg = open(file + '.svg', 'r').readlines()[4:]
    37        """ ここまで """
    38
    39        resp.content = api.template('result.html', question=question, choices=choices, svg=svg)  # svgもビューに渡す

    ちょっと工夫して描画する棒グラフは、少し凝ったものにしてみました。

    受け取ったsvgをビューで展開

    次に、受け取ったsvgをビューで展開します。

    このとき、< や> が勝手に&lt; や&gt; にエスケープされないように Jinja2 の{% autoescape false %} を使います。

    1{% extends "layout.html" %}
    2{% block content %}
    3<br>
    4<a href="/" class="btn btn-primary">戻る</a>
    5<h2>{{question['question_text']}}</h2>
    6<hr>
    7<p>質問公開日:{{question['pub_date']}}</p>
    8<br>
    9<h3>現在の投票結果</h3>
    10{% for choice in choices %}
    11    <p><span class="badge badge-info">{{choice['votes']}}</span> &nbsp; {{choice['choice_text']}}</p>
    12{% endfor %}
    13
    14<!-- ここからNew -->
    15{% for line in svg %}
    16    {% autoescape false %}{{line}}{% endautoescape %}
    17{% endfor %}
    18<!-- ここまで -->
    19
    20{% endblock %}

    確認

    描画するとこんな感じです。

    (matplotlib ver. 3.1.1 での表示)

    とても COOL ですね!

    APIドキュメントを作ってみる

    APIドキュメントとは、その名の通りAPIの使い方を示すドキュメントです。

    もし、外部のWebページから本アプリケーションを操作したり、連携したりする場合はAPIを公開する必要があります。

    (GoogleカレンダーやGoogleマップなどが身近ですね)

    その際、「利用規約や利用方法など細かくAPIについてまとめたもの」、すなわちAPIドキュメントが必要です。

    APIドキュメントを作成するときに、最近では「Swgger(OpenAPI)」と呼ばれるフレームワークがよく利用されます。

    Responderでは、このSwagger記法に則ったAPIドキュメントが簡単に作成できます

    本アプリケーションは、特にオープンAPIとして開発しているわけではありませんが、Responderの機能ということで紹介します。

    最低限の必要な設定

    一番最初に書いた、アプリケーションのAPIを作成する部分がありました。

    1api = responder.API()

    このAPIクラスに引数として必要事項を渡すことで、APIドキュメントを生成してくれます。

    早速やってみましょう。

    以下のように加筆してみてください。

    1api = responder.API(
    2    title='Polls Application with Responder',
    3    version='1.0',
    4    openapi='3.0.2',
    5    docs_route='/docs',
    6    description='This is a simple polls application referenced Django tutorials with Responder 1.3.1.',
    7    contact={
    8        'name': 'RightCode Inc. Support',
    9        'url': 'https://rightcode.co.jp/contact',
    10        'email': '****@abcdefg.com'
    11    }
    12)

    引数の名前から何を設定しているかはわかると思います。

    OpenAPIは、現在(2019/10/15)の最新版は「3.0.2」なので、openapi='3.0.2' としています。

    APIドキュメントのルーティングは、docs_route='/docs' です。

    確認

    では、早速サーバを立ち上げて、127.0.0.1:5042/docs にアクセスしてみましょう。

    Swagger によって、 APIドキュメントの大枠が作成されました!

    ちなみに、Swagger のソースは、http://127.0.0.1:5042/schema.yml にあります。

    モデルのスキーマ(構造)の追加

    まずは、モデルのスキーマからドキュメントに入れていきましょう。

    まず、コードが乱雑にならないように、新しく shemas.py というファイルを作りましょう。

    そうしたら、各モデルに対してフィールド情報をmarshmallow(マシュマロ)というライブラリを使って定義していきます。

    1from urls import api
    2from marshmallow import Schema, fields
    3
    4
    5@api.schema('Question')
    6class QuestionSchema(Schema):
    7    id = fields.Integer()
    8    question_text = fields.Str()
    9    pub_date = fields.DateTime()
    10
    11
    12@api.schema('Choice')
    13class ChoiceSchema(Schema):
    14    id = fields.Integer()
    15    question = fields.Integer()
    16    choice_text = fields.Str()
    17    votes = fields.Integer()
    18
    19
    20@api.schema('User')
    21class UserSchema(Schema):
    22    id = fields.Integer()
    23    username = fields.Str()
    24    password = fields.Str()

    このあたりは、ほぼ Responder のドキュメント通りです。

    あとは、このファイルを urls.pyでインポートします。

    1# .. 省略
    2
    3api = responder.API(
    4    title='Polls Application with Responder',
    5    version='1.0',
    6    openapi='3.0.2',
    7    docs_route='/docs',
    8    description='This is a simple polls application referenced Django tutorials with Responder 1.3.1.',
    9    contact={
    10        'name': 'RightCode Inc. Support',
    11        'url': 'https://rightcode.co.jp/contact',
    12        'email': '****@abcdefg.com'
    13    }
    14)
    15
    16# ModelSchemaをimport
    17import schemas
    18
    19# ... 省略

    早速、もう一度サーバを立ち上げ直して、ドキュメントを見てみましょう!

    モデルスキーマがドキュメントに追加されました!

    URLスキーマの追加

    あとは、URLによるレスポンスなどを追加してみましょう。

    試しに、ルート("/")でやってみます。

    今回は、かなり簡単な記述ですが、気になる方は Swagger の記法を調べてみてください。

    1@api.route('/')
    2class Index:
    3    """
    4    ---
    5    get:
    6        description: Get Question list except future questions
    7        responses:
    8           200:
    9               description: Success
    10    """
    11    def on_get(self, req, resp):  # ... 省略
    12
    13    def get_queryset(self, latest=5):  # ... 省略

    これで、サーバを立ち上げ直してドキュメントを確認してみます。

    確認

    実際に「Try it out」して実行(Execute)してみると、インデックスビューのボディがレスポンスされるのをWebページ上から確認できます。

    URLスキーマのサンプル

    次に、GETメソッドとPOSTメソッドによって、処理の違うログイン("/ad_login")についてのスキーマの例を書いてみます。

    あまり、有用ではありませんが、記述方法の参考になれば幸いです。

    1@api.route('/ad_login')
    2class AdLogin:
    3    """
    4    ---
    5    get:
    6        description: Redirect Login view (admin.html)
    7    post:
    8        description: If login successes, redirect admin page view (administrator.html).
    9        parameters:
    10            - name: username
    11              in: body
    12              required: true
    13              description: username
    14              schema:
    15                type: strings
    16                properties:
    17                    username:
    18                      type: string
    19                      example: hogehoge
    20
    21            - name: password
    22              in: body
    23              required: true
    24              description: password
    25              schema:
    26                type: strings
    27                properties:
    28                    password:
    29                        type: string
    30                        example: a1B2c3D4e5
    31        responses:
    32           200:
    33               description: Redirect administrator page.
    34
    35    """

    確認

    最低限必要な構成要素は、

    1"""
    2# ここにはコメントなど
    3---
    4# ここにSwagger(yml)を記述
    5"""

    でしょう。

    さいごに【番外編】

    Djangoチュートリアルを一通り終え、さらにその先までアプリ作成してみました。

    Responder の良い部分、使いにくい部分など、様々あるかと思います。

    Responder は、まだまだ発展途上なフレームワークと言えると思います。

    しかし、これからのアップデートで、さらに便利な機能がデフォルトで追加されると思うので、楽しみですね。

    今回、作成したアプリのソースは、Githubにあげています。

    【ソースはこちら】
    https://github.com/rightcode/ResponderTutorial

    少しだけディレクトリ構成などが異なっていて、本連載の「responder」ディレクトリは「ResponderTutorial/polls」にあたりますのでご注意を!

    連載終了!

    これで「Responderを使ってDjangoチュートリアルをやってみた」の連載は終了となります!

    全8回に渡ってご愛読いただいたみなさま、ありがとうございました!

    ぜひ、当ブログの他の記事も読んでみてくださいね!

    【全編まとめ】Responderを使ってDjangoチュートリアルをやってみた

    featureImg2019.10.25【まとめ編】Responderを使ってDjangoチュートリアルをやってみたResponderを使ってDjangoチュートリアルをやってみた~まとめ~ライトコード社長も今、イチオシのWEBフレー...

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

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
    featureImg2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...

    広告メディア事業部

    広告メディア事業部

    おすすめ記事