巳年じゃないけど 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
や各種 CSS は GitHub のリポジトリを参考にしてください。
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 問題が起きやすいので覚えておくと良いでしょう。