• トップ
  • ブログ一覧
  • 【第4回】Responderを使ってDjangoチュートリアルをやってみた【公開ビュー作成編】
  • 【第4回】Responderを使ってDjangoチュートリアルをやってみた【公開ビュー作成編】

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

    IT技術

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

     

    前回、「【第3回】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チュートリアルをやってみた~初期セットアップが、まだお済みでない方は前回...

    オーバビュー

    今記事は、Djangoチュートリアルでいうところのと、「はじめての Django アプリ作成、その 3 」と「 はじめての Django アプリ作成、その 4 」にあたります。

    現在、作成しているPollsアプリケーションでは、以下のビューを公開用として作成していきます。

    質問-インデックスページ最新エントリーをいくつか表示
    質問-詳細ページ結果を表示せず、質問テキストと投票フォームを表示
    質問-結果ページ特定の質問の結果を表示
    投票ページ特定の質問の選択を投票として受付

    また、ビューのURLは、前回の「/delete/question/1」のような解りやすくシンプルなURLパターンで提供できるようにします。

    早速作成していきましょう。

    質問-インデックスページ

    インデックスページとは、http://127.0.0.1:5042で表示されるビューです。

    簡単ではありますが、インデックスページを以前すでに作っています。

    現在、templates/index.html は以下のようになっているはずです。

    1{% extends "layout.html" %}
    2{% block content %}
    3<br>
    4<h1>Welcome to Polls Application!</h1>
    5<hr>
    6<p>
    7    This is a simple polls application from Django Tutorial
    8    (URL: <a href="https://docs.djangoproject.com/ja/2.2/intro/tutorial01/">https://docs.djangoproject.com/ja/2.2/intro/tutorial01/</a>)
    9</p>
    10{% endblock %}

    この章では、これを改造していきたいと思います。

    インデックスページでは、質問一覧を表示させる必要があります。

    しかし、このようなデータベース処理は、【第1回】から読んでいただいている方には、さほど難しくはないはずです。

    urls.py

    それでは、まず、urls.py の@api.route('/') を触っていきましょう。

    Questionテーブルから「id」「質問内容」「公開日」を持ってきてビューに渡すだけですが、少し凝ってみましょう。

    1@api.route('/')
    2def index(req, resp):
    3    resp.content = api.template("index.html")

    1@api.route('/')
    2class Index:
    3    def on_get(self, req, resp):
    4
    5        # 最新5個の質問を降順で取得
    6        questions = selfget_queryset()
    7
    8        # フォーマットを変更して必要なものだけ
    9        pub_date = [q.pub_date.strftime('%Y-%m-%d %H:%M:%S') for q in questions]
    10
    11        resp.content = api.template("index.html", questions=questions,  pub_date=pub_date)
    12
    13    def get_queryset(self, latest=5):
    14        """
    15        最新latest個の質問を返す
    16        :param latest:
    17        :return:
    18        """
    19        # 公開日の大きいものでソートして取得
    20        questions = db.session.query(Question).order_by(Question.pub_date.desc()).all()
    21        db.session.close()
    22
    23        return questions[:latest]

    としてみました。

    def get_queryset(self, latest=5) で、最新latest 個、公開日が新しい順で取得しています。

    今回は、最新順で表示させる必要があるため、データベースからは降順でデータを取得する必要があります。

    したがって、SQLでいう「ORDER BY DESC」でデータを取得する必要があり、SQLAlchemyではorder_by() を使います。

    ビュー

    それでは、次に、ビューを作ります。

    こちらも、もらった質問一覧をビューで展開するだけなので、とても簡単です。

    1{% extends "layout.html" %}
    2{% block content %}
    3<br>
    4<h1>Welcome to Polls Application!</h1>
    5<hr>
    6<p>
    7    This is a simple polls application from Django Tutorial
    8    (URL: <a href="https://docs.djangoproject.com/ja/2.2/intro/tutorial01/">https://docs.djangoproject.com/ja/2.2/intro/tutorial01/</a>)
    9</p>
    10
    11<!-- ここから追記 -->
    12<br>
    13<br>
    14<h3>最新の質問</h3>
    15
    16<table class="table">
    17    <thead class="thead-dark">
    18    <tr>
    19        <th scope="col">質問</th>
    20        <th scope="col">公開日</th>
    21    </tr>
    22    </thead>
    23    <tbody>
    24    {% for question in questions %}
    25    <tr>
    26        <td><a href="/detail/{{question['id']}}">{{question['question_text']}}</a></td>
    27        <td>{{pub_date[loop.index-1]}}</td>
    28    </tr>
    29    {% endfor%}
    30    </tbody>
    31</table>
    32<!-- ここまで -->
    33
    34{% endblock %}

    今回は、このように実装してみました。

    質問の詳細は、/detail/1のようなURLパターンで処理することにしています。

    質問一覧としては機能していますが、せっかくなので少し仕様を変更してみましょう。

    最新(直近)の質問だけ強調する

    現在は、公開日が新しい順に並んでいますが、昨日までに公開されたものを強調してみましょう。

    久しぶりに、モデルをいじります。

    1class Question(Base):
    2
    3    #  省略
    4
    5    def was_published_recently(self, days=1):
    6        """
    7        最近追加された質問に対してTrueを返す関数
    8        :return:
    9        """
    10        return self.pub_date >= datetime.now() - timedelta(days=days)

    上記のような関数を追加しました。

    これは、登録されている日付が、現在からdays 日前に存在していればTrueを返す関数です。

    次に、インデックスページを表示させる関数に以下の処理を追加していきます。

    1@api.route('/')
    2class Index:
    3    def on_get(self, req, resp):
    4
    5        # 最新5個の質問を降順で取得
    6        questions = self.get_queryset()
    7
    8        # New! 最新かどうか
    9        emphasized = [question.was_published_recently() for question in questions]
    10
    11        # フォーマットを変更して必要なものだけ
    12        pub_date = [q.pub_date.strftime('%Y-%m-%d %H:%M:%S') for q in questions]
    13
    14        resp.content = api.template("index.html", questions=questions, emphasized=emphasized, pub_date=pub_date)

    Pythonの内包表記を使って、各質問の公開日が最新かどうかをlistとして取得しています。

    ビュー

    それでは、ビューには、以下のように追記してみましょう。

    1{% extends "layout.html" %}
    2{% block content %}
    3<br>
    4<h1>Welcome to Polls Application!</h1>
    5<hr>
    6<p>
    7    This is a simple polls application from Django Tutorial
    8    (URL: <a href="https://docs.djangoproject.com/ja/2.2/intro/tutorial01/">https://docs.djangoproject.com/ja/2.2/intro/tutorial01/</a>)
    9</p>
    10
    11<br>
    12<br>
    13<h3>最新の質問</h3>
    14
    15<table class="table">
    16    <thead class="thead-dark">
    17    <tr>
    18        <th scope="col">質問</th>
    19        <th scope="col">公開日</th>
    20    </tr>
    21    </thead>
    22    <tbody>
    23    {% for question in questions %}
    24    <tr>
    25        <td>
    26            <!-- ここから変更 -->
    27            <a href="/detail/{{question['id']}}">{{question['question_text']}}</a>
    28            {% if emphasized[loop.index-1]%}
    29                <span class="badge badge-info"> new! </span>
    30            {% endif %}
    31            <!-- ここまで -->
    32        </td>
    33        <td>{{question['pub_date']}}</td>
    34    </tr>
    35    {% endfor%}
    36    </tbody>
    37</table>
    38
    39{% endblock %}

    Jinja2では、loop.index は「」から始まるので、配列のインデックスとして扱う場合は上記のように-1 します。

    動作確認

    それでは、新しく質問を追加してみて、実際に動作するか確認してみましょう!

    画像のように、バッジが表示されていれば成功です!

    SQLAlchemyでは、モデルクラスに関数の定義が可能で、それらをResponderとリンクさせることでさまざまな機能が実現できます。

    質問-詳細ページ

    それでは、詳細ページを作っていきます。

    まず、 detail/{id} というURLパターンをurls.py に追記していきます。

    これは、URLによって指定された「id」に対して、合致するものをデータベースから取得しビューに渡すだけなので、

    1@api.route('/detail/{q_id}')
    2class Detail:
    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        db.session.close()
    7
    8        resp.content = api.template('/detail.html', question=question, choices=choices)

    のように記述します。

    Questionテーブルからは、一つで良いのでfirst() 、Choiceテーブルからは質問idが同じものすべて取得する必要があるのでall() を使っていきます。

    ビュー

    次に、ビューを作って行きましょう。

    ビューでは、質問内容に対して選択肢と、投票機能を備えていれば良いので以下のようなコーディングが良いでしょう。

    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<form action="/vote/{{question['id']}}" method="post">
    10    {% for choice in choices %}
    11        <p><input type="radio" name="choice" value="{{choice['id']}}" {% if loop.first %} checked {% endif %}> {{choice['choice_text']}}</p>
    12    {% endfor %}
    13    <button type="submit" name="submit" class="btn btn-success">投票する</button>
    14</form>
    15
    16{% endblock %}

    次に、投票機能質問-結果ページを実装していきたいと思います。

    投票機能と質問-結果ページ

    投票機能では、先ほどのビューで /vote/{id} のようなURLパターンで提供するように実装しました。

    したがって、いつもの如くルーティング作業をしていきます。

    1@api.route('/vote/{q_id}')
    2class Vote:
    3    async def on_post(self, req, resp, q_id):
    4        # postデータを取得
    5        data = await req.media()
    6
    7        # 該当するchoiceを取得しvoteをインクリメント
    8        choice = db.session.query(Choice).filter(Choice.id == data.get('choice')).first()
    9        choice.votes += 1
    10        db.session.commit()
    11
    12        db.session.close()
    13
    14        # リダイレクト
    15        url_redirect = '/result/' + str(q_id)
    16        api.redirect(resp, url_redirect)

    実装内容の詳細な説明は、もう大丈夫だと思います。

    これで、投票機能はできたので、リダイレクト先となる質問-結果ページを作成していきます。

    ルーティング

    まずは、ルーテイングから。

    1@api.route('/result/{q_id}')
    2class Result:
    3    async def on_get(self, req, resp, q_id):
    4
    5        question = db.session.query(Question).filter(Question.id == q_id).first()
    6        choices = db.session.query(Choice).filter(Choice.question == q_id).all()
    7        db.session.close()
    8
    9        resp.content = api.template('result.html', question=question, choices=choices)

    ビュー

    次に、ビューは以下のように実装しました。

    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{% endblock %}

    投票数は、bootstrap4のbadgeクラスを用いてデザインしていますが、どんな形でも構いません。

    動作確認

    実際に投票してみると、以下のように、うまいこと投票機能と結果のビューが実装できていることが確認できます。

    404エラーの送出

    Djangoチュートリアルでは、ここで404エラー、すなわち「ページが存在しないときのエラー画面」についての説明が入ります。

    しかし、Responderには現在そのようなエラーハンドリングがないようです。

    ネット上でも様々な人が試行錯誤していますが、既存のResponderではスマートに解決できていないようです。

    力技で解決する場合

    この方法は、Responderのコード自体をいじってしまうため、あまりオススメできませんが、ひとつの解決案として参考にしてください。

    Responderのコードをいじると言っても1行変更するだけです。

    Responderのapi.py というファイルの494行目にdefault_response() という関数があります。

    その関数を、以下のように変更してみたください(変更部分は508行目)。

    1    def default_response(
    2        self, req=None, resp=None, websocket=False, notfound=False, error=False
    3    ):
    4        if websocket:
    5            return
    6
    7        if resp.status_code is None:
    8            resp.status_code = 200
    9
    10        if self.default_endpoint and notfound:
    11            self.default_endpoint(req=req, resp=resp)
    12        else:
    13            if notfound:
    14                resp.status_code = status_codes.HTTP_404
    15                # resp.text = "Not found." ここを以下に変更
    16                resp.content = self.template('404.html')
    17            if error:
    18                resp.status_code = status_codes.HTTP_500
    19                resp.text = "Application error."

    これで404エラーコードはすべてテンプレートの「404.html」にレスポンスされます

    404.html

    ひとまず、404.htmlは、以下のようにしてみました。

    1{% extends "layout.html" %}
    2{% block content %}
    3<br>
    4<a href="/" class="btn btn-primary btn-sm">トップページにもどる</a>
    5<br><br>
    6<h2>404: Not Found.</h2>
    7<hr>
    8<p>お探しのページが見つかりませんでした。</p>
    9<p>The page you are looking for is not found.</p>
    10<p></p>
    11
    12{% endblock %}

    これで、ルーティングしていないURLにアクセスすると、今追加した404.htmlが表示されると思います。

    あまりスマートな方法ではありませんが、大きな変更ではないのでひとつの案としてご紹介しました。

    第5回へつづく!

    ここまでの内容で、Pollsアプリケーションは、最低限の機能の実装を終えました

    投票の一覧も確認できて、投票もできて、結果も確認できるようになりました。

    そして、質問や選択肢を追加したり、変更したり、削除もできます。

    次は、少し内容が変わって、自動テストについてのチュートリアルに移ります。

    内容が内容なだけに距離をおきがちですが、このチュートリアルでは大したことはやらないので、これを機に自動テストについて理解を深めていきましょう。

    第5回の記事はこちら

    featureImg2019.09.27【第5回】Responderを使ってDjangoチュートリアルをやってみた【自動テスト導入編】第5回~Responderを使ってDjangoチュートリアルをやってみた~前回の記事の「【第4回】Responderを...

    【全編まとめ】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│   ├── detail.html
    18│   ├── index.html
    19│   ├── layout.html
    20│   └── result.html
    21└── urls.py
    広告メディア事業部

    広告メディア事業部

    おすすめ記事