似非プログラマのうんちく

「似非プログラマの覚え書き」出張版

巳年じゃないけど Python やろうぜ(その 16)

汎用ビューの活用

Django には、用途に応じて使える汎用ビューが多数用意されています。

django_test/urls.py

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^hello/', include('hello.urls')),
    url(r'^customer_search/', include('customer_search.urls')),
    url(r'^cities/', include('major_city.urls')),  # 追加
]

major_city/urls.py

from django.conf.urls import url
from django.views.generic import ListView, DetailView
from .models import City

app_name = 'major_city'
urlpatterns = [
    url(
        r'^$',
        ListView.as_view(
            queryset=City.objects.all(),
            context_object_name='cities',
            template_name='major_city/list.html'
        ),
        name='index'
    ),
    url(
        r'^(?P<pk>\d+)/$',
        DetailView.as_view(
            model=City,
            context_object_name='city',
            template_name='major_city/detail.html'
        ),
        name='show'
    ),
]

今回は一覧表示系のビューに使用する ListView と、詳細表示系のビューに使用する DetailView を使うことにしました。

name を使ってそれぞれ名前を付けています。app_name は「名前空間のようなもの」で、逆引きの際に 'major_city:index' のようにすることで、他のアプリの index と区別できるようになります。

URL の逆引き

major_city/templates/major_city/list.html

{% load static %}
<!doctype html>
<html>
  <head>
    <title>日本の政令指定都市</title>
    <link rel="stylesheet" href="{% static 'major_city/css/list.css' %}">
  </head>
  <body>
    <table border="1">
      <caption>日本の政令指定都市</caption>
      <thead>
        <tr>
          <th>No.</th>
          <th>都道府県</th>
          <th>都市名</th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        {% for city in cities %}
        <tr>
          <td class="no">{{ city.id }}</td>
          <td class="pref">{{ city.prefecture.name }}</td>
          <td class="name">{{ city.name }}</td>
          <td class="button">
            <form method="get" action="{% url 'major_city:show' city.id %}">
              <input type="submit" value="詳細">
            </form>
          </td>
        </tr>
        {% endfor %}
      </tbody>
    </table>
  </body>
</html>

テンプレート内では url タグを使って URL の逆引きができます。

major_city/templates/major_city/detail.html や各種 CSSGitHubリポジトリを参考にしてください。

GitHub - akaneko3/PracticeDjango: Django のお勉強

n + 1 query 問題への対処

http://localhost:8000/cities/ にアクセスすると次のような SQL たちが発行されていることがわかります。

(0.018) SELECT "major_city_city"."id", "major_city_city"."name", "major_city_city"."prefecture_id", "major_city_city"."designated", "major_city_city"."area", "major_city_city"."population" FROM "major_city_city"; args=()
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 1; args=(1,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 4; args=(4,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 11; args=(11,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 12; args=(12,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 14; args=(14,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 14; args=(14,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 14; args=(14,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 15; args=(15,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 22; args=(22,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 22; args=(22,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 23; args=(23,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 26; args=(26,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 27; args=(27,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 27; args=(27,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 28; args=(28,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 33; args=(33,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 34; args=(34,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 40; args=(40,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 40; args=(40,)
(0.000) SELECT "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id" FROM "major_city_prefecture" WHERE "major_city_prefecture"."id" = 43; args=(43,)
[27/Feb/2017 12:58:45] "GET /cities/ HTTP/1.1" 200 9694
[27/Feb/2017 12:58:45] "GET /static/major_city/css/list.css HTTP/1.1" 200 415
[27/Feb/2017 12:58:45] "GET /static/major_city/css/common.css HTTP/1.1" 200 58
Not Found: /favicon.ico
[27/Feb/2017 12:58:45] "GET /favicon.ico HTTP/1.1" 404 2273
Not Found: /favicon.ico
[27/Feb/2017 12:58:45] "GET /favicon.ico HTTP/1.1" 404 2273

これは「n + 1 query 問題」と言われるもので、パフォーマンスの低下につながります。これを改善する方法が Django にも用意されています。

from django.conf.urls import url
from django.views.generic import ListView, DetailView
from .models import City

app_name = 'major_city'
urlpatterns = [
    url(
        r'^$',
        ListView.as_view(
            queryset=City.objects.select_related().all(),  # select_related を追加
            context_object_name='cities',
            template_name='major_city/list.html'
        ),
        name='index'
    ),
    url(
        r'^(?P<pk>\d+)/$',
        DetailView.as_view(
            model=City,
            context_object_name='city',
            template_name='major_city/detail.html'
        ),
        name='show'
    ),
]

変更を保存するとサーバーが再起動しますので、ページをリロードしてみましょう。

(0.001) SELECT "major_city_city"."id", "major_city_city"."name", "major_city_city"."prefecture_id", "major_city_city"."designated", "major_city_city"."area", "major_city_city"."population", "major_city_prefecture"."id", "major_city_prefecture"."name", "major_city_prefecture"."district_id", "major_city_district"."id", "major_city_district"."name" FROM "major_city_city" INNER JOIN "major_city_prefecture" ON ("major_city_city"."prefecture_id" = "major_city_prefecture"."id") INNER JOIN "major_city_district" ON ("major_city_prefecture"."district_id" = "major_city_district"."id"); args=()
[27/Feb/2017 13:16:37] "GET /cities/ HTTP/1.1" 200 6934
[27/Feb/2017 13:16:38] "GET /static/major_city/css/list.css HTTP/1.1" 304 0
[27/Feb/2017 13:16:38] "GET /static/major_city/css/common.css HTTP/1.1" 304 0

長い SQL ですが、1 回のみの発行になりました。一覧表示系のビューでは n + 1 query 問題が起きやすいので覚えておくと良いでしょう。