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

    メディアチームメディアチーム
    2019.08.29

    IT技術

     

    前回、「【第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

    ライトコードでは、エンジニアを積極採用中!

    ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。

    採用情報へ

    メディアチーム
    メディアチーム
    Show more...

    おすすめ記事

    エンジニア大募集中!

    ライトコードでは、エンジニアを積極採用中です。

    特に、WEBエンジニアとモバイルエンジニアは是非ご応募お待ちしております!

    また、フリーランスエンジニア様も大募集中です。

    background