暇人じゃない

「はじめての Django アプリ作成」チュートリアルをやったメモ その3
DjangoPython

はじめての Django アプリ作成、その 3 — Django v1.0 documentation http://djangoproject.jp/doc/ja/1.0/intro/tutorial03.html

前回の続きでチュートリアルを追ってみた個人的メモです。

環境: MacOS X 10.6.4 Python 2.6.5 Django 1.2.3

ビューの設計

Poll アプリケーションのビュー

  • アーカイブ:最新の調査項目
  • 詳細:調査項目と投票フォーム。開票結果は表示しない
  • 開票結果:調査項目に対する結果
  • 投稿:選択肢に対する投票を受け付ける

ビューは Python の関数として表現される。

URL スキームの設計

ビューを書く上での最初のステップは URL 構造の設計。 →URLconf と呼ばれる Python モジュールを作成

Django で作られたページをリクエストすると...

    * settings.py の **ROOT_URLCONF** を探す
    例:ROOT_URLCONF = 'mysite.urls' * urls.py をロード * **urlpatterns** という変数を探す
    (正規表現, Python のコールバック関数, [, オプションの辞書オブジェクト])
    ex. (r'^admin/', include(admin.site.urls)), * 先頭のタプルから順にリクエストされた URL と正規表現がマッチするまでテストしていく * マッチしたら指定されているコールバック関数を呼び出す

    patterns

    • コールバック関数には HttpRequest オブジェクトを第一引数として渡す
    • 正規表現内でキャプチャした値をキーワード引数として渡す
    • オプションの辞書オブジェクトが指定されていれば、その内容も追加のキーワード引数として渡す

    参照:URL ディスパッチャ — Django v1.0 documentation http://djangoproject.jp/doc/ja/1.0/topics/http/urls.html

    実際に urls.py を書いてみる

    urlpatterns = patterns('',
        (r'^polls/$', 'mysite.polls.views.index'),
        (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
        (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
        (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
        (r'^admin/', include(admin.site.urls)),
    )

    /polls/23 をリクエストすると...

    detail(request=<HttpRequest object>, poll_id='23')

    /polls/23/vote をリクエストすると...

    vote(request=<HttpRequest object>, poll_id='23')

    ... と、関数が呼び出される。

    pollid='23' -> (?P<pollid>\d+) ?P<poll_id> でマッチしたパターンを識別する名前をつけている \d+ は数字にマッチする正規表現

    正規表現で実現できる限りの URL を表現できる。 こんなものだって (r'^polls/latest.php$', 'mysite.polls.views.index')

    これらの正規表現では GET/POST のパラメーター、ドメイン名は検索されない。 /hoge/?vote_id=23 とリクエストがきても、/hoge/ を探す

    正規表現は URLconf モジュールをロードする時にコンパイルされるので、高速に動作する。

    ビュー作成

    簡単に作ってみる

    # polls/views.py
    from django.http import HttpResponse
    
    def index(request):
        return HttpResponse("Hello, world. You're at the poll index.")
    
    def detail(request, poll_id):
        return HttpResponse("Hello, world. You're at the poll %s." % poll_id)

    /polls/ にアクセスすると Hello, world. You're at the poll index. が、 /polls/23/ にアクセスすると Hello, world. You're at the poll 23. が表示される。

    各ビューは HttpResponse オブジェクトを返すか Http404 のような例外を返さなければいけない。

    # polls/views.py
    from mysite.polls.models import Poll
    from django.http import HttpResponse
    
    def index(request):
        latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
        output = ', '.join([p.question for p in latest_poll_list])
        return HttpResponse(output)

    最新 5 件の Poll をカンマで区切って日付順に表示させる。 ページのデザインがビューの中に記述されているのは良くない。

    Django のテンプレートシステムを使って書く

    # polls/views.py
    from django.template import Context, loader
    from mysite.polls.models import Poll
    from django.http import HttpResponse
    
    def index(request):
        latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
        t = loader.get_template('polls/index.html')
        c = Context({
            'latest_poll_list':latest_poll_list,
        })
        return HttpResponse(t.render(c))

    settings.py の TEMPLATE_DIRS で設定したディレクトリに polls というディレクトリを作成し、index.html を作成する。

    mytemplates/polls/index.html

    {% if latest_poll_list %} {% for poll in latest_poll_list %} * [{{ poll.question
    }}](/polls/{{ poll.id }}/ "{{ poll.question }}") {% endfor %} {% else %}
    <p>No polls are available.</p>
    {% endif %}

    ページをリロードすると、箇条書きで Poll が表示される。

    rendertoresponse() テンプレートをロードして、Context に値を入れてレンダリングして HttpResponse オブジェクトで返す、というのは良く使うので、一発でできる rendertoresponse() を使ったほうがいい。

    # polls/views.py
    from django.shortcuts import render_to_response
    from mysite.polls.models import Poll
    
    def index(request):
        latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5]
        return render_to_response('polls/index.html',
                                 {'latest_poll_list' : latest_poll_list})

    第一関数にテンプレート名の指定、第二引数(オプション)に辞書を指定する。 テンプレートを指定の Context でレンダリングし、HttpResponse オブジェクトにして返す かなり簡素化できた!

    詳細ページの作成

    # polls/views.py
    from django.http import Http404
    
    def detail(request, poll_id):
        try:
            p = Poll.objects.get(pk=poll_id)
        except Poll.DoesNotExist:
            raise Http404
        return render_to_response('polls/detail.html', {'poll':p})

    getobjector_404 この、get()を実行しオブジェクトが無い時は Http404 を返す、というのは rendertoresponse() と同じくよく使う関数で、ショートカットが用意されている。

    書き換えてみると....

    # polls/views.py
    from django.shortcuts import render_to_response, get_object_or_404
    
    def detail(request, poll_id):
        p = get_object_or_404(Poll, pk=poll_id)
        return render_to_response('polls/detail.html', {'poll':p})

    リストが空の場合は Http404 を出す getobjector_404() という関数もある

    カスタマイズした 404/500 を使用したい場合はテンプレートディレクトリのルートに それぞれ 404.html/500.html を置く

    テンプレートディレクトリの polls/detail.html を作成

    # {{ poll.question }}
    
    {% for choice in poll.choice_set.all %}
    * {{ choice.choice }}
    {% endfor %}

    poll.choiceset.all -> poll.choiceset_all()

    Poll に属している Choice オブジェクトを取得し for で取り出している

    URLconf の単純化

    # urls.py
    urlpatterns = patterns('',
        (r'^polls/$', 'mysite.polls.views.index'),
        (r'^polls/(?P<poll_id>\d+)/$', 'mysite.polls.views.detail'),
        (r'^polls/(?P<poll_id>\d+)/results/$', 'mysite.polls.views.results'),
        (r'^polls/(?P<poll_id>\d+)/vote/$', 'mysite.polls.views.vote'),
        (r'^admin/', include(admin.site.urls)),
    )

    っていうのは mysite.polls.views と、同じものが書かれていて冗長な部分がある。 patterns の第一引数に prefix を設定すると...

    # urls.py
    urlpatterns = patterns('mysite.polls.views',
        (r'^polls/$', 'index'),
        (r'^polls/(?P<poll_id>\d+)/$', 'detail'),
        (r'^polls/(?P<poll_id>\d+)/results/$', 'results'),
        (r'^polls/(?P<poll_id>\d+)/vote/$', 'vote'),
        (r'^admin/', include(admin.site.urls)),

    こんな感じにスッキリと書く事ができる。

    複数 urlpatterns がある場合はこう書く。

    urlpatterns = patterns('',
        ...
    )
    urlpatterns += patterns('',
        ...
    )

    URLconf の脱カップリング urls.py の設定をプロジェクトから切り離してアプリにくっつける。

    urls.py を polls/urls.py にコピーする

    プロジェクト側の urls.py を以下のように編集

    # urls.py
    urlpatterns = patterns('',
        (r'^polls/', include('mysite.polls.urls')),
        (r'^admin/', include(admin.site.urls)),
    )

    /polls/23/ にアクセスすると... ^polls/ にマッチすると、polls/ を削って、残りの 23/ を mysite.polls.urls という URLconf に送り、処理させる。

    アプリ側の urls.py を以下のように編集

    # polls/urls.py
    from django.conf.urls.defaults import *
    
    urlpatterns = patterns('mysite.polls.views',
        (r'^$', 'index'),
        (r'^(?P<poll_id>\d+)/$', 'detail'),
        (r'^(?P<poll_id>\d+)/results/$', 'results'),
        (r'^(?P<poll_id>\d+)/vote/$', 'vote'),
    )

    polls/ が削られて、poll_id(さっきの例であれば 23/)のみが渡ってくるので polls/ は削除する。

    これで /polls/ や /fun_polls/ /content/polls/ などどんな URL ルート下に置けるようになる。 polls アプリケーションは、絶対 URL ではなく、相対 URL だけに注意している

About

chocoby (GitHub / Twitter)

フリーのソフトウェア開発者です。 Ruby を使った Web 開発を得意としています。