Django再入門 RandomNoteを作る vol.2 汎用ビュー(Genericview)

  • 3月 07, 2007 07:45

管理画面で本文を追加したり、消したりできるようになったので 今度は管理画面以外でもできるようにしましょう。 今日のポイントは汎用ビューを使うことです。 Railsのscalfoldが足場に対して汎用ビュー(GenericView)は完成品です。 Djangoの特徴的な機能なので是非使いこなせるようになりましょう。 (どちらが良いというわけではありません)

まずmyproject/urls.pyを

'myproject/urls.py'

from django.conf.urls.defaults import *

urlpatterns = patterns('',
     (r'^randomnote/', include('randomnote.urls')),
     (r'^admin/', include('django.contrib.admin.urls')),
)

と編集しましょう。 これでlocalhost:8000/randomnote/~~ とアクセスされたものはmyproject/randomnote/urls.pyを呼び出して処理するようにします。 プロジェクト直下のurls.pyですべてのアプリをさばいてもいいのですが、 通常はアプリごとにurls.pyを持つようにした方がいいようです。 adminもdjango.contrib.admin.urls.pyを呼び出して処理しています。

ではmyproject/randomnote/urls.pyは自分で作らないといけないので作りましょう。

通常はurls.pyでurlを正規表現で判別して、veiws.pyに書いた関数へ引数を渡すのですが、 Djangoには汎用ビュー(generic.view)というよくある処理を簡単にかける仕組みがあります。 汎用ビューは便利なので積極的に使いましょう。

'randomnote/urls.py'

from django.conf.urls.defaults import *
from randomnote.models import Page

info_dict = {
'queryset':Page.objects.all(),
}

urlpatterns = patterns('django.views.generic.list_detail',
(r'^$', 'object_list', info_dict),
(r'^detail/(?P<object_id>\d+)/$', 'object_detail', info_dict),
)

urlpatterns += patterns('django.views.generic.create_update',
(r'^create/$', 'create_object',
        {'model':Page, 'post_save_redirect':'/randomnote/'} ),
(r'^update/(?P<object_id>\d+)/$','update_object',
        {'model':Page}),
(r'^delete/(?P<object_id>\d+)/$','delete_object',
        {'model':Page, 'post_delete_redirect':'/randomnote/'} ),
)

Djangoは正規表現でurlを解釈してそれぞれの処理に渡します。 例えば localhost:8000/randomnote/detail/1 とブラウザで開いた場合 (r'^detail/(?P<object_id>d+)/$', 'object_detail', info_dict), にヒットするので django.views.generic.list_detail.object_detailという処理にinfo_dict(queryset=Page.objects.all())とobject_id=1が引数として渡されます。 何を引数に渡せるかはドキュメントに詳しく書かれてます。

汎用ビュー(django.views.generic)はいろいろな種類があって 今回は

処理内容: 使用するgeneric.view : 適用されるテンプレートファイル 一覧:django.views.generic.list_detail.object_list : アプリ名/モデル名_list.html 詳細: django.views.generic.list_detail.object_detail : アプリ名/モデル名_detail.html 新規入力: django.views.generic.create_update.create_object : アプリ名/モデル_form.html 追加入力: django.views.generic.create_update.update_object : アプリ名/モデル_form.html 消去: django.views.generic.create_update.delete_object : アプリ名/モデル名_confirm_delete.html

を使ってます。(他には日付ごとにアーカイブを分けてくれるものがあります)

汎用ビューを使う場合は views.pyに書かなくて良いので、urls.pyで正しい引数を渡して、対応するテンプレートファイルを作ればOKです。

ではテンプレートファイルを作っていきましょう。 settings.pyで指定したtemplatesの直下に作っていきます。

Djangoはテンプレートを継承できるので baseとなるテンプレートから

'myproject/templates/randomnote/base.html'

<html>
<h1>RandomNote</h1>
<a href="/randomnote/create/">Create</a><br/>
{% block content %}
    edit content
{% endblock %}
</html>

Djangoのテンプレートは継承するタイプです。 {% block content %} ここの部分を継承したテンプレートに任せる {% endblock %} ことになります。 継承するテンプレートは {% extends "randomnote/base.html" %} {% block content %} ここのそのテンプレート独自の処理を書く {% endblock %} を書くことになります。

ではgenericviewに対応するテンプレートファイルを書いていきましょう

まず、 一覧の汎用ビュー(list_detail.object_list)に対応するテンプレートです。 アプリ名/モデル名_list.htmlがデフォルトなので randomnote/page_list.htmlになります

object_listがモデルの一覧の配列みたいなものなので forを使って一つ一つ取り出して、それぞれの値(idとbody)を出力しています。

'myproject/templates/randomnote/page_list.html'

{% extends "randomnote/base.html" %}
{% block content %}
    {% for page in object_list %}
      <div style="border: solid 1px #999; padding: 0.5em; margin: 1em">
        <div style="font-size: 80%; background-color: #fcc">
          <a href="/randomnote/detail/{{ page.id }}">Show</a>
          <a href="/randomnote/update/{{ page.id }}">Edit</a>
          <a href="/randomnote/delete/{{ page.id }}">Destroy</a>
        </div>
        {{ page.body }}
      </div>
    {% endfor %}
{% endblock %}

次に詳細の汎用ビュー(list_detail.object_detail) アプリ名/モデル名_detail.htmlなので randomnote/page_detail.htmlを作ります。 ここでテンプレートが受け取るのは object という特定のインスタンス urls.pyは (r'^detail/(?P<object_id>d+)/$', 'object_detail', {'queryset':Page}), なので http://localhost:8000/randomnote/detail/1/ というアクセスがあった場合、id=1, queryset=Page が汎用ビューに渡され 汎用ビューは Pageのidが1のインスタンスを object という名前でテンプレートに渡します。

object(=Pageのインスタンス)はidとbodyを持っているのでそれをテンプレートで表示させます。

'myproject/templates/randomnote/page_detail.html'

{% extends "randomnote/base.html" %}
{% block content %}
      <div style="border: solid 1px #999; padding: 0.5em; margin: 1em">
        <div style="font-size: 80%; background-color: #fcc">
          <a href="/randomnote/">List</a>
          <a href="/randomnote/update/{{ object.id }}">Edit</a>
          <a href="/randomnote/delete/{{ object.id }}">Destroy</a>
        </div>
        {{ object.body }}
      </div>
{% endblock %}

つぎにcreateとupdateを扱う汎用ビュー(create_update.create_object とcreate_update.update_object )です。 この二つの汎用ビューは アプリ名/モデル名_form.htmlというひとつのテンプレートを扱います。

createは model=Page、post_save_redirect=/randomnote/ という引数を渡します

(r'^create/$', 'create_object',
    {'model':Page, 'post_save_redirect':'/randomnote/'} ),

Pageというモデルのインスタンスを作ってsaveしたら /randomnote/へリダイレクトしてね という意味です。 updateはobject_idとモデルがPageという引数を渡してます。 http://localhost:8000/randomnote/update/1/というurlでアクセスした場合

(r'^update/(?P<object_id>\d+)/$','update_object',
    {'model':Page}),

object_id=1, model=Pageという引数を渡します。 Pageモデルのidが1のインスタンスを表示、変更したらsaveするという意味です。デフォルトのリダイレクト先はmodel.pyのclass Pageのget_absolut_url()というメソッドになります。これを指定しないとエラーになるので models.pyのPageに

def get_absolute_url(self):
    return "/randomnote/detail/%s" % str(self.id)

を追加してください。

3~7行目で objectが存在してるかどうかでCreateかUpdateか判断してます。 8~14行目は 汎用ビューから受け取るformというインスタンスがエラーを持ってるかどうかを確認してあったら表示 15行目~ はインスタンスそれぞれの値を受け取ってエラーがあれば表示してます。 <label for="id_body">Body:</label>は決まった書き方です。 objectだったりformだったり、ちょっと紛らわしいけど、動かしてみればすぐに分かると思います。

'myproject/templates/randomnote/page_form.html'

{% extends "randomnote/base.html" %}
{% block content %}
{% if object %}
    <h1> Update </h1>
{% else %}
    <h1> Create </h1>
{% endif %}
{% if form.has_errors %}
    {% for field in form.fields %}
        {% if field.error %}
            {{ field.error }}
        {% endif %}
    {% endfor %}
{% endif %}
    <form action="." method="post" accept-charset="utf-8">
        <label for="id_body">Body:</label><br/>{{ form.body }}
        {% if form.body.errors %} *** {{ form.body.errors|join:", "}}{% endif %}<br/>
        <p><input type="submit" value="Continue →" /></p>
    </form>
{% endblock %}

削除の汎用ビュー(create_update.delete_object)はちょっと特殊で アプリ名/モデル名_comfirm_delete.htmlというテンプレートを作ります。 いきなり消さないで確認するためのページですね。 (ここってJavascriptじゃね!?という話も確かにあります)

http://localhost:8000/randomnote/update/1/ というアクセスがきた場合

(r'^delete/(?P<object_id>\d+)/$','delete_object',
     {'model':Page, 'post_delete_redirect':'/randomnote/'} ),

によって object_id=1, model=Page, post_delete_redirect=/randomnote/ という引数を渡します。 モデルがPageでidが1のインスタンスを消して 消したら /randomnote/へリダイレクトしてねということです。

'myproject/templates/randomnote/page_confirm_delete.html'

<form method="post" action=".">
<p>Are you sure?</p>
<input type="submit" />
</form>

この辺は型みたいなものなので他のアプリを作るときもサンプルを見ながら作ることになると思います。

これでとりあえず動くようになります。

http://farm1.static.flickr.com/115/278026275_47022ccee8.jpg

bodyに何も入れずに保存しようとすると Djangoは国際化対応してあるので、ちゃんと日本語でエラーメッセージが表示されます。

http://farm1.static.flickr.com/100/278026260_faee9f0ee4.jpg