暇人じゃない

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

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

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

「はじめての Django アプリ作成」チュートリアルをやったメモ その2 http://chocoby.jp/blog/2010/11/03/first-django-app-tutorial-memo-2/

環境: 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 をリクエストすると… python detail(request=<HttpRequest object>, poll_id='23')

    /polls/23/vote をリクエストすると… python 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 だけに注意している