DjangoでTODOリストを作ろう(その4)

TODOリスト の4回目。

今回は駆け足で登録・更新・削除を作っていく。 コレが出来れば一通り必要な機能は全部実装。

ではでは早速。まずは urls.py を編集してリクエストからのパスを通してあげる。以下、 todo/urls.py

from django.conf.urls.defaults import *

# create, update, deleteを追加。
urlpatterns = patterns('',
    (r'^create/$', 'hige.todo.views.create'),
    (r'^update/(?P<object_id>\d+)/$', 'hige.todo.views.update'),
    (r'^delete/(?P<object_id>\d+)/$', 'hige.todo.views.delete'),
    (r'^list/$', 'hige.todo.views.list'),
    (r'^$', 'hige.todo.views.list'),
)

ココで出て来た (?P<object_id>\d+) って部分、 ココではURLから値を抜き取って、 object_id な名前をつけて view の引数に渡すよ。って指示をしてる。なので、 views.pyupdatedelete の引数には request 以外にも object_id な引数が飛び込んでくる。ココでは \d+ を指定してるので、数字しか一致しない。なので、引数に入ってくるのは必ず数字になる。(型が数字かどうかは別)

ちょっと眺めてみると hige.todo.views な文字が何回も書かれてる事に気づく。少しココで整頓しておく。 patterns 関数の第1引数はキックする view のプリフィックスを指定しておける便利機能付き。今回は hige.todo.views までが同じなので、それをプリフィックスとして指定する。

from django.conf.urls.defaults import *

# create, update, deleteを追加。
urlpatterns = patterns('hige.todo.views',
    (r'^create/$', 'create'),
    (r'^update/(?P<object_id>\d+)/$', 'update'),
    (r'^delete/(?P<object_id>\d+)/$', 'delete'),
    (r'^list/$', 'list'),
    (r'^$', 'list'),
)

コレでそれぞれリクエストから view までの紐付けが出来たので、 views.py に実装して行く。とりあえずは create から作って新規登録を出来るようにする訳ですが、殆ど変わらないので update も一緒に作ってしまう。以下、 todo/views.py

def create(request):
    p = {}

    # モデルに紐づくフォームのクラス!の作成
    f = forms.form_for_model(Task)

    # ココでフォームをインスタンス化
    form = f(request.POST.copy() or None)

    # データのバリデート処理
    if form.is_valid():

        # エラーが無ければセーブしてリダイレクト
        form.save()
        return HttpResponseRedirect('/todo/')

    # GETで来た時やエラーがあった場合はフォーム画面へ
    p['title'] = 'add new task.'
    p['form'] = form
    return render_to_response('todo/task_form.html', p)


def update(request, object_id):
    p = {}

    # object_idで該当するデータをもってくる。
    object = get_object_or_404(Task, pk=object_id, is_active=True)

    # インスタンスに紐づくフォームクラスの作成。
    f = forms.form_for_instance(object)

    form = f(request.POST.copy() or None)

    if form.is_valid():
        form.save()
        return HttpResponseRedirect('/todo/')

    p['title'] = 'update task #%s' % object.id
    p['form'] = form
    return render_to_response('todo/task_form.html', p)

ここでちょっと説明とか。バリデートに関しては、デフォルトでモデルを作成したときの制約をほぼそのままフォームに適応してくれる。ので、特に変更が無ければ何も書かなくてもちゃんとデータの妥当性を検証してくれる。 oldforms の頃からそうだったけど、 newforms になってさらにコード量が減った。かなり素敵。

で、ロジックで createupdate で違う所と言えば、既存のデータを取ってくるのと、それに合わせて newforms のメソッドが変わるぐらい。それ以外はほぼ一緒。テンプレートに落とすタイトルが違うぐらい。

次に todo/task_form.html を作る。 newforms には簡単にフォームを作る便利機能があるのですが、個人的にあんまり好きじゃないので、今回はひとつずつ書いていく。覚えといて損はないしね。

{% extends "base.html" %}

{% block title %}{{ title }}{% endblock %}

{% block contents %}
<h1>{{ title }}</h1>
<form action="." method="post" accept-charset="utf-8">
    <ul>
        <li>
            <label for="{{ form.priority.auto_id }}">
                {{ form.priority.label }}
            </label>
            {{ form.priority }}
            {% if form.priority.errors %}
            <label for="{{ form.priority.auto_id }}">
                {{ form.priority.errors|join:"," }}
            </label>
            {% endif %}
        </li>
        <li>
            <label for="{{ form.description.auto_id }}">
                {{ form.description.label }}
            </label>
            {{ form.description }}
            {% if form.description.errors %}
            <label for="{{ form.description.auto_id }}">
                {{ form.description.errors|join:"," }}
            </label>
            {% endif %}
        </li>
        <li>
            <label>&nbsp;</label>
            <input type="submit" value="add task." />
        </li>
    </ul>
</form>
{% endblock %}

コレでひとまず完了。ただ、このままだとURL直叩きじゃないと来れないので、一覧画面にテキトーにリンクを貼る。今回はあんまり深く考えずに create へのリンクは一番下あたりに、 update へのリンクは task 部分に貼ってみた。ついでに delete へのリンクもこしらえておく。以下、 task_list.html の抜粋。

{% block contents %}
<h1>Todo List</h1>
{% if object_list %}
<ul>
    {% for object in object_list %}
    <li>
        {{ object.priority }}&nbsp;
        <a href="/todo/update/{{ object.id }}/">{{ object }}</a>
        <a href="/todo/delete/{{ object.id }}/">delete</a>
    </li>
    {% endfor %}
</ul>
{% else %}
<p>no task.</p>
{% endif %}
<a href="/todo/create/">add new task.</a>
{% endblock %}

ココでひとまず遊んでみる。エラーな画面が出る場合はデバッグを楽しむ。一通り遊んでみたら最後に残った削除画面の作成にはいる。

削除機能に関しては、リンクから GET で飛んでだけでデータが削除されるってのは個人的にものすごく気持ち悪いので、確認画面を間に挟んでそこから POST させて削除する形で作る。以下、 views.py の抜粋。

def delete(request, object_id):
    p = {}
    object = get_object_or_404(Task, pk=object_id, is_active=True)

    # POSTなリクエストが来たら削除。
    if request.method == 'POST':
        object.delete()
        return HttpResponseRedirect('/todo/')

    p['object'] = object
    return render_to_response('todo/task_confirm_delete.html', p)

確認画面の todo/task_confirm_delete.html はこんな感じ。キャンセルボタンはちょっと手抜きしました。

{% extends "base.html" %}

{% block title %}delete confirm{% endblock %}

{% block contents %}
<p>delete task #{{ object.id }}?</p>
<dl>
    <dt>Priority:</dt>
    <dd>{{ object.priority }}</dd>
    <dt>Description:</dt>
    <dd>{{ object }}</dd>
</dl>
<form action="." method="post" accept-charset="utf-8">
    <input type="hidden" name="object_id" value="{{ object.id }}" />
    <input type="submit" value="delete this task." />
    <input type="button" value="cancel" onClick="javascript:history.back();" />
</form>
{% endblock %}

フォームから object_id な値を投げてるのは特に意味はありません。ただ、何も投げないのが気持ち悪かったのと、どっかで使えるかな?って思ってただけ。 POST で来た時にURL中の object_id と比較して、一致したら削除するってのも良いかも。(あんまり意味ないか?)

以下、今回のまとめ?

  • urls.pypatterns 関数の第1引数を上手く使おう。
  • URLからは正規表現でパラメータを引っこ抜く。
  • newforms はそのうち名前が変わるのでリネームは絶対。

これで一通りの動作を全部実装!かなり過不足が多い気がするけど。あと、デザインを全く弄ってないので見てくれはかなりダサイ。そこらへんは好みとかあると思うのであえて触れない方向で。

最後に Django の開発環境でのCSSなどの静的ファイルの提供方法を。プロジェクト直下の urls.py に以下を追加する。

from django.conf import settings
if settings.DEBUG:
    urlpatterns += patterns('django.views.static',
        (r'^site_media/(?P<path>.*)$', 'serve', {'document_root':settings.MEDIA_ROOT,}),
    )

HTMLにはこんな感じで書く。

<link rel="stylesheet" type="text/css" href="/site_media/path/to/css" />

コレでCSSとかJavaScriptとかも読み込めるはず。でも、コレはあくまでも開発環境用なので本番では使わないでね。とのこと。 コチラ本家サイト の情報が和訳されております。

結局 jQuery 使わなかった。すいません。でも、また時間見つけてなにか書きたいと思う。何より自分の勉強になりました。乱雑な内容ではありますが、これから Django を触ってみようと思ってる方(某 endless 氏とか)の手助けになればと思います。ってならないかぁ。

Posted at: 
2007/06/24 23:58:42
3 Comments
0 TrackBacks
Tags: 
Django
Python
Trackback: 
http://humming.via-kitchen.com/2007/06/24/todo-list-by-using-django-part4/trackback/

TrackBacks

まだ登録されていません。

Comments

yabe

一通り実行できました。
参考になりました。
ありがとうございます。

Created at: 
2007/08/22 20:14:51

kishi-r

やっとこさ出来やしたw

ただ慣れないせいで、
スペルミスなどが多いのでデバックに
時間が掛かってしまったよぉ。。。

とりあえず全部完了っす!

Created at: 
2007/09/14 00:13:28

nobu

>yabeさん
>kishi-rさん

お疲れさまですー。
内容が薄くてすいませんです。

徐々にではあるものの、まわりにPythonistaが増えているのが嬉しいですなw

Created at: 
2007/09/14 01:20:42

Add Comment

Add Comment