at_yasu's blog

ロード的なことを

メモ書き

よく忘れるのでメモ書き。versionは、1.1.0 alpha。svnで取得。ただ1.0のドキュメントを見ながら作ったので、1.0と変化無し。

練習として、もの凄く簡単な掲示板を作成。

複数の板と言われるものがあって、その板にそれぞれコメントを投稿していく形。投稿したコメントは削除、変更はできない物とする。なお板はAdminしか追加/削除/変更ができない物とする。

プロジェクト名は「notepad」アプリケーション名は「board」としました。

Model -- データベース --

データベース(model)は、threadBoardと云う板と、書き込んだ内容を溜めるthreadCommentsの二つ。

DjangoのAdminからのみthreadBoardを追加するようにするため、admin.site.registerを設定。

threadCommentsは投稿フォームを作成する必要があるので、threadCommentsFormというModelFormを作成。

その際に、excludeで、入力不要な項目を設定しておく

なお、このthreadBoardのthreadTitleにuniqe属性を付けるの忘れてた…

from django.db import models
from django.forms import ModelForm

# Create your models here.
class threadBoard   (models.Model):
	threadTitle = models.CharField(max_length=100) # 板のタイトル
	pubData     = models.DateTimeField()           # 板を作成した日時
	
	def __unicode__ (self):
		return self.threadTitle;

class threadComments(models.Model):
	board    = models.ForeignKey(threadBoard)          # 投稿した板
	cmtTitle = models.CharField(max_length=50)         # コメントタイトル
	cmtName  = models.CharField(max_length=50)         # 投稿者名
	pubDate  = models.DateTimeField(auto_now_add=True) # 投稿日時
	cmt      = models.TextField()                      # 投稿内容
	fromIP   = models.IPAddressField()                 # 投稿者IP
	
	def __unicode__ (self):
		return self.board, self.cmt

class threadBoardForm(ModelForm):
	class Meta:
		model = threadBoard

class threadCommentsForm(ModelForm):
	class Meta:
		model   = threadComments
		exclude = ('board','pubDate','fromIP' )

from django.contrib import admin

admin.site.register(threadBoard)
admin.site.register(threadComments)

URL

どこにアクセスしたらどうなるとか、そう言うのを設定。*1

まず、下記のリストのようなURLインターフェースにする。

/admin/(.*)
Djangoの管理画面へ
/entry/([^/]+)/?
投稿処理をする
/([^/]+)/?
板の内容を表示
/
トップ画面。板一覧を表示

次にurls設定。

from django.conf.urls.defaults import *

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Example:
    # (r'^notepad/', include('notepad.foo.urls')),

    # Uncomment the admin/doc line below and add 'django.contrib.admindocs' 
    # to INSTALLED_APPS to enable admin documentation:
    # (r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    (r'^admin/(.*)', admin.site.root),
    
    # Getting the comment from user.
    (r'entry/(?P<boardName>[^/]+)/?', 'notepad.board.views.submit'),
    
    # Show the boards
    (r'(?P<boardName>[^/]+)/?$', 'notepad.board.views.board'),
    
    # Top Directory
    (r'^$', 'notepad.board.views.top'),
)

ViewController

アクセスがあったら表示する処理。

次に書くのは関数名と、その処理内容。関数名は上記のURLsに書いている。'notepad.board.views.*'の*と合わす事。*2

subject
投稿処理をする。板がない場合や、システム的に不適切な投稿内容の場合は「/」に飛ばしたり404を返す。*3データ、IPアドレス等のシステム的に安心なデータを入れたい場合は、一度POSTの内容をsave(commit=False)でテイントチェックというか安全なコードにする。その返り値は安全なデータを持ったModelFormオブジェクトなので、プロパティに追加していく形になる。下記で云うと、valid_post.fromIP。なお、ForeignKeyのプロパティを指定する場合は、Modelオブジェクトを代入すれば自動的にリレーションを貼る。下記で云う、vavlid_post.boardの事。
board
板の内容を表示する。そのまんまな処理。ほとんどテンプレート側の処理。
top
トップ画面を表示する。板のタイトルを表示させて、それぞれにリンクを貼る。
from django.shortcuts import get_object_or_404,render_to_response
from django import forms
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse

from notepad.board.models import threadBoard, threadComments, threadCommentsForm

def submit (req, boardName = ""):
	if boardName == "":
		return HttpResponseRedirect("/")
	
	boardInfo = get_object_or_404(threadBoard, threadTitle__exact = boardName)
	if req.POST:
		post = req.POST.copy()
		
		form = threadCommentsForm(post)
		if form.is_valid():
			valid_post = form.save(commit=False)
			valid_post.board   = boardInfo
			valid_post.fromIP  = req.META['REMOTE_ADDR']
			valid_post.save()
			return HttpResponseRedirect(reverse('notepad.board.views.board', args=(boardInfo.threadTitle,)))
	
	return HttpResponseRedirect("/")

# -

def board (req, boardName = ""):
	if boardName == "":
		return top(req)
	
	boardInfo = get_object_or_404(threadBoard, threadTitle__exact=boardName)
	
	cmt = threadComments.objects.filter(board__threadTitle__exact = boardName)
	return render_to_response('board.html',
		{'comments':cmt,
		 'board':boardInfo,
		 'form':threadCommentsForm(),
		}
	)

# -

def top (req):
	boardList = threadBoard.objects.all()
	
	return render_to_response('index.html',
		{'boards': boardList})

テンプレート

大事だけど一番面倒な所。今回は煩わしかったので、本当に質素なHTMLでテンプレートを書きました。

なおテンプレートの指定は、上記のViewで云うとrender_to_responseの第一引数に書かれているファイル名が、テンプレート名になる。

board.html
board関数が呼び出す。これは、板を開いた時に呼び出される。つまり投稿内容を表示し、投稿フォームを表示させる。
index.html
top関数が呼び出す。板一覧表示。


Board.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
	<meta http-equiv="Content-Style-Type" content="text/css" />
	<meta http-equiv="Content-Script-Type" content="text/javascript" />
	<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
	<link rel="stylesheet" href="/media/index.css" type="text/css" />
	{% block css_block %}{% endblock css_block %}
	<title>note Pad</title>
</head>

<body>
<h1><a href="/">note Pad</a></h1>

<div class="top_menu_bar">
	<span><a href="/">Top</a></span>
	<span><a href="http://www.djangoproject.com/">Powerd By django</a></span>
</div>

<div class="form">
<form action="/entry/{{board.threadTitle}}/" method="POST">
{{ form.as_p }}
<input type='submit' />
</div>

<div class="board">
{% for comment in comments %}
<p>
{{ comment.cmtTitle }}<br />
{{ comment.cmtName }}<br />
{{ comment.cmt }}
</p>
{% endfor %}
</div>
</body>
</html>


index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
	<meta http-equiv="Content-Style-Type" content="text/css" />
	<meta http-equiv="Content-Script-Type" content="text/javascript" />
	<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
	<link rel="stylesheet" href="/media/index.css" type="text/css" />
	{% block css_block %}{% endblock css_block %}
	<title>note Pad</title>
</head>

<body>
<h1><a href="/">note Pad</a></h1>

<div class="top_menu_bar">
	<span><a href="/">Top</a></span>
	<span><a href="http://www.djangoproject.com/">Powerd By django</a></span>
</div>

<div class="board">
{% for board in boards %}
<a href="/{{board.threadTitle}}/">{{board.threadTitle}}</a>
{% endfor %}
</div>
</body>
</html>

設定 -- settings.pyの事。

ほとんどは変わりなし。個人的に良く書く書き方があるのでメモ書き。

TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
	os.path.abspath(os.path.dirname(__file__)) + "/template",
    # '/home/notepad/workshop/notepad/template',
)

# i18n
ugettext = lambda s: s
LANGUAGES = (
	('ja', ugettext('Japanese')),
	('en', ugettext('English')),
)

# Cache
CACHE_BACKEND = 'dummy:///'

0.96からの変更点

一番大きいのが、ModelFormの使用。Manipulatorと言うのを0.96の時は使っていたので、それ一式が使い物にならなくなり、全て書き直し。

他は、Modelのmaxlengthがmax_lengthに変更かしら…

参考:Django ドキュメント — Django v1.0 documentation

*1:これは0.96から変わってないっぽい

*2:例外として、admin.site.rootは、Djangoの管理者画面になる

*3:つか、404はキャッシュされるからマズいなぁ