• トップ
  • ブログ一覧
  • 【第3回】Responderを使ってDjangoチュートリアルをやってみた【データベース操作編】
  • 【第3回】Responderを使ってDjangoチュートリアルをやってみた【データベース操作編】

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

    IT技術

    第3回~Responderを使ってDjangoチュートリアルをやってみた~

     

    前回「【第2回】Responderを使ってDjangoチュートリアルをやってみた【データベース・モデル構築編】」の続きです。

    今回も、Responder(レスポンダー)を使って「Djangoのチュートリアル」をやってみたいと思います。

    Django のチュートリアル「はじめての Django アプリ作成」を、Responderで追う形になりますので、多少内容が異なる部分がありますが、成果物はできるだけ同じモノになるよう作る予定です。

    【はじめての Django アプリ作成】
    https://docs.djangoproject.com/ja/2.2/intro/

    第1回はこちら

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

    データベースを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">&nbsp;
    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回の記事はこちら

    featureImg2019.08.29【第4回】Responderを使ってDjangoチュートリアルをやってみた【公開ビュー作成編】第4回~Responderを使ってDjangoチュートリアルをやってみた~&nbsp;前回、「【第3回】Respond...

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

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

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

    featureImg2020.07.17ライトコード的「やってみた!」シリーズ「やってみた!」を集めました!(株)ライトコードが今まで作ってきた「やってみた!」記事を集めてみました!※作成日が新し...
    featureImg2020.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
    広告メディア事業部

    広告メディア事業部

    おすすめ記事