【第3回】Responderを使ってDjangoチュートリアルをやってみた【データベース操作編】
IT技術
前回「【第2回】Responderを使ってDjangoチュートリアルをやってみた【データベース・モデル構築編】」の続きです。
今回も、Responder(レスポンダー)を使って「Djangoのチュートリアル」をやってみたいと思います。
Django のチュートリアル「はじめての Django アプリ作成」を、Responderで追う形になりますので、多少内容が異なる部分がありますが、成果物はできるだけ同じモノになるよう作る予定です。
【はじめての Django アプリ作成】
https://docs.djangoproject.com/ja/2.2/intro/
第1回はこちら
データベースをWeb上で操作する
今回も、Djangoのチュートリアルには無い内容を含みますが、同じアプリケーションを作成する上では、通らねばならない道なのでやっていきたいと思います。
データベースへの追加や変更削除をWeb上で
今回の目標は章題の通り、データベースへの追加や変更削除をWeb上で行えるようにすることです。
前回で「Add」ボタンや「Change」ボタン、「Delete」ボタンを作りましたが、まだあれらは機能していませんでした。
今日は、それらを機能させていきたいと思います!
データの追加(Add)
ルーティング作業なので、いつものごとくurls.py に追記していきましょう。
まずは、/add_question から実装していきます。
とりあえず、getで受け取ったなら質問追加ページ(add_question.html)を表示させて、postで受け取ったときにQuestionテーブルにデータを挿入し、管理者トップページ(admin_top)にリダイレクトさせてみます。
実装
実装例は、以下のようになります。
1@api.route('/add_Question')
2class AddQuestion:
3 async def on_get(self, req, resp):
4 """
5 getの場合は追加専用ページを表示させる。
6 """
7 authorized(req, resp, api)
8
9 resp.content = api.template('add_question.html')
10
11 async def on_post(self, req, resp):
12 """
13 postの場合は受け取ったデータをQuestionテーブルに追加する。
14 """
15 data = await req.media()
16 error_messages = list()
17
18 # もし何も入力されていない場合
19 if data.get('question_text') is None:
20 error_messages.append('質問内容が入力されていません。')
21 resp.content = api.template('add_question.html', error_messages=error_messages)
22 return
23
24 # テーブルに追加
25 question = Question(data.get('question_text'))
26 db.session.add(question)
27 db.session.commit()
28 db.session.close()
29
30 api.redirect(resp, '/admin_top')
やっていることは、前回とほとんど変わりませんね。
ビューの作成
次に、ビューを作っていきます。
これも、最低限の機能さえあれば良いので、以下のように実装しました。
1{% extends "layout.html" %}
2{% block content %}
3
4<br><br>
5<h3>Add Question</h3>
6<a class="btn-sm btn-primary" href="/admin_top">Back to Admin top</a>
7<br>
8<br>
9<div class="row">
10 <div class="col-md5"><p>question_text</p></div>
11 <div class="col-md-7">
12 <form action="/add_Question" method="post">
13 <input type="text" name="question_text">
14
15 {% for error_message in error_messages %}
16 <p class="text-danger">{{error_message}}</p>
17 {% endfor %}
18 <br>
19 <hr>
20 <input class="btn btn-warning" type="submit" name="submit" value="Add Question">
21 </form>
22 </div>
23</div>
24
25{% endblock %}
これで、質問テーブルにデータを挿入できるようになりました!
実行
早速、動かしてみましょう!
うまく追加できました!
Choiceの追加機能を作る
やることは同じですので、説明は省きます。
さっきと違う点は、getで得た場合や、postでエラーを返すときに、Questionの一覧を渡す部分です。
コード
1@api.route('/add_Choice')
2class AddChoice:
3 async def on_get(self, req, resp):
4 """
5 getの場合は追加専用ページを表示させる。
6 """
7 authorized(req, resp, api)
8
9 questions = db.session.query(Question.id, Question.question_text)
10 resp.content = api.template('add_choice.html', questions=questions)
11
12 async def on_post(self, req, resp):
13 """
14 postの場合は受け取ったデータをQuestionテーブルに追加する。
15 """
16 data = await req.media()
17 error_messages = list()
18
19 # もし何も入力されていない場合
20 if data.get('choice_text') is None:
21 error_messages.append('選択肢が入力されていません。')
22 questions = db.session.query(Question.id, Question.question_text)
23 resp.content = api.template('add_choice.html', error_messages=error_messages, questions=questions)
24 return
25
26 # テーブルに追加
27 choice = Choice(data.get('question'), data.get('choice_text'))
28 db.session.add(choice)
29 db.session.commit()
30 db.session.close()
31
32 api.redirect(resp, '/admin_top')
ビュー
ビューは、先と同じように、シンプルな実装にしました。
1{% extends "layout.html" %}
2{% block content %}
3
4<br><br>
5<h3>Add Choice</h3>
6<a class="btn-sm btn-primary" href="/admin_top">Back to Admin top</a>
7<br>
8<br>
9<div class="row">
10 <form action="/add_Choice" method="post">
11 <div class="col-md-5"><p>question</p></div>
12 <div class="col-md-7">
13 <select name="question">
14 {% for question in questions %}
15 <option value="{{question['id']}}">{{question['question_text']}}</option>
16 {% endfor %}
17 </select>
18 </div>
19 <br>
20 <div class="col-md-5"><p>question_text</p></div>
21 <div class="col-md-7">
22 <input type="text" name="choice_text">
23
24 {% for error_message in error_messages %}
25 <p class="text-danger">{{error_message}}</p>
26 {% endfor %}
27 <br>
28 <hr>
29 <input class="btn btn-warning" type="submit" name="submit" value="Add Choice">
30 </div>
31 </form>
32</div>
33
34{% endblock %}
実行
動作確認してみましょう!
無事、動いているようです!
選択肢を一つ一つ追加するのは少し面倒ではありますが、機能としては、実現できているので良しとしましょう!
データの変更(Change)
次は、変更機能の実装です。
先ほどの「Add」では、各テーブルごとに処理を書いていましたが、「Change」機能は、工夫して一つの関数でまとめてみましょう。
そんな時に役立つのが、引数を持つルーティングです。
例えば、http://127.0.0.1:5042/change/question/1 なら「Questionテーブルでid=1のデータを変更する処理」。
http://127.0.0.1:5042/change/choice/2 なら「Choiceテーブルでid=2のデータを変更する処理」。
というようにhttp://127.0.0.1:5042/change/の後に記述するパスによって機能を変更できます。
Responderでは、@api.route('/change/{teble}/{data_id}') のように{ } を使ってルーティングします。
コード
実際にコーディングしてみると、以下のように書くことができます。
(少し強引な部分もありますが...)
1@api.route('/change/{table_name}/{data_id}')
2class ChangeData:
3 async def on_get(self, req, resp, table_name, data_id):
4 authorized(req, resp, api)
5
6 table = Question if table_name == 'question' else Choice
7 # [table].id == data_idとなるようなレコードをひとつ持ってくる
8 field = db.session.query(table).filter(table.id == data_id).first()
9 resp.content = api.template('/change.html', field=field, table_name=table_name)
10
11 async def on_post(self, req, resp, table_name, data_id):
12 data = await req.media()
13 error_messages = list()
14
15 # もし何も入力されていない場合
16 text = table_name + '_text'
17 if data.get(text) is None:
18 error_messages.append('フィールドに入力されていない項目があります。')
19 resp.content = api.template('change.html', error_messages=error_messages,
20 field=data, table_name=table_name)
21 return
22
23 # データを更新
24 table = Question if table_name == 'question' else Choice
25 record = db.session.query(table).filter(table.id == data_id).first()
26
27 if table is Question:
28 record.question_text = data.get(text)
29 record.pub_date = datetime(
30 int(data['year']),
31 int(data['month']),
32 int(data['day']),
33 int(data['hour']),
34 int(data['minute']),
35 int(data['second'])
36 )
37 else:
38 record.choice_text = data.get(text)
39
40 db.session.commit()
41 db.session.close()
42
43 api.redirect(resp, '/admin_top')
このように、引数をもつルーティングでは、処理を行う関数の引数としても、それらを追加してあげます。
getで受け取った時は、まずはログインチェックauthorized() をして、最初にどのテーブルを扱うかをチェックし保持しておきます。
table = Question if table_name == 'question' else Choice
次に、そのテーブルから主キーとなるidと合致するデータを持ってきて、ビューに渡します。
ビューの実装
ビューは、以下のように実装しました。
1{% extends "layout.html" %}
2{% block content %}
3
4<br><br>
5<h3>Change {{table_name}}</h3>
6<a class="btn-sm btn-primary" href="/admin_top">Back to Admin top</a>
7<br>
8<br>
9<form action="/change/{{table_name}}/{{field['id']}}" method="post">
10 <p>id : {{field['id']}}</p>
11 <div class="row">
12 <div class="col-sm-4"><p>{{table_name}}_text</p></div>
13 <div class="col-sm-8">
14 {% set text = table_name + '_text' %}
15 <p><input type="text" name="{{table_name}}_text" value="{{field[text]}}"></p>
16 </div>
17 </div>
18 <br>
19 {% if table_name == 'question' %}
20 <div class="row">
21 <div class="col-sm-4"><p>pub_date</p></div>
22 <div class="col-sm-8">
23 <p>
24 <input type="text" name="year" value="{{field['pub_date'].year}}" size="4">年
25 <input type="text" name="month" value="{{field['pub_date'].month}}" size="2">月
26 <input type="text" name="day" value="{{field['pub_date'].day}}" size="2">日
27 <input type="text" name="hour" value="{{field['pub_date'].hour}}" size="2">:
28 <input type="text" name="minute" value="{{field['pub_date'].minute}}" size="2">:
29 <input type="text" name="second" value="{{field['pub_date'].second}}" size="2">
30
31 </p>
32 <br>
33 {% for error_message in error_messages %}
34 <p class="text-danger">{{error_message}}</p>
35 {% endfor %}
36 <br>
37 </div>
38 </div>
39 {% endif %}
40 <hr>
41 <input class="btn btn-warning" type="submit" name="submit" value="Change {{teble_name}}">
42</form>
43
44{% endblock %}
少し複雑ですが、もらったtable_name と対象のデータfield をhtml内に展開しているだけです。
これで一気に、QuestionとChoiceの「Change」機能の実装が終わりました。
実行
それでは、実際に動かしてみましょう!
(スクショ撮った後が右下に写っていますが気にしないでください)
うまく機能していそうですね!
データの削除(Delete)
では次に、「Delete」機能ですが、これはさっきの「Change」機能と大きな違いはありません。
とはいっても、多少、postの処理が違うのと、ビューも少し異なります。
コードと、実際に動いている様子を見て理解した方が早そうです。
コード
1@api.route('/delete/{table_name}/{data_id}')
2class DeleteData:
3 async def on_get(self, req, resp, table_name, data_id):
4 authorized(req, resp, api)
5
6 table = Question if table_name == 'question' else Choice
7 # table.id == data_idとなるようなレコードをひとつ持ってくる
8 field = db.session.query(table).filter(table.id == data_id).first()
9 db.session.commit()
10 db.session.close()
11
12 resp.content = api.template('/delete.html', field=field, table_name=table_name)
13
14 async def on_post(self, req, resp, table_name, data_id):
15 data = await req.media()
16
17 # データを削除
18 table = Question if table_name == 'question' else Choice
19 record = db.session.query(table).filter(table.id == data_id).first()
20 db.session.delete(record)
21 db.session.close()
22
23 api.redirect(resp, '/admin_top')
1{% extends "layout.html" %}
2{% block content %}
3
4<br><br>
5<h3 class="text-danger">WARNING!</h3>
6<hr>
7<p>This {{table_name}} will be deleted, Are you sure?</p>
8<a class="btn-sm btn-primary" href="/admin_top">Back to Admin top</a>
9<br>
10<br>
11<br>
12<form action="/delete/{{table_name}}/{{field['id']}}" method="post">
13 <div class="row">
14 <div class="col-sm-4"><p>id</p></div>
15 <div class="col-sm-8">
16 <p>{{field['id']}}</p>
17 </div>
18 </div>
19 <div class="row">
20 <div class="col-sm-4"><p>{{table_name}}_text</p></div>
21 <div class="col-sm-8">
22 {% set text = table_name + '_text' %}
23 <p>{{field[text]}}</p>
24 </div>
25 </div>
26 <br>
27 {% if table_name == 'question' %}
28 <div class="row">
29 <div class="col-sm-4"><p>pub_date</p></div>
30 <div class="col-sm-8">
31 <p>{{field['pub_date']}}</p>
32 <br>
33 <br>
34 </div>
35 </div>
36 {% endif %}
37 <hr>
38 <input class="btn btn-warning" type="submit" name="submit" value="Delete {{table_name}}">
39</form>
40
41{% endblock %}
実行
上のコードが実際に動いている様子は、以下のようになります。
問題なく動いているようです!
どれもシンプルなビューですが、凝りたい人は、こだわって作ってみるのも面白いと思います!
第4回へつづく!
さて、今回は、ここまでになります。
今回の第3回でひとつの山場は越えたと思います。
難しい場所も多いので、一旦ここで立ち止まり、コードをしっかり読んで処理を理解する時間を設けても良いのではないでしょうか?
また、ここまでのコーディングが理解できると、いろいろな応用が効くようにもなるので、ぜひ遊んで見てください。
ちなみに、この記事で書いているコードで「ここはもっとこうしたら良いんじゃないか?」など見つけるのも面白いと思います。
さて、次回は「公開用のビュー」を作成していきます。
今までは、ずっと裏の実装をしていたので「Poll (投票)のアプリ」を作っていたことを忘れてしまいそうでしたが…(笑)
いよいよ形にしていくことになりますので、お楽しみに!
第4回の記事はこちら
【全編まとめ】Responderを使ってDjangoチュートリアルをやってみた
こちらの記事もオススメ!
2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
2020.07.30Python 特集実装編※最新記事順Responder + Firestore でモダンかつサーバーレスなブログシステムを作ってみた!P...
ディレクトリ構成
現在のディレクトリ構成も載せておきます。
1.responder/
2├── __pycache__
3├── auth.py
4├── db.py
5├── db.sqlite3
6├── models.py
7├── run.py
8├── sample_insert.py
9├── static
10├── templates
11│ ├── add_choice.html
12│ ├── add_question.html
13│ ├── admin.html
14│ ├── administrator.html
15│ ├── change.html
16│ ├── delete.html
17│ ├── index.html
18│ └── layout.html
19└── urls.py
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
「好きを仕事にするエンジニア集団」の(株)ライトコードです! ライトコードは、福岡、東京、大阪、名古屋の4拠点で事業展開するIT企業です。 現在は、国内を代表する大手IT企業を取引先にもち、ITシステムの受託事業が中心。 いずれも直取引で、月間PV数1億を超えるWebサービスのシステム開発・運営、インフラの構築・運用に携わっています。 システム開発依頼・お見積もり大歓迎! また、現在「WEBエンジニア」「モバイルエンジニア」「営業」「WEBデザイナー」を積極採用中です! インターンや新卒採用も行っております。 以下よりご応募をお待ちしております! https://rightcode.co.jp/recruit