【番外編】Responderを使ってDjangoチュートリアルをやってみた【追加でアプリ改良】
IT技術
今回の記事は、「【第7回】Responderを使ってDjangoチュートリアルをやってみた【adminページ改良編】」の続きにあたります。
第1回から第7回まで読んでいない方は、ぜひ【第1回】から読んでくださいね!
今回の記事は、「Djangoチュートリアル」には載っていませんが、私が第7回までに作ってきたアプリケーションを改良していきたいと思います!
「Webアプリケーションとして、さらにユーザに満足してもらうには?」
「Webアプリケーションに他に必要なものは?」
など様々な視点で改良していきます。
第1回はこちら
フッターの設定
まず、最初はフッターの設定からやっていきましょう。
これは、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 © 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 © 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をビューで展開します。
このとき、< や> が勝手に< や> にエスケープされないように 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> {{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チュートリアルをやってみた
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit