Django基本チュートリアル2 掲示板を作る1

  • 2月 07, 2009 11:01

ueBLOG | Djangoの基本チュートリアル

の続きです。

次に簡単な掲示板を作ってみます。

アプリの名前はbbsとします。

firstprojectのディレクトリに移動して

% python manage.py startapp bbs

でアプリを作ります。

startproject/settings.py のINSTALLED_APPSに firstproject.bbsを追加します。

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'firstproject.firstapp',
    'firstproject.bbs', #追加
)

次にモデルを作ります、。

firstproject/bbs/models.py

from django.db import models

class Entry(models.Model):

    author = models.CharField(max_length=50)
    body = models.TextField()
    updated = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return self.author

authorが投稿者、bodyが掲示板に書き込みをした内容、updatedは投稿、編集した時刻です。

updatedのauto_now=Trueは、何もしなくても新規や変更のあったときに時刻を記録してくれるタイムスタンプです。

__unicode__はこのモデルのインスタンスを表示するときに返す文字列です。ここではauthorを返すようにしました。

次にmodelsをデータベースに反映させます。

はじめてsyncdbをする場合はINSTALLED_APPSに登録しているアプリのモデルすべてを反映します。

% python manage.py syncdb
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
# bbsアプリのEntryモデルを作る
Creating table bbs_entry
# スーパーユーザーを作るか聞かれます。username admin password adminで作っておきましょう。後で修正できます。
You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username: admin
E-mail address: #あなたのメールアドレス
Password: #admin
Password (again): #admin
Superuser created successfully.
Installing index for auth.Permission model
Installing index for auth.Message model

これでbbs_entryというテーブルが出来ました。

では、どんなSQLでテーブルが作られたか見てみましょう。

% python manage.py sql bbs
BEGIN;
CREATE TABLE "bbs_entry" (
    "id" integer NOT NULL PRIMARY KEY,
    "author" varchar(50) NOT NULL,
    "body" text NOT NULL,
    "updated" datetime NOT NULL
)
;
COMMIT;

id は自動で振られる値でPRIMARY KEYになります。

Djangoは manage.py shellでPyhonの対話式でアプリを試すモードがあるので、これをつかって今つくったモデルを扱ってみましょう。

% python manage.py shell
Python 2.5.4 (r254:67916, Dec 23 2008, 15:10:54) [MSC v.1310 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)

# bbs/models.pyのEntryクラスをインポート
>>> from bbs.models import Entry
# id と updatedは自動で入るのでauthorとbodyのみ引数にしてEntryクラスのインスタンスを作ります。
>>> e = Entry(author="John", body="first entry")
#どんな値があるのか見てみましょう。idとupdatedの値はまだなく、データベースにも反映されてません。
>>> e.__dict__
{'body': 'first entry', 'updated': None, 'id': None, 'author': 'John'}
#saveメソッドを呼び出すことでデータベースに反映されます。
>>> e.save()
>>> e.__dict__
{'body': 'first entry', 'updated': datetime.datetime(2009, 2, 6, 20, 20, 30, 562000), 'id': 1, 'author': 'John'}
# 次にデータベースからすべてのデータを取得してみましょう
>>> entrys = Entry.objects.all()
>>> entrys
[<Entry: John>]
# id(primarykey)が1のもの(先ほど作ったデータ)を取得
>>> e1 = Entry.objects.get(pk=1)
>>> e1.__dict__
{'body': u'first entry', 'updated': datetime.datetime(2009, 2, 6, 20, 20, 30, 562000), 'id': 1, 'author': u'John'}
# id,author,body,updateなどは属性になっています。
>>> e1.body
u'first entry'
>>> e1.updated
datetime.datetime(2009, 2, 6, 20, 20, 30, 562000)
# インスタンスのdeleteメソッドでデータが消えます。
>>> e1.delete()
# 消えたか確認
>>> Entry.objects.all()
[]
>>> Entry.objects.count()
0
#テスト用にデータを作っておく
>>> e = Entry(author="John", body="body1")
>>> e.save()
>>> e = Entry(author="Paul", body="body2")
>>> e.save()
>>> Entry.objects.all()
[<Entry: John>, <Entry: Paul>]

と、このようにデータを扱うことができます。

より詳しくモデルの扱いを知りたい場合はドキュメントを参考にしましょう。

クエリを生成する — Django v1.0 documentation

では、今つくったエントリーを実際にブラウザで表示してみましょう。

URLの振り分けはfirstproject/urls.py 表示する部分を作るのはfirstproject/bbs/views.py、

になります

firstproject/urls.py

from django.conf.urls.defaults import *
urlpatterns = patterns('',
     url(r'^$', 'firstproject.firstapp.views.helloworld', name='home'),
     (r'^ut/$', 'firstproject.firstapp.views.usetemplate'),
     (r'^plus/(?P<arg1>\d+)/(?P<arg2>\d+)/$', 'firstproject.firstapp.views.calc',
            {"templatename": "calc.html"}),
     (r'^bbs/$', 'firstproject.bbs.views.object_list'), #これを追加
)
firstproject/bbs/views.py

from django.shortcuts import render_to_response
from bbs.models import Entry

def object_list(request):
    object_list = Entry.objects.all()
    return render_to_response("object_list.html",
            {
                "object_list": object_list,
            }
    )

テンプレートにobject_list.htmlというファイルを使うように指定したので作ってみましょう。

アプリ直下にtemplatesというディレクトリを作るとDjangoは自動的にtemplatesを探してくれます。

% mkdir bbs/tempates

そしてobject_list.htmlというファイルを作ります。

firstproject/bbs/templates/object_list.html

{% for object in object_list %}
<dl>
    <dt>{{ object.id }} :<font color=green><b>{{ object.author }}</b></font>
    :{{ object.updated|date:"Y-m-d H:i" }}</dt>
    <dd>{{ object.body }}</dd>
</dl>
{% endfor %}

これで

http://static.flickr.com/3529/3258744845_18aceea25c.jpg

のようなものが表示されたと思います。|date:"Y-m-d H:i"はテンプレートタグのフィルターと呼ばれるものです。ここではdatetimeの表示を見やすくしました。

フィルターは他にもいろいろあるのでドキュメントを調べてみてください。

組み込みタグ/フィルタリファレンス — Django v1.0 documentation

さて、表示の部分はできましたが、掲示板なのでユーザーの投稿のフォームが必要です。

Djangoにはモデルから簡単にフォームを作る機能があるのでそれを利用しましょう。

一度、サーバーをとめて、再び対話式を使って実験してみます。

% python manage.py shell
# forms と bbs/models.pyのEntryをインポート
>>> from django import forms
>>> from bbs.models import Entry
# Entryモデルに基づいたEntryFormクラスを作ります。
>>> class EntryForm(forms.ModelForm):
...     class Meta:
...         model = Entry
...
# インスタンス化
>>> f = EntryForm()
# print で htmlが表示されます。
>>> print f
<tr><th><label for="id_author">Author:</label></th><td><input id="id_author" type="text" name="author" maxlength="50" /></td></tr>
<tr><th><label for="id_body">Body:</label></th><td><textarea id="id_body" rows="10" cols="40" name="body"></textarea></td></tr>
# as_pメソッドでpタグくくりにもできます。
>>> print f.as_p()
<p><label for="id_author">Author:</label> <input id="id_author" type="text" name="author" maxlength="50" /></p>
<p><label for="id_body">Body:</label> <textarea id="id_body" rows="10" cols="40" name="body"></textarea></p>
# authorやbodyなど一部のhtmlを表示させたいときは辞書のように指定します。
>>> print f["author"]
<input id="id_author" type="text" name="author" maxlength="50" />
# ユーザーからのデータ入力は辞書で値を渡します
>>> f = EntryForm({"author":"Link"})
# authorだけでbodyを入力しませんでした。そうするとerrorsにエラーメッセージが辞書で渡されます。
>>> f.errors
{'body': [u'This field is required.']}
# is_validメソッドは正しいデータがあるか確認するメソッド
>>> f.is_valid()
False
# 今度はauthorもbodyも入力
>>> f = EntryForm({"author":"Link", "body":"new entry"})
# errorsは空で、is_validメソッドはTrueを返します
>>> f.errors
{}
>>> f.is_valid()
True
# フォームインスタンスをsaveすることでデータベースに反映することもできます
>>> f.save()
<Entry: Link>
>>> Entry.objects.all()
[<Entry: John>, <Entry: Paul>, <Entry: Link>]
>>>

では、上の結果をふまえてフォーム部分を作ります。

まず、firstproject/bbs/forms.pyというファイルを作ってEntryFormクラスを書きましょう。

from django import forms

from bbs.models import Entry

class EntryForm(forms.ModelForm):

    class Meta:
        model = Entry

次にformを表示させるためにfirstproject/bbs/views.pyを編集します。

from django.shortcuts import render_to_response

from bbs.models import Entry
from bbs.forms import EntryForm

def object_list(request):
    if request.method == "POST": #requestがPOSTかGETか判別
        form = EntryForm(request.POST) #request.POSTは辞書のようなデータなのでそのままEntryFormに渡せる
        if form.is_valid(): # エラーがないか判別
            form.save() #エラーがないのでsave()
            form = EntryForm() #保存したのでフォームを空に、is_valid()でFalseの場合はerrosや入れられた値を持ったまま
    else:
         form = EntryForm() #GETなので空のフォームを作る
    object_list = Entry.objects.all()
    # formとobject_listをテンプレートに渡す
    return render_to_response("object_list.html",
            {
                "form": form,
                "object_list": object_list,
            }
    )

慣れないと複雑に見えるかもしれませんが、すぐになれると思います。

次にテンプレートにフォームの部分を追加します。

firstproject/bbs/templates/object_list.html

{% for object in object_list %}
    <dl>
        <dt>{{ object.id }} :<font color=green><b>{{ object.author }}</b></font>
        :{{ object.updated|date:"Y-m-d H:i" }}</dt>
        <dd>{{ object.body }}</dd>
    </dl>
{% endfor %}
<form method="POST" action="." >
    <table>
        {{ form }}
    </table>
    <input type="submit" />
</form>

これで非常に簡単ですが掲示板ができました。

http://static.flickr.com/3495/3259576456_179b583029.jpg

authorとbodyどちらかしか入力しないでsubmitボタンを押すと、エラーメッセージがでると思います。