gumiStudy#3 Django – 次の一歩

Preview:

DESCRIPTION

Djangoの出自からDjangoの仕組みについて理解を深めます。アプリケーション設計のための第一歩です。

Citation preview

Django - 次の一歩 based on Django 1.2.3

株式会社アトム アーキテクト 露木誠

では、始めます。(改ページ)最初に言っておきますが、今回のスライドは喋る内容のノート付きで公開しますので、表示されたものやしゃべっている内容自体を逐一メモする必要はありません。見たり聞いたりしてご自身が感じたことをメモする以外は無駄になります。もちろん後でもっと深く知るためにキーワードをメモするのは非常に良いことだと思います。今回、かなりの勢いでいろんなキーワードに触れます。いきなりたった一時間で深く知るのは不可能ですので、キーワードを持ち帰って実際にコードを書いて試してください。

Django - 次の一歩 based on Django 1.2.3

株式会社アトム アーキテクト 露木誠

gumiStudy#3 - 28th Sep. 2010では、始めます。(改ページ)最初に言っておきますが、今回のスライドは喋る内容のノート付きで公開しますので、表示されたものやしゃべっている内容自体を逐一メモする必要はありません。見たり聞いたりしてご自身が感じたことをメモする以外は無駄になります。もちろん後でもっと深く知るためにキーワードをメモするのは非常に良いことだと思います。今回、かなりの勢いでいろんなキーワードに触れます。いきなりたった一時間で深く知るのは不可能ですので、キーワードを持ち帰って実際にコードを書いて試してください。

who am i?

日本で一番最初にDjangoについてブログを書いた

djangoのpoをメンテしていた(今のメンテナはGoogleのtmatsuoさん)

django/trunk/AUTHORSに載ってる(日本人3人)

ちょいとばかし、誰が喋るのかについて。DjangoのAUTHORSファイルには、日本からはPython関係の翻訳で有名なアクセンステクノロジーの増田さん、Googleの松尾さん、あとはなぜか私の3人が載ってます。少しでも貢献すれば載せてもらえたりするので、なにか思うところがあったらやってみたらいいと思います。

who i amhttp://www.tsuyukimakoto.com/about/

Djangoの発表だらけ

7色の言語を操る男、ではないですが、業務ではいろいろやってきています。なんねんかやってれば誰でも10以上になりますよねぇ。ま、どうでもいいですが。しかしですね、プライベートを見るとあまりにDjangoだらけ!しかももう丸5年以上です!一応今回で最後にしたいと思っていますが、どうでしょうねぇ。

Djangoが開発された経緯と開発スタイルある新聞社で…

では、Djangoがどういうモノを目指していたかというところから。

開発開始 - 2003年

それまでPHPで開発していたカンザス州の地方新聞社がPythonを使って開発を開始します。

開発開始 - 2003年USでiTunes Storeが開始

貴乃花引退

スペースシャトル・コロンビア空中分解

六本木ヒルズオープン

OSX 10.3 Panther

SMAP「世界に一つだけの花」シングルDミリオン

2003年といえばこういう年ですね。

オープンソース化 - 2005年

で、Ruby on Railsが猛威を振るいだした頃、あれ?もしかしておれたちモテんじゃね?ってことでオープンソース化。いい感じにもててるみたいです。

オープンソース化 - 2005年ジョージ・W・ブッシュ 二期目

「竹島の日」条例

JR福知山線脱線事故

OSX 10.4 Tiger

iMac G5

閏秒

2005年はこんなことがあった頃です。

World Online often has only

a matter of hours to take a complicated Web application from concept to public launch.

ははーん。

新聞社のとある工程企画・要素決定

デザイン

機能開発

コーディング

データ投入機能開発

データ投入

ローンチ

かなり単純化するとこんな感じでしょうか。(改ページ)この工程を数時間でやると。(改ページ)…

新聞社のとある工程企画・要素決定

デザイン

機能開発

コーディング

データ投入機能開発

データ投入

ローンチ

つまり、この工程を数時間で。

かなり単純化するとこんな感じでしょうか。(改ページ)この工程を数時間でやると。(改ページ)…

新聞社のとある工程企画・要素決定

デザイン

機能開発

コーディング

データ投入機能開発

データ投入

ローンチ

つまり、この工程を数時間で。

よろしく!

かなり単純化するとこんな感じでしょうか。(改ページ)この工程を数時間でやると。(改ページ)…

線表はこんな感じ企画・要素

企画が要素と共に決定すると、(改ページ)デザインをしつつDB設計をして、(改ページ)とりあえず表示機能つくってURLを決定、(改ページ)続いてテストデータを入れて(改ページ)デザイナにコーディングをしてもらいます。そして、オペレータが間違いなくデータ投入できる口を開発します。場合によってはコーディングされたものをテンプレート化するのも開発者の仕事かもしれません。(改ページ)一通り完成したらオペレータがデータを投入してローンチです。

線表はこんな感じ企画・要素

デザインDB設計

企画が要素と共に決定すると、(改ページ)デザインをしつつDB設計をして、(改ページ)とりあえず表示機能つくってURLを決定、(改ページ)続いてテストデータを入れて(改ページ)デザイナにコーディングをしてもらいます。そして、オペレータが間違いなくデータ投入できる口を開発します。場合によってはコーディングされたものをテンプレート化するのも開発者の仕事かもしれません。(改ページ)一通り完成したらオペレータがデータを投入してローンチです。

線表はこんな感じ企画・要素

デザインDB設計

機能開発

企画が要素と共に決定すると、(改ページ)デザインをしつつDB設計をして、(改ページ)とりあえず表示機能つくってURLを決定、(改ページ)続いてテストデータを入れて(改ページ)デザイナにコーディングをしてもらいます。そして、オペレータが間違いなくデータ投入できる口を開発します。場合によってはコーディングされたものをテンプレート化するのも開発者の仕事かもしれません。(改ページ)一通り完成したらオペレータがデータを投入してローンチです。

線表はこんな感じ企画・要素

デザインDB設計

機能開発 Testデータ

企画が要素と共に決定すると、(改ページ)デザインをしつつDB設計をして、(改ページ)とりあえず表示機能つくってURLを決定、(改ページ)続いてテストデータを入れて(改ページ)デザイナにコーディングをしてもらいます。そして、オペレータが間違いなくデータ投入できる口を開発します。場合によってはコーディングされたものをテンプレート化するのも開発者の仕事かもしれません。(改ページ)一通り完成したらオペレータがデータを投入してローンチです。

線表はこんな感じ企画・要素

デザインDB設計

機能開発 Testデータ管理機能

コーディング

企画が要素と共に決定すると、(改ページ)デザインをしつつDB設計をして、(改ページ)とりあえず表示機能つくってURLを決定、(改ページ)続いてテストデータを入れて(改ページ)デザイナにコーディングをしてもらいます。そして、オペレータが間違いなくデータ投入できる口を開発します。場合によってはコーディングされたものをテンプレート化するのも開発者の仕事かもしれません。(改ページ)一通り完成したらオペレータがデータを投入してローンチです。

線表はこんな感じ企画・要素

デザインDB設計

機能開発 Testデータ管理機能

コーディングデータ投入

企画が要素と共に決定すると、(改ページ)デザインをしつつDB設計をして、(改ページ)とりあえず表示機能つくってURLを決定、(改ページ)続いてテストデータを入れて(改ページ)デザイナにコーディングをしてもらいます。そして、オペレータが間違いなくデータ投入できる口を開発します。場合によってはコーディングされたものをテンプレート化するのも開発者の仕事かもしれません。(改ページ)一通り完成したらオペレータがデータを投入してローンチです。

Djangoでこんな感じに企画・要素

デザインモデル定義

URL定義コーディングデータ投入

理想的にはまったとすると、こんな感じになります。実際、85%位はこの流れでいけたっぽいです。

http://www2.ljworld.com/

LJWorld.com

LJWorldは…

こんなサイト

http://www2.ljworld.com/

LJWorld.com

新聞社のサイトと言っても、(改ページ)サインインがあったり、(改ページ)記事にコメントできたり、(改ページ)マルチメディアがあったり、(改ページ)そもそもいろんなコンテンツを投稿できたり、私が2005年当時に見た時からこんなでしたので、結構先進的な新聞社だったようです。DjangoのリードデベロッパだったAdrianも、実はジャーナリズム専攻です。コンピュータサイエンス出身じゃないのがこれまた面白いですね。あからさまに簡単な構成のサイトではないことはわかると思います。

こんなサイト

http://www2.ljworld.com/

LJWorld.com

新聞社のサイトと言っても、(改ページ)サインインがあったり、(改ページ)記事にコメントできたり、(改ページ)マルチメディアがあったり、(改ページ)そもそもいろんなコンテンツを投稿できたり、私が2005年当時に見た時からこんなでしたので、結構先進的な新聞社だったようです。DjangoのリードデベロッパだったAdrianも、実はジャーナリズム専攻です。コンピュータサイエンス出身じゃないのがこれまた面白いですね。あからさまに簡単な構成のサイトではないことはわかると思います。

こんなサイト

http://www2.ljworld.com/

LJWorld.com

新聞社のサイトと言っても、(改ページ)サインインがあったり、(改ページ)記事にコメントできたり、(改ページ)マルチメディアがあったり、(改ページ)そもそもいろんなコンテンツを投稿できたり、私が2005年当時に見た時からこんなでしたので、結構先進的な新聞社だったようです。DjangoのリードデベロッパだったAdrianも、実はジャーナリズム専攻です。コンピュータサイエンス出身じゃないのがこれまた面白いですね。あからさまに簡単な構成のサイトではないことはわかると思います。

こんなサイト

http://www2.ljworld.com/

LJWorld.com

新聞社のサイトと言っても、(改ページ)サインインがあったり、(改ページ)記事にコメントできたり、(改ページ)マルチメディアがあったり、(改ページ)そもそもいろんなコンテンツを投稿できたり、私が2005年当時に見た時からこんなでしたので、結構先進的な新聞社だったようです。DjangoのリードデベロッパだったAdrianも、実はジャーナリズム専攻です。コンピュータサイエンス出身じゃないのがこれまた面白いですね。あからさまに簡単な構成のサイトではないことはわかると思います。

こんなサイト

http://www2.ljworld.com/

LJWorld.com

新聞社のサイトと言っても、(改ページ)サインインがあったり、(改ページ)記事にコメントできたり、(改ページ)マルチメディアがあったり、(改ページ)そもそもいろんなコンテンツを投稿できたり、私が2005年当時に見た時からこんなでしたので、結構先進的な新聞社だったようです。DjangoのリードデベロッパだったAdrianも、実はジャーナリズム専攻です。コンピュータサイエンス出身じゃないのがこれまた面白いですね。あからさまに簡単な構成のサイトではないことはわかると思います。

チュートリアル書かれていることと、書かれていないこと

たった一回チュートリアルをやってみても、実際のところなにがなにやらわからない。あるいは、チュートリアルはわかったけど、いろんなことをどうしていいかわからない…、ここにいる方々はそういう状態の方々のはずですね?。…まぁ、チュートリアルやってなくても構いません。

プロジェクトと    アプリケーションDjangoを知る上で最重要のポリシー

さて、きっと忘れてますよね?

プロジェクトとアプリケーションを作ってみましょう。(動画 27.6 秒)django-admin.pyのstartprojectとstartappに、それぞれプロジェクト名、アプリケーション名を渡します。プロジェクトと、アプリケーションの雛形ができました。あれ?と思った方、よく覚えてますね。チュートリアルでは、作ったプロジェクトの中で、manage.pyを使ってアプリケーションを作りましたよね。プロジェクトの中にアプリケーションを作るのは決して悪いことではありませんが、そこにしか作れないという誤解を持ち続けかねないので、プロジェクトと同階層に作りました。

ファイル構造├── blog│ ├── __init__.py│ ├── models.py│ ├── tests.py│ └── views.py│└── gumistudy ├── __init__.py ├── manage.py ├── settings.py └── urls.py

プロジェクトとアプリケーションを作った際にできるファイルはこれだけですね。(改ページ)プロジェクトディレクトリには4つのファイルができます。ほとんど中身は空っぽです。settings.pyだけ、よく書き換えることになる設定のデフォルト値が設定された状態です。(改ページ)アプリケーションディレクトリにも4つのファイルが出来ます。基本的に中身は空っぽです。

ファイル構造├── blog│ ├── __init__.py│ ├── models.py│ ├── tests.py│ └── views.py│└── gumistudy ├── __init__.py ├── manage.py ├── settings.py └── urls.py

└── gumistudy ├── __init__.py ├── manage.py ├── settings.py └── urls.py

← Pythonのパッケージに必要なファイル← django-admin.pyの便利ラッパー← プロジェクトの設定スクリプト← プロジェクトのURL設定スクリプト

プロジェクトとアプリケーションを作った際にできるファイルはこれだけですね。(改ページ)プロジェクトディレクトリには4つのファイルができます。ほとんど中身は空っぽです。settings.pyだけ、よく書き換えることになる設定のデフォルト値が設定された状態です。(改ページ)アプリケーションディレクトリにも4つのファイルが出来ます。基本的に中身は空っぽです。

ファイル構造├── blog│ ├── __init__.py│ ├── models.py│ ├── tests.py│ └── views.py│└── gumistudy ├── __init__.py ├── manage.py ├── settings.py └── urls.py

├── blog│ ├── __init__.py│ ├── models.py│ ├── tests.py│ └── views.py│

← Pythonのパッケージに必要なファイル← django-admin.pyの便利ラッパー← プロジェクトの設定スクリプト← プロジェクトのURL設定スクリプト

← Pythonのパッケージに必要なファイル←モデルを格納するモジュール←テストを格納するモジュール←Viewを格納するモジュール

プロジェクトとアプリケーションを作った際にできるファイルはこれだけですね。(改ページ)プロジェクトディレクトリには4つのファイルができます。ほとんど中身は空っぽです。settings.pyだけ、よく書き換えることになる設定のデフォルト値が設定された状態です。(改ページ)アプリケーションディレクトリにも4つのファイルが出来ます。基本的に中身は空っぽです。

settings.pyINSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages',)

└── gumistudy ├── __init__.py ├── manage.py ├── settings.py └── urls.py

settings.py

プロジェクトの設定ファイルであるsettings.pyを見てみます。一番下の方に INSTALLED_APPS というタプルがあり、最初から5つのアプリケーションが設定されています。認証やセッションといったWebアプリに欠かせないアプリケーションが登録されています。これらのアプリケーションは非常に柔軟に出来ていますが、使わないことも他のものと取り替えることもできます。さっきつくったblogアプリケーションを登録してみましょう。(改ページ)プロジェクト名は書かずに、blogとだけ書きました。

settings.py└── gumistudy ├── __init__.py ├── manage.py ├── settings.py └── urls.py

settings.py

INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'blog',)

プロジェクトの設定ファイルであるsettings.pyを見てみます。一番下の方に INSTALLED_APPS というタプルがあり、最初から5つのアプリケーションが設定されています。認証やセッションといったWebアプリに欠かせないアプリケーションが登録されています。これらのアプリケーションは非常に柔軟に出来ていますが、使わないことも他のものと取り替えることもできます。さっきつくったblogアプリケーションを登録してみましょう。(改ページ)プロジェクト名は書かずに、blogとだけ書きました。

PYTHONPATH

(動画 10秒)。このままプロジェクトディレクトリに移動をして、manage.py shellを実行しようとすると、blogというモジュールが見つからないと怒られます。プロジェクトの下でstartappした場合には、gumistudy.blogという名前でもblogという名前でも見つけてくれます。実は、manage.pyを使うと実行された./manage.pyのおいてあるディレクトリをPYTHONPATHの先頭に追加をしてPython Shellを立ち上げてくれているのです。gumistudy.blogという名前で発見できるのは、プロジェクトディレクトリと同じパッケージ名は特殊扱いをしてくれているためです。このあたりを理解していないと、デプロイの際にmanage.pyの助けがなくなってハマります。今回配置の場合はひとつ上のディレクトリをPYTHONPATHに追加してやればblogも見えるようになります。

モデルドメインロジックはすべてここに

例えばこんな感じ# -*- coding: utf8 -*-from django.db import modelsfrom django.contrib.auth.models import Userfrom datetime import datetime

class Entry(models.Model): slug = models.SlugField(u'スラグ',

help_text=u'内容を説明するURLの一部に使われます') title = models.CharField(u'タイトル', max_length=128,

help_text=u'記事のタイトルを入力してください') open_time = models.DateTimeField(u'公開日時',

default=datetime.now,help_text=u'記事が公開になる日時です')

create_date = models.DateTimeField(u'作成日時',auto_now_add=True, db_index=True,help_text=u'作成日時が自動で入ります')

author = models.ForeignKey(User, u'あなたを選択してください')Djangoのモデルは、モデルの振る舞いをすべてきちんとモデルに記述するものでしたよね?各フィールドにきちんと型の指定をして、(改ページ)保存されるときの振る舞いなんかも記述できるようにできてます。

例えばこんな感じ# -*- coding: utf8 -*-from django.db import modelsfrom django.contrib.auth.models import Userfrom datetime import datetime

class Entry(models.Model): slug = models.SlugField(u'スラグ',

help_text=u'内容を説明するURLの一部に使われます') title = models.CharField(u'タイトル', max_length=128,

help_text=u'記事のタイトルを入力してください') open_time = models.DateTimeField(u'公開日時',

default=datetime.now,help_text=u'記事が公開になる日時です')

create_date = models.DateTimeField(u'作成日時',auto_now_add=True, db_index=True,help_text=u'作成日時が自動で入ります')

author = models.ForeignKey(User, u'あなたを選択してください')Djangoのモデルは、モデルの振る舞いをすべてきちんとモデルに記述するものでしたよね?各フィールドにきちんと型の指定をして、(改ページ)保存されるときの振る舞いなんかも記述できるようにできてます。

書いたものは使われる

Entry.meta.verbese_name_plural

Fieldの第一引数

ForeignKey.verbose_name

help_text

default

モデルに書いたメタ情報は、管理サイトで表示やバリデーションに使われます。管理サイトを使わない場合でも、エラーメッセージやモデルから生成できる入力フォームで使われます。

必要があれば国際化しておく

admindocというアプリケーションを使ってモデルのドキュメントを表示した画面です。先程のモデルにはベタで日本語を書いたのですが、タイプの部分はDjango標準の国際化が効いています。モデルやview関数に関するドキュメントも参照できるので、 外部に向けて配布するアプリケーションを作る際には、 国際化をしておくといつの日か幸せになれるかもしれません。DjangoはBIDIにも対応しているので比較的優れてると思いますが、国際化は文字列を読めるようにすれば済むという話ではないので、うまくハマるかどうかはわかりません。このドキュメントはモデル定義をきちんとしておけば動的に表示できるので、デザイナのコーディング作業時にプログラマに聞かなければならないことが減ります。また、データの入力制限もこの時点で出来ているので、オペレータはデータ投入を開始できてしまうのです。よってモデルの定義とドキュメントはきっちり書くべきです。

欲しいFieldが無いんだけど•AutoField•BigIntegerField•BooleanField•CharField•CommaSeparatedIntegerField•DateField•DateTimeField•DecimalField•EmailField•FileField

•FilePathField•FloatField•ImageField•IntegerField•IPAddressField•NullBooleanField•PositiveIntegerField•PositiveSmallIntegerField•SlugField•SmallIntegerField

•TextField•TimeField•URLField•XMLField•ForeignKey•ManyToManyField•OneToOneField

http://docs.djangoproject.com/en/1.2/ref/models/fields/「なんでも書けって言ったって、必要な動きをするフィールドがありませーん」ってこともありますよね。結構変わったフィールドもありますが、開発者いわく、「非常に標準的なもののみしかない」

validatorsを設定する

RegexValidator

■ URLValidator

■ validate_email

■ validate_slug

■ validate_ipv4_address

■ validate_comma_separated_integer_list

■ MaxValueValidator

■ MinValueValidator

■ MaxLengthValidator

■ MinLengthValidator

from django.core import validatorsclass Entry(models.Model): asin = models.CharField('ASIN', validators=[validators.validate_slug])

http://docs.djangoproject.com/en/1.2/ref/validators/その場合、入力値の検証に使うバリデータを設定するとしのげるかもしれません。Modelにフィールド定義するコードで、validators引数に、適用したいバリデータのリストを渡せば、適用されます。ただ、標準のvalidatorはやはり種類が少ないので…

validatorを作成する

http://docs.djangoproject.com/en/1.2/ref/validators/

from django.core.exceptions import ValidationError

def validate_even(value): if value % 2 != 0: raise ValidationError(u'%s is not an even number' % value)

バリデータを作成しましょう。独自のバリデータを作るのは、実はそんなに難しくありません。検証して問題が見つかった場合には、ValidationErrorをraiseする、それだけです。「でもでもでも、毎回同じバリデータを定義するのってDRYじゃないんじゃない?長さとかだってさぁ。」

Fieldを作成する

http://code.djangoproject.com/browser/django/tags/releases/1.2.3/django/db/models/fields/__init__.py

class SlugField(CharField): description = _("String (up to %(max_length)s)") def __init__(self, *args, **kwargs): kwargs['max_length'] = kwargs.get('max_length', 50) # Set db_index=True unless it's been set manually. if 'db_index' not in kwargs: kwargs['db_index'] = True super(SlugField, self).__init__(*args, **kwargs)

def get_internal_type(self): return "SlugField"

def formfield(self, **kwargs): defaults = {'form_class': forms.SlugField} defaults.update(kwargs) return super(SlugField, self).formfield(**defaults)

こうなったらモデルの定義に使えるフィールドを作っちゃえばいいです。ぶっちゃけそんなに難しくないんで、モデルのフィールド自体のコードを見たら多分書けます。まずCharFieldかIntegerフィールドを継承してできないかって考えてみてください。validatorsだけ上書きしてRegexValidator(レゲックスバリデータ)ベースのvalidatorを追加するだけでかなりイケルはずです。この例は、CharFieldを継承したSlugFieldのコードです。おっと、Slugは新聞系の用語みたいで、URLの一部に内容がわかる文字列を使うためのものです。DBのインデックスをデフォルトオンにするなど、用途に特化したチューニングを行っています。

激しくFieldを作成する

「そんなんじゃ勘弁ならんちゃっ」て人は(改ページ)カスタムフィールドの書き方がきちんと書かれているので、頑張れとしか言いようがありません。がんばれー

激しくFieldを作成する

http://docs.djangoproject.com/en/1.2/howto/

custom-model-fields/

「そんなんじゃ勘弁ならんちゃっ」て人は(改ページ)カスタムフィールドの書き方がきちんと書かれているので、頑張れとしか言いようがありません。がんばれー

models.pyが長大に…

├── __init__.py├── admin.py├── model_a.py├── model_b.py├── models.py├── tests.py└── views.py

from model_a import *from model_b import *

で、モデルに付いてなんとなく分かってきたところで、「ガリガリと書いてたら、なんだかアプリケーションに多くのモデルが定義されて見通しが悪くなったー。」(改ページ)「モデルはアプリケーション直下のmodelsというモジュールから取り出せればいいので、別のモジュールに種類ごとに分けて、models.pyでインポートしておけば使えるよー。」

models.pyが長大に…

├── __init__.py├── admin.py├── model_a.py├── model_b.py├── models.py├── tests.py└── views.py

from model_a import *from model_b import *

別モジュールで定義してmodels.pyにimport

で、モデルに付いてなんとなく分かってきたところで、「ガリガリと書いてたら、なんだかアプリケーションに多くのモデルが定義されて見通しが悪くなったー。」(改ページ)「モデルはアプリケーション直下のmodelsというモジュールから取り出せればいいので、別のモジュールに種類ごとに分けて、models.pyでインポートしておけば使えるよー。」

アプリケーションディレクトリが…

├── __init__.py├── admin.py├── models│ ├── __init__.py│ ├── model_a.py│ ├── model_b.py│ ├── model_c.py│ └── model_d.py├── tests.py└── views.py

from model_a import *from model_b import *from model_c import *from model_d import *

__all__ = [ModelA, ModelB,.....]

「モジュールを分けたらファイルがたくさんになって、アプリケーションディレクトリが溢てきたよ…」(改ページ)「とにかくアプリケーション直下のmodelsという名前からimportできればいいので、modelsというパッケージを作ってしまえば使えるよー。」「ただし、この場合はパッケージ階層がずれてしまうので、各モデルのmetaクラスにapp_labelを定義しなきゃだめだよー」

アプリケーションディレクトリが…

├── __init__.py├── admin.py├── models│ ├── __init__.py│ ├── model_a.py│ ├── model_b.py│ ├── model_c.py│ └── model_d.py├── tests.py└── views.py

from model_a import *from model_b import *from model_c import *from model_d import *

__all__ = [ModelA, ModelB,.....]

パッケージにしちまえよ。app_labelに気をつけな

「モジュールを分けたらファイルがたくさんになって、アプリケーションディレクトリが溢てきたよ…」(改ページ)「とにかくアプリケーション直下のmodelsという名前からimportできればいいので、modelsというパッケージを作ってしまえば使えるよー。」「ただし、この場合はパッケージ階層がずれてしまうので、各モデルのmetaクラスにapp_labelを定義しなきゃだめだよー」

分割しよう!モデルが多すぎるのは

なんか間違ってる気がする

分割したほうがいいよ

ひとつのアプリケーションに大量のモデルが含まれている状況は、おそらくおかしなアプリケーションを設計しているはずです。一つのサイト全体を一つのアプリケーションで開発しようとしてはいけません。

ルースカップリング

分割分割っていうけど、結局密結合しちゃうんじゃないの?…この先、何度かこう思うことでしょう。なんせ、アプリケーションを分割して再利用可能にしようってんですから、いろいろと仕掛けがあります。(改ページ)まずは、モデルの関連から。Entryというモデルにdjango.contrib.authアプリケーションのモデル「User」との関連を定義したとします。Entryからauhtorをたどれるのは当然として、UserからEntryも辿りたいですよね。大丈夫です。この時点で、Userモデルのインスタンスにentry_setという関連マネージャが動的に定義されています。もちろん、「定義されていない名前に対するアクセスを文字列判定で」なんてことはしていません。きちんとUserのインスタンスが持っている名前空間にオブジェクトが追加されています。

ルースカップリングfrom django.contrib.auth.models import User

class Entry(models.Model): author = models.ForeignKey(User)

>>> user = User.objects.get(pk=1)>>> user.entry_set.all()[]

分割分割っていうけど、結局密結合しちゃうんじゃないの?…この先、何度かこう思うことでしょう。なんせ、アプリケーションを分割して再利用可能にしようってんですから、いろいろと仕掛けがあります。(改ページ)まずは、モデルの関連から。Entryというモデルにdjango.contrib.authアプリケーションのモデル「User」との関連を定義したとします。Entryからauhtorをたどれるのは当然として、UserからEntryも辿りたいですよね。大丈夫です。この時点で、Userモデルのインスタンスにentry_setという関連マネージャが動的に定義されています。もちろん、「定義されていない名前に対するアクセスを文字列判定で」なんてことはしていません。きちんとUserのインスタンスが持っている名前空間にオブジェクトが追加されています。

Signals

http://code.djangoproject.com/browser/django/tags/releases/1.2.3/django/contrib/auth/management/__init__.py

ついでにシグナルも知っておきましょう。シグナルは、任意のタイミングでイベントを発行してそのイベントを待ち受けているコールバック関数を呼び出す仕組みです。(改ページ)例えばこのコードは、django.contrib.authアプリケーションに含まれているもので、各アプリケーションのモデルがデータベースに登録されたタイミングで、モデルに対する基本パーミッションをdjango.contrib.contenttypesアプリケーションのContentTypeに対する権限データとして登録しています。この動作は、django.contrib.authアプリケーションが必要としているだけなので、他のアプリケーションのコードは一切このことは知りません。

Signalsdef create_permissions(app, created_models, verbosity, **kwargs): from django.contrib.contenttypes.models import ContentType from django.contrib.auth.models import Permission app_models = get_models(app) if not app_models: return for klass in app_models: ctype = ContentType.objects.get_for_model(klass) for codename, name in _get_all_permissions(klass._meta): p, created = Permission.objects.get_or_creat(codename=codename,

content_type__pk=ctype.id, defaults={'name': name, 'content_type': ctype})

if created and verbosity >= 2: print "Adding permission '%s'" % p

signals.post_syncdb.connect(create_permissions, dispatch_uid = "django.contrib.auth.management.create_permissions")

http://code.djangoproject.com/browser/django/tags/releases/1.2.3/django/contrib/auth/management/__init__.py

ついでにシグナルも知っておきましょう。シグナルは、任意のタイミングでイベントを発行してそのイベントを待ち受けているコールバック関数を呼び出す仕組みです。(改ページ)例えばこのコードは、django.contrib.authアプリケーションに含まれているもので、各アプリケーションのモデルがデータベースに登録されたタイミングで、モデルに対する基本パーミッションをdjango.contrib.contenttypesアプリケーションのContentTypeに対する権限データとして登録しています。この動作は、django.contrib.authアプリケーションが必要としているだけなので、他のアプリケーションのコードは一切このことは知りません。

定義済みSignlasModel signals

pre_init

post_init

pre_save

post_save

pre_delete

post_delete

m2m_changed

class_prepared

Management signals

post_syncdb

Request/response signals

request_started

request_finished

got_request_exception

Test signals

template_rendered

Database Wrappers

connection_created

http://docs.djangoproject.com/en/1.2/ref/signals/定義済みのシグナルです。モデルに関して多くのシグナルが定義されています。キャッシュカラムの更新とか、memcachedの更新とかによく使ってます。

Signalを作成する

http://docs.djangoproject.com/en/1.2/topics/signals/自分のコードでオリジナルのSignalを定義することも簡単にできます。(改ページ)Signalのインスタンスを任意の名前に割り当てるだけです。引数が必要であれば、引数の名前も定義します。(改ページ)シグナルの発行は、シグナルに対して呼び出し主と、引数を渡してsendするだけです。

Signalを作成する

http://docs.djangoproject.com/en/1.2/topics/signals/

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

Signalの定義

自分のコードでオリジナルのSignalを定義することも簡単にできます。(改ページ)Signalのインスタンスを任意の名前に割り当てるだけです。引数が必要であれば、引数の名前も定義します。(改ページ)シグナルの発行は、シグナルに対して呼び出し主と、引数を渡してsendするだけです。

Signalを作成する

http://docs.djangoproject.com/en/1.2/topics/signals/

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

class PizzaStore(object): ...

def send_pizza(self, toppings, size): pizza_done.send(sender=self, toppings=toppings, size=size) ...

Signalの定義

Signalの発行

自分のコードでオリジナルのSignalを定義することも簡単にできます。(改ページ)Signalのインスタンスを任意の名前に割り当てるだけです。引数が必要であれば、引数の名前も定義します。(改ページ)シグナルの発行は、シグナルに対して呼び出し主と、引数を渡してsendするだけです。

Signalの関係Signalのインスタンス(リスナー)

シグナルの動きはちょっと分かりにくいので、流れを説明します。まず、シグナルのインスタンスを定義します。(改ページ)次に、対象のシグナルのインスタンスに向けてsendします。すると、senderとargsがシグナルのインスタンスに渡されます。ただし、この時点ではなんのコールバック関数も登録されていないため何も起きません。今度はコールバック関数をシグナルのインスタンスにconnectしておきます。(改ページ)すると、今度はリスナー登録されているコールバック関数があるのでsenderとargsが渡されコールバック関数が呼び出されます。コールバックをconnectする時に、シグナルの発行元であるsenderの種類を指定することもできます。

Signalの関係Signalのインスタンス(リスナー)

Signal発行元

send

シグナルの動きはちょっと分かりにくいので、流れを説明します。まず、シグナルのインスタンスを定義します。(改ページ)次に、対象のシグナルのインスタンスに向けてsendします。すると、senderとargsがシグナルのインスタンスに渡されます。ただし、この時点ではなんのコールバック関数も登録されていないため何も起きません。今度はコールバック関数をシグナルのインスタンスにconnectしておきます。(改ページ)すると、今度はリスナー登録されているコールバック関数があるのでsenderとargsが渡されコールバック関数が呼び出されます。コールバックをconnectする時に、シグナルの発行元であるsenderの種類を指定することもできます。

Signalの関係Signalのインスタンス(リスナー)

Signal発行元

コールバック関数connect

send {args}

シグナルの動きはちょっと分かりにくいので、流れを説明します。まず、シグナルのインスタンスを定義します。(改ページ)次に、対象のシグナルのインスタンスに向けてsendします。すると、senderとargsがシグナルのインスタンスに渡されます。ただし、この時点ではなんのコールバック関数も登録されていないため何も起きません。今度はコールバック関数をシグナルのインスタンスにconnectしておきます。(改ページ)すると、今度はリスナー登録されているコールバック関数があるのでsenderとargsが渡されコールバック関数が呼び出されます。コールバックをconnectする時に、シグナルの発行元であるsenderの種類を指定することもできます。

inherit/proxy

http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance他のアプリケーションにあるモデルを拡張したい…、という欲求もあるかもしれませんね。ここで他のアプリケーションをプロジェクトにコピーして、ガリガリ書き換えてしまうのは非常に問題です。継承を使いましょう。

inherit/proxyMultitable Inheritance

http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance

class Address(models.Model): zipcode = models.XXXField(.. street = models.XXXField(.. person = models.ForeignKey(User, ...

class NetAddress(othermodels.Address): twitter = models.XXXField(.. facebook = models.XXXField(..

in pim/models.py

in netizen/models.py

通常、独立した既存のアプリケーションに情報を追加したい場合には、この継承を使うことになります。(改ページ)継承したモデルに対応するテーブルが、既存のアプリケーションのモデルに対応するテーブルに対する参照を持ちます。(改ページ)使うときは特に意識せずに使えます。普通のPythonで継承した時と同じような感じですね。サブクラスの方を使います。

inherit/proxyMultitable Inheritance

http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance

class Address(models.Model): zipcode = models.XXXField(.. street = models.XXXField(.. person = models.ForeignKey(User, ...

class NetAddress(othermodels.Address): twitter = models.XXXField(.. facebook = models.XXXField(..

in pim/models.py

in netizen/models.py

pim_addressid

zipcodestreet

person_id

netizen_netaddressaddress_ptr_id

twitterfacebook

10..1

通常、独立した既存のアプリケーションに情報を追加したい場合には、この継承を使うことになります。(改ページ)継承したモデルに対応するテーブルが、既存のアプリケーションのモデルに対応するテーブルに対する参照を持ちます。(改ページ)使うときは特に意識せずに使えます。普通のPythonで継承した時と同じような感じですね。サブクラスの方を使います。

inherit/proxyMultitable Inheritance

http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance

>>> na = NetAddress(zipcode=u'251', street=u'Shonan', twitter=u'everes', facebook=u'makoto.tsuyuk')>>> na.person = some_user>>> na.save()

通常、独立した既存のアプリケーションに情報を追加したい場合には、この継承を使うことになります。(改ページ)継承したモデルに対応するテーブルが、既存のアプリケーションのモデルに対応するテーブルに対する参照を持ちます。(改ページ)使うときは特に意識せずに使えます。普通のPythonで継承した時と同じような感じですね。サブクラスの方を使います。

inherit/proxy

http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance

Abstractclass Address(models.Model): zipcode = models.XXXField(.. street = models.XXXField(.. class Meta: abstract = True

class NetAddress(othermodels.Address): twitter = models.XXXField(.. facebook = models.XXXField(..

in pim/models.py

in netizen/models.py

netizen_netaddressid

zipcode

street

person_id

twitterfacebook

Abstractなモデルも定義できます。別のモデルに継承して使われることを前提としたモデルで、syncdbしてもアブストラクトなモデルに対応するテーブルは作成されず、継承したモデルのテーブルだけで表現されます。

inherit/proxy

http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance

Proxyclass Address(models.Model): ....

class JapanLabel(othermodels.Address): def printable_zip(self): return '%s-%s' % (self.zipcode[:3], self.zipcode[3:]) class Meta: proxy = True

in pim/models.py

in posting/models.py

pim_addressid

zipcodestreet

person_id

「そもそもカラムは必要なくって、ロジックだけ付け加えたいんだけど…」って場合には、Proxyモデルを使います。(改ページ)使い方はやはり通常の継承と変わりません。

inherit/proxy

http://docs.djangoproject.com/en/1.2/topics/db/models/#model-inheritance

Proxy

>>> posting = JapanLabel(zipcode=u'2510000', street=u'Shonan',)>>> posting.person = some_user>>> posting.save()>>> print posting.printable_zip()'251-0000'

「そもそもカラムは必要なくって、ロジックだけ付け加えたいんだけど…」って場合には、Proxyモデルを使います。(改ページ)使い方はやはり通常の継承と変わりません。

Model APIさらにDRYに、かつ柔軟に

一つ言って置かなければいけないことがあります。O/R Mapperは、SQLを隠蔽してくれますが、データベースについて考えなくていいというものではありません。よりよくO/R Mapperを使うには、モデルの裏側で走っているSQLを想像しながら、場合によってはSQLをデバッグ出力して実行の効率を確かめながら使う必要があります。

Queryset

http://docs.djangoproject.com/en/1.2/topics/db/queries/

>>> query1 = Entry.objects.filter(title__startswith='test')

モデルのobjectsという名前には暗黙でモデルマネージャのインスタンスが設定されます。モデルマネージャというのは、モデルの集合体を扱うクラスです。そのマネージャを通して、filterやexcludeで絞り込んだり、allで全件を取り出したりします。…(改ページ)関連のモデルが持つフィールドを使って絞込もできましたよね。…(改ページ)allやfilter等のメソッドの戻り値はクエリセットと呼ばれるオブジェクトで、評価するまで実行されませんし、…(改ページ)、クエリセットをさらに絞り込んだ場合には新しいクエリセットのオブジェクトが返ってくるので、元のクエリセットに影響を与えません。

Queryset

http://docs.djangoproject.com/en/1.2/topics/db/queries/

>>> query1 = Entry.objects.filter(title__startswith='test')>>> query2 = query1.filter(author__username__startwith='tsuyu')

モデルのobjectsという名前には暗黙でモデルマネージャのインスタンスが設定されます。モデルマネージャというのは、モデルの集合体を扱うクラスです。そのマネージャを通して、filterやexcludeで絞り込んだり、allで全件を取り出したりします。…(改ページ)関連のモデルが持つフィールドを使って絞込もできましたよね。…(改ページ)allやfilter等のメソッドの戻り値はクエリセットと呼ばれるオブジェクトで、評価するまで実行されませんし、…(改ページ)、クエリセットをさらに絞り込んだ場合には新しいクエリセットのオブジェクトが返ってくるので、元のクエリセットに影響を与えません。

Queryset

http://docs.djangoproject.com/en/1.2/topics/db/queries/

>>> query1 = Entry.objects.filter(title__startswith='test')>>> query2 = query1.filter(author__username__startwith='tsuyu')

>>> query1[<Entry: test entry1>, <Entry: test entry2>]>>> query2[]

モデルのobjectsという名前には暗黙でモデルマネージャのインスタンスが設定されます。モデルマネージャというのは、モデルの集合体を扱うクラスです。そのマネージャを通して、filterやexcludeで絞り込んだり、allで全件を取り出したりします。…(改ページ)関連のモデルが持つフィールドを使って絞込もできましたよね。…(改ページ)allやfilter等のメソッドの戻り値はクエリセットと呼ばれるオブジェクトで、評価するまで実行されませんし、…(改ページ)、クエリセットをさらに絞り込んだ場合には新しいクエリセットのオブジェクトが返ってくるので、元のクエリセットに影響を与えません。

Queryset

http://docs.djangoproject.com/en/1.2/topics/db/queries/

>>> query1 = Entry.objects.filter(title__startswith='test')>>> query2 = query1.filter(author__username__startwith='tsuyu')

>>> query1[<Entry: test entry1>, <Entry: test entry2>]>>> query2[]

>>> query3 = query1.filter(author__username__startswith='ma')>>> query3[<Entry: test entry1>, <Entry: test entry2>]

モデルのobjectsという名前には暗黙でモデルマネージャのインスタンスが設定されます。モデルマネージャというのは、モデルの集合体を扱うクラスです。そのマネージャを通して、filterやexcludeで絞り込んだり、allで全件を取り出したりします。…(改ページ)関連のモデルが持つフィールドを使って絞込もできましたよね。…(改ページ)allやfilter等のメソッドの戻り値はクエリセットと呼ばれるオブジェクトで、評価するまで実行されませんし、…(改ページ)、クエリセットをさらに絞り込んだ場合には新しいクエリセットのオブジェクトが返ってくるので、元のクエリセットに影響を与えません。

F/Aggregations

ちょっと寄り道して、FとAggregationについて見ておきましょう。(改ページ)Fは、レコード自身の値を比較に使うためのものです。この例では、レートがコメント数+トラックバック数より小さいものを対象にしようとしています。(改ページ)Aggregationは、集合関数を使う方法です。aggregateとannotateがあり、aggregateは集計した結果を辞書で返します。annotateは結果をモデルインスタンスのプロパティに追加して返します。AggregateとAnnotateはマネージャに対してのみでなく、Querysetに対しても使えます。

F/Aggregations>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

http://docs.djangoproject.com/en/1.2/topics/db/queries/#filters-can-reference-fields-on-the-model

ちょっと寄り道して、FとAggregationについて見ておきましょう。(改ページ)Fは、レコード自身の値を比較に使うためのものです。この例では、レートがコメント数+トラックバック数より小さいものを対象にしようとしています。(改ページ)Aggregationは、集合関数を使う方法です。aggregateとannotateがあり、aggregateは集計した結果を辞書で返します。annotateは結果をモデルインスタンスのプロパティに追加して返します。AggregateとAnnotateはマネージャに対してのみでなく、Querysetに対しても使えます。

F/Aggregations>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))

http://docs.djangoproject.com/en/1.2/topics/db/queries/#filters-can-reference-fields-on-the-model

>>> from django.db.models import Avg, Max, Min, Count>>> Book.objects.aggregate(Avg('price'), Max('price'), Min('price')){'price__avg': 34.35,'price__max': Decimal('81.20'),'price__min': Decimal('12.99')}

>>> u = User.objects.annotate(entry_count=Count('entry__id'))>>> u[0].entry_count3

http://docs.djangoproject.com/en/1.2/topics/db/aggregation/

ちょっと寄り道して、FとAggregationについて見ておきましょう。(改ページ)Fは、レコード自身の値を比較に使うためのものです。この例では、レートがコメント数+トラックバック数より小さいものを対象にしようとしています。(改ページ)Aggregationは、集合関数を使う方法です。aggregateとannotateがあり、aggregateは集計した結果を辞書で返します。annotateは結果をモデルインスタンスのプロパティに追加して返します。AggregateとAnnotateはマネージャに対してのみでなく、Querysetに対しても使えます。

Managerclass PublicManager(models.Manager): def get_query_set(self): return super(PublicManager, self).get_query_set()\ .filter(open_date__lte=datetime.now)

class Entry(models.Model): ... #Field objects = models.Manager() public_objects = PublicManager()

「よく使う絞り込みをなんども書くのがDRYじゃなくって馬鹿らしい?」カスタムマネージャを定義しましょう。カスタムマネージャには、Querysetを生成して返すget_query_setという名前のメソッドを定義します。モデルにカスタムマネージャのインスタンスを持たせます。(改ページ)この例の場合にはEntryモデルのpublic_objectsはpub_dateが現在以前のものに予め絞り込んだQuerysetを返します。あえて、objectsに普通のManagerインスタンスを持たせているのは、Djangoはモデルにマネージャを見つけるとデフォルトのマネージャを追加してくれないためです。Djangoからは絶対この条件でしか抽出しないということであれば、定義しなくても構いません。

Managerclass PublicManager(models.Manager): def get_query_set(self): return super(PublicManager, self).get_query_set()\ .filter(open_date__lte=datetime.now)

class Entry(models.Model): ... #Field objects = models.Manager() public_objects = PublicManager()

>>> Entry.public_objects.all()open_dateが現在以前に絞られたQuerysetが返る

「よく使う絞り込みをなんども書くのがDRYじゃなくって馬鹿らしい?」カスタムマネージャを定義しましょう。カスタムマネージャには、Querysetを生成して返すget_query_setという名前のメソッドを定義します。モデルにカスタムマネージャのインスタンスを持たせます。(改ページ)この例の場合にはEntryモデルのpublic_objectsはpub_dateが現在以前のものに予め絞り込んだQuerysetを返します。あえて、objectsに普通のManagerインスタンスを持たせているのは、Djangoはモデルにマネージャを見つけるとデフォルトのマネージャを追加してくれないためです。Djangoからは絶対この条件でしか抽出しないということであれば、定義しなくても構いません。

MultiDB

ようやくDjangoも一人前になりました。(改ページ)データベースの設定が複数できるようになりました。 今までは、Djangoインスタンスごとにデータベースの設定を変えての運用であれば、マスタースレーブ的な使い方はできましたが、マスターを複数に分けることはできませんでした。

MultiDBDATABASES = { 'default': { 'ENGINE': 'django.db.backends.', 'NAME': '', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', }}

DATABASE_ENGINE = ''DATABASE_NAME = ''DATABASE_USER = ''DATABASE_PASSWORD = ''DATABASE_HOST = ''DATABASE_PORT = ''

Before 1.2 After 1.2

ようやくDjangoも一人前になりました。(改ページ)データベースの設定が複数できるようになりました。 今までは、Djangoインスタンスごとにデータベースの設定を変えての運用であれば、マスタースレーブ的な使い方はできましたが、マスターを複数に分けることはできませんでした。

データベースの選択

>>> Author.objects.all()

>>> Author.objects.using('default').all()

>>> Author.objects.using('slave').all()

usingで指定すればいいぜ

http://docs.djangoproject.com/en/1.2/topics/db/multi-db/

usingで利用するデータベースを指定できます。usingで指定しない場合には、defaultという名前にフォールバックします。(改ページ)でもでも、これはこれで使うことがあるかもしれないですが、イケテてませんよね?

データベースの選択

>>> Author.objects.all()

>>> Author.objects.using('default').all()

>>> Author.objects.using('slave').all()

usingで指定すればいいぜ

http://docs.djangoproject.com/en/1.2/topics/db/multi-db/

usingで利用するデータベースを指定できます。usingで指定しない場合には、defaultという名前にフォールバックします。(改ページ)でもでも、これはこれで使うことがあるかもしれないですが、イケテてませんよね?

DatabaseRoutersclass MyAppRouter(object):

def db_for_read(self, model, **hints): # return database name or None def db_for_write(self, model, **hints): # return database name or None

def allow_relation(self, obj1, obj2, **hints): #return True or None def allow_syncdb(self, db, model): # return True or False or None

DATABASE_ROUTERS = ['path.to.MyRouter', '...', '']http://docs.djangoproject.com/en/1.2/topics/db/multi-db/#multiple-databases

そう。ロジックにゴリゴリと利用するデータベース名を書いてしまったら、再利用できませんよね。どんな環境で使うかわかりませんから。アプリケーション内は特に気にすることなくロジックを書き、プロジェクトごとに必要な設定を行えばよいのです。単純なマスタースレーブでDjangoも複数インスタンス用意して振り分けるのであれば、わざわざDatabaseRoutersを使う必要もないかもしれません。DatabaseRoutersは読み込みを行うとき、書き込みを行うとき、関連を持たせようとするとき、syncdbしようとしたときに、それぞれ呼ばれます。渡されてくる値を参照して、シャーディングのような使い方もできます。各メソッドで渡されたものについて判断がつかない場合には、Noneを返します。Noneが帰ってくると、Djangoは次のDatabaseRouterに問い合わせます。最後まで判断がつかないものについてはdefaultにフォールバックされます。簡単ですね。

Admin開発と運用のお供に

そもそもDjangoはこれを作るために開発が始まったのですから、かなりの自由度をもっています。Djangoを使った開発の速度を非常に押し上げてくれる部分ですが、あくまでdjango.contribのアプリケーションなので軽く流します。

http://bitbucket.org/tsuyukimakoto/私のブログで使っている管理画面です。(改ページ)(動画 1:30秒) しばらくいじっていないのですが、いまではこんなものよりさらにカスタマイズできるようになっています。各々のモデルが管理画面で操作された場合のロジックもカスタマイズ出来るようになっています。そこまでやらなくても、モデルアドミンというモデルに対応して定義できる仕組みを使うと、テンプレートや振る舞いをカスタマイズできます。このコードはほぼほぼこのままのものをbitbucketに公開しています。今なら簡単にできることを非常に頑張ってたりするので、ちょっと悲しいです。

http://bitbucket.org/tsuyukimakoto/私のブログで使っている管理画面です。(改ページ)(動画 1:30秒) しばらくいじっていないのですが、いまではこんなものよりさらにカスタマイズできるようになっています。各々のモデルが管理画面で操作された場合のロジックもカスタマイズ出来るようになっています。そこまでやらなくても、モデルアドミンというモデルに対応して定義できる仕組みを使うと、テンプレートや振る舞いをカスタマイズできます。このコードはほぼほぼこのままのものをbitbucketに公開しています。今なら簡単にできることを非常に頑張ってたりするので、ちょっと悲しいです。

AdminActions

http://docs.djangoproject.com/en/1.2/ref/contrib/admin/actions/さて、実はレコードの一覧画面で、一括処理ができるようになっています。しかも、非常に簡単に定義できます。(改ページ)簡単な関数を定義して、ModelAdminに利用可能アクションとして設定するだけです。関数に渡ってくるQuerysetは選択されたデータに絞り込まれるQuerysetです。ちなみに例のようなコードでbulk updateすると、モデルのsave関数が呼ばれないので、個々の保存プロセスをきちんと通したい場合には一つづつ取り出して処理するコードを書いてください。

AdminActions

http://docs.djangoproject.com/en/1.2/ref/contrib/admin/actions/

from django.contrib import adminfrom myapp.models import Article

def make_published(modeladmin, request, queryset): queryset.update(status='p')

class ArticleAdmin(admin.ModelAdmin): actions = [make_published]

admin.site.register(Article, ArticleAdmin)

さて、実はレコードの一覧画面で、一括処理ができるようになっています。しかも、非常に簡単に定義できます。(改ページ)簡単な関数を定義して、ModelAdminに利用可能アクションとして設定するだけです。関数に渡ってくるQuerysetは選択されたデータに絞り込まれるQuerysetです。ちなみに例のようなコードでbulk updateすると、モデルのsave関数が呼ばれないので、個々の保存プロセスをきちんと通したい場合には一つづつ取り出して処理するコードを書いてください。

Template差し替え

http://www.ellingtoncms.com/

さて、もし、管理画面をお客さんに提供するときに、「Django 管理サイト」と表示されていると少し値切られてしまいそうですよね。Djangoの管理画面は、EllingtonというCMSとして販売もされています。当然、管理画面のテンプレートは様々な方法でカスタマイズが可能です。かなりフックポイントが多いので、今回はadminに特化しない方法について触れます。

Template Loadersand Inherits

えーっと。本当はいつもここだけを話していたいくらいです。テンプレートのローダーとテンプレートの継承についてです。(改ページ)settings.pyを見ると、TEMPLATE_LOADERSという設定と、TEMPLATE_DIRSという設定があります。TEMPLATE_LOADERSにはfilesystem.Loaderとapp_directories.Loaderが設定されていて、TEMPLATE_DIRSは空っぽです。そういえば、管理画面のテンプレートはどうやって表示されているのでしょうか?TEMPLATE_DIRSの設定は空っぽです。そうです。app_directories.Loaderで表示されているのです。

Template Loadersand Inherits

TEMPLATE_LOADERS = ( 'django.template.loaders.filesystem.Loader', 'django.template.loaders.app_directories.Loader',)

TEMPLATE_DIRS = ()

settings.py

えーっと。本当はいつもここだけを話していたいくらいです。テンプレートのローダーとテンプレートの継承についてです。(改ページ)settings.pyを見ると、TEMPLATE_LOADERSという設定と、TEMPLATE_DIRSという設定があります。TEMPLATE_LOADERSにはfilesystem.Loaderとapp_directories.Loaderが設定されていて、TEMPLATE_DIRSは空っぽです。そういえば、管理画面のテンプレートはどうやって表示されているのでしょうか?TEMPLATE_DIRSの設定は空っぽです。そうです。app_directories.Loaderで表示されているのです。

django.template.loaders .app_directories.Loader

.├── __init__.py├── admin.py├── models.py├── templates│ ├── base.html│ └── index.html│ ├── blog│ │ ├── base.html│ │ ├── entry_detail.html│ │ ├── entry_form.html│ │ └── entry_list.html├── tests.py└── views.py

app_directories.Loaderはその名の通り、アプリケーションディレクトリに関連するテンプレートローダーです。このローダーが設定されていると、Djangoはインストールされているアプリケーションの直下にあるtemplatesディレクトリを起点にテンプレートを探索します。(改ページ)つまり、app_directories.Loaderからは、このように見えます。(改ページ)各アプリケーションの中にあるtemplatesディレクトリを順に探索し、指定された名前のファイルを見つけるとそのファイルを使用するのです。

django.template.loaders .app_directories.Loader

.├── __init__.py├── admin.py├── models.py├── templates│ ├── base.html│ └── index.html│ ├── blog│ │ ├── base.html│ │ ├── entry_detail.html│ │ ├── entry_form.html│ │ └── entry_list.html├── tests.py└── views.py

│ ├── base.html│ └── index.html│ ├── blog│ │ ├── base.html│ │ ├── entry_detail.html│ │ ├── entry_form.html│ │ └── entry_list.html

app_directories.Loaderはその名の通り、アプリケーションディレクトリに関連するテンプレートローダーです。このローダーが設定されていると、Djangoはインストールされているアプリケーションの直下にあるtemplatesディレクトリを起点にテンプレートを探索します。(改ページ)つまり、app_directories.Loaderからは、このように見えます。(改ページ)各アプリケーションの中にあるtemplatesディレクトリを順に探索し、指定された名前のファイルを見つけるとそのファイルを使用するのです。

django.template.loaders .app_directories.Loader

.├── __init__.py├── admin.py├── models.py├── templates│ ├── base.html│ └── index.html│ ├── blog│ │ ├── base.html│ │ ├── entry_detail.html│ │ ├── entry_form.html│ │ └── entry_list.html├── tests.py└── views.py

│ ├── base.html│ └── index.html│ ├── blog│ │ ├── base.html│ │ ├── entry_detail.html│ │ ├── entry_form.html│ │ └── entry_list.html

← base.html

← blog/base.html

← blog/entry_list.html

app_directories.Loaderはその名の通り、アプリケーションディレクトリに関連するテンプレートローダーです。このローダーが設定されていると、Djangoはインストールされているアプリケーションの直下にあるtemplatesディレクトリを起点にテンプレートを探索します。(改ページ)つまり、app_directories.Loaderからは、このように見えます。(改ページ)各アプリケーションの中にあるtemplatesディレクトリを順に探索し、指定された名前のファイルを見つけるとそのファイルを使用するのです。

django.template.loaders .filesystem.Loader

TEMPLATE_DIRS = ( '/path/to/some_directory', '/path/to/second_directory',)

filesystem.LoaderはTEMPLATE_DIRSに設定されたディレクトリを起点として、指定された名前のファイルを探索に行きます。これに関しても指定されている起点の数だけ、順に、探しに行きます。プロジェクトレベルのテンプレートや、上書きするテンプレートはプロジェクトの下にtemplatesというディレクトリを掘って、その中に格納することがベストプラクティスなのでは、と私は考えています。プロジェクト直下に置く場合、開発時はTEMPLATE_DIRSを動的に指定できます。(改ページ)こうすることで、各開発者がどこにプロジェクトをチェックアウトしても変更が不要になります。

django.template.loaders .filesystem.Loader

TEMPLATE_DIRS = ( '/path/to/some_directory', '/path/to/second_directory',)

import osTEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates'),)

filesystem.LoaderはTEMPLATE_DIRSに設定されたディレクトリを起点として、指定された名前のファイルを探索に行きます。これに関しても指定されている起点の数だけ、順に、探しに行きます。プロジェクトレベルのテンプレートや、上書きするテンプレートはプロジェクトの下にtemplatesというディレクトリを掘って、その中に格納することがベストプラクティスなのでは、と私は考えています。プロジェクト直下に置く場合、開発時はTEMPLATE_DIRSを動的に指定できます。(改ページ)こうすることで、各開発者がどこにプロジェクトをチェックアウトしても変更が不要になります。

admin/site_base.htmlを差し替える{% extends "admin/base.html" %}{% load i18n %}

{% block title %}{{ title }} | {% trans 'Django site admin' %}{% endblock %}

{% block branding %}<h1 id="site-name">{% trans 'Django administration' %}</h1>{% endblock %}

{% block nav-global %}{% endblock %}PYTHONPATH/django/contrib/admin/templates/admin/site_base.html

さて、いよいよ「Django 管理サイト」と記述のあるテンプレートを差し替えましょう。Djangoの管理サイトを表す文字列は国際化されている2箇所ですね。双方ともこの文字列を表すことが目的のようなブロックで定義されているので、さらに子供のテンプレートで修正しても良いかもしれません。が、どう考えても大量の子供テンプレートがいそうです。

admin/site_base.htmlを差し替える

結局どうするの?

さて、別のadmin/site_base.htmlを使うようにするには、どうすれば良いでしょうか?…(改ページ)app_directory.Loaderがテンプレートを見つけ出す前に、filesystem.Loaderが書き換えたテンプレートを見つけるようにしてやればいいだけですね。(ちょいと待ち)いいですか?

admin/site_base.htmlを差し替える

プロジェクトの直下にtemplates/adminディレクトリを作成する

templates/adminディレクトリにsite_base.htmlをコピーする

コピーしたsite_base.htmlを書き換える

settings.pyのTEMPLATES_DIRSにプロジェクト直下のtemplatesディレクトリを追加する

さて、別のadmin/site_base.htmlを使うようにするには、どうすれば良いでしょうか?…(改ページ)app_directory.Loaderがテンプレートを見つけ出す前に、filesystem.Loaderが書き換えたテンプレートを見つけるようにしてやればいいだけですね。(ちょいと待ち)いいですか?

Template継承テンプレート

TemplateシステムはTemplate Loaderとの組み合わせでアプリケーションという単位を際立たせています。

と、Templateの前にHandler URLconfMiddleware View

Model

process_request

resolvecallback,arg,kwarg

process_view

callback(request, arg, kwarg)

responseprocess_response

FormTemplate

Templateについて詳しく見る前に、Djangoのフローを確認しておきましょう。WebサーバからDjangoへは、まずHandlerと呼ばれる機能が呼び出されます。HandlerはWebサーバからの環境変数をRequestと呼ばれるラッパクラスのインスタンスとして生成します。次に、設定されているMiddlewareのprocess_requestメソッドを呼び出します。(改ページ)これは、settings.pyに初期設定されているMiddlewareです。Middlewareでは、例えばユーザのオブジェクトをRequestのインスタンスにセットしたりしています。(改ページ)次に、リクエストされたURLからビュー関数と引数を問い合せます。ビュー関数が無事に得られると、HandlerはMiddlewareのprocess_viewメソッドを呼び出します。次に、VIew関数を呼び出し、VIew関数は、ModelやForm、Templateを好きに用いてResponseクラスのインスタンスを生成し、Handlerへ返します。HandlerはMiddlewareのprocess_responseを呼び出します。

と、Templateの前にHandler URLconfMiddleware View

Model

process_request

resolvecallback,arg,kwarg

process_view

callback(request, arg, kwarg)

responseprocess_response

FormTemplate

MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',)

Templateについて詳しく見る前に、Djangoのフローを確認しておきましょう。WebサーバからDjangoへは、まずHandlerと呼ばれる機能が呼び出されます。HandlerはWebサーバからの環境変数をRequestと呼ばれるラッパクラスのインスタンスとして生成します。次に、設定されているMiddlewareのprocess_requestメソッドを呼び出します。(改ページ)これは、settings.pyに初期設定されているMiddlewareです。Middlewareでは、例えばユーザのオブジェクトをRequestのインスタンスにセットしたりしています。(改ページ)次に、リクエストされたURLからビュー関数と引数を問い合せます。ビュー関数が無事に得られると、HandlerはMiddlewareのprocess_viewメソッドを呼び出します。次に、VIew関数を呼び出し、VIew関数は、ModelやForm、Templateを好きに用いてResponseクラスのインスタンスを生成し、Handlerへ返します。HandlerはMiddlewareのprocess_responseを呼び出します。

と、Templateの前にHandler URLconfMiddleware View

Model

process_request

resolvecallback,arg,kwarg

process_view

callback(request, arg, kwarg)

responseprocess_response

FormTemplate

Templateについて詳しく見る前に、Djangoのフローを確認しておきましょう。WebサーバからDjangoへは、まずHandlerと呼ばれる機能が呼び出されます。HandlerはWebサーバからの環境変数をRequestと呼ばれるラッパクラスのインスタンスとして生成します。次に、設定されているMiddlewareのprocess_requestメソッドを呼び出します。(改ページ)これは、settings.pyに初期設定されているMiddlewareです。Middlewareでは、例えばユーザのオブジェクトをRequestのインスタンスにセットしたりしています。(改ページ)次に、リクエストされたURLからビュー関数と引数を問い合せます。ビュー関数が無事に得られると、HandlerはMiddlewareのprocess_viewメソッドを呼び出します。次に、VIew関数を呼び出し、VIew関数は、ModelやForm、Templateを好きに用いてResponseクラスのインスタンスを生成し、Handlerへ返します。HandlerはMiddlewareのprocess_responseを呼び出します。

MiddlewareSomeMiddleware(object): def __init__(self): #some initialize def process_request(self, request): return Response or None def process_view(self, request, view_func, view_args, view_kwargs): return Response or None def process_response(self, request, response): return Response or None def process_exception(self, request, exception): return Response or None

Middlewareは、CSRF対策や、リクエスト単位でのトランザクション制御等、ほとんどのリクエストに共通して適用したい処理をうけもつものです。他のフレームワークではFilterと呼ばれている機能に相当します。settings.pyに設定されているMiddlewareを順に各フェーズで呼び出します。process_responseだけは、設定されている順番の逆に呼び出されます。いずれかの処理がResponseを返すと、その時点でHandlerはWebサーバへResponseを返して処理を終了します。Middlewareはこのメソッドのいずれか、ないしは全部でもいいのですが、定義してsettings.pyに設定しておけば独自のものを使えます。

URLconfROOT_URLCONF = 'gumistudy.urls'

gumistudy/settings.py

http://docs.djangoproject.com/en/1.2/topics/http/urls/

URLconfは、リクエストされたURLとビュー関数の関連を定義するものです。(改ページ)たとえば、このURLに対してリクエストが来たとします。settings.pyのROOT_URLCONFの設定は、gumistudy.urlsとしていますので、(改ページ)、gumistudyディレクトリ直下のurls.pyを参照します。この時点でスキーマとホストに当たる部分は剥ぎ取られています。urlpatternsにそれっぽい文字列がありました。blog.urlsがincludeされており、移譲されているため、blog/urls.pyを参照します。(改ページ)、この時点で、すでにマッチした文字列は剥ぎ取られています。はい。一致する文字列を見つけました。設定されているビュー関数を呼び出します。ちなみに、ここに書かれているnameはURLの逆引き参照で利用します。(改ページ)URLはアプリケーションをどのように使うかによって決定するため、PythonコードやTemplateに直に書くことができません。ですから、このURLの逆引きは非常に重要な意味を持っています。逆引きの際、view関数のフルネームでも呼べますが、Djangoは最初に見つけたurlpatternを使おうとするので、同じView関数を2箇所で呼べなくなってしまいます。このnameが他とかぶると、さらにnamespaceなんてものを使ったりしなければならず、現実的には再利用できなくなってしまうので、頑張って名前を考えてください。

URLconfROOT_URLCONF = 'gumistudy.urls'

gumistudy/settings.py

http://host/gumistudy/blog/entry/1/

http://docs.djangoproject.com/en/1.2/topics/http/urls/

URLconfは、リクエストされたURLとビュー関数の関連を定義するものです。(改ページ)たとえば、このURLに対してリクエストが来たとします。settings.pyのROOT_URLCONFの設定は、gumistudy.urlsとしていますので、(改ページ)、gumistudyディレクトリ直下のurls.pyを参照します。この時点でスキーマとホストに当たる部分は剥ぎ取られています。urlpatternsにそれっぽい文字列がありました。blog.urlsがincludeされており、移譲されているため、blog/urls.pyを参照します。(改ページ)、この時点で、すでにマッチした文字列は剥ぎ取られています。はい。一致する文字列を見つけました。設定されているビュー関数を呼び出します。ちなみに、ここに書かれているnameはURLの逆引き参照で利用します。(改ページ)URLはアプリケーションをどのように使うかによって決定するため、PythonコードやTemplateに直に書くことができません。ですから、このURLの逆引きは非常に重要な意味を持っています。逆引きの際、view関数のフルネームでも呼べますが、Djangoは最初に見つけたurlpatternを使おうとするので、同じView関数を2箇所で呼べなくなってしまいます。このnameが他とかぶると、さらにnamespaceなんてものを使ったりしなければならず、現実的には再利用できなくなってしまうので、頑張って名前を考えてください。

URLconfROOT_URLCONF = 'gumistudy.urls'

from django.conf.urls.defaults import *urlpatterns = patterns('', (r'^gumistudy/blog/', include('blog.urls')),)

gumistudy/settings.py

gumistudy/urls.py

http://host/gumistudy/blog/entry/1/gumistudy/blog/entry/1/

http://docs.djangoproject.com/en/1.2/topics/http/urls/

URLconfは、リクエストされたURLとビュー関数の関連を定義するものです。(改ページ)たとえば、このURLに対してリクエストが来たとします。settings.pyのROOT_URLCONFの設定は、gumistudy.urlsとしていますので、(改ページ)、gumistudyディレクトリ直下のurls.pyを参照します。この時点でスキーマとホストに当たる部分は剥ぎ取られています。urlpatternsにそれっぽい文字列がありました。blog.urlsがincludeされており、移譲されているため、blog/urls.pyを参照します。(改ページ)、この時点で、すでにマッチした文字列は剥ぎ取られています。はい。一致する文字列を見つけました。設定されているビュー関数を呼び出します。ちなみに、ここに書かれているnameはURLの逆引き参照で利用します。(改ページ)URLはアプリケーションをどのように使うかによって決定するため、PythonコードやTemplateに直に書くことができません。ですから、このURLの逆引きは非常に重要な意味を持っています。逆引きの際、view関数のフルネームでも呼べますが、Djangoは最初に見つけたurlpatternを使おうとするので、同じView関数を2箇所で呼べなくなってしまいます。このnameが他とかぶると、さらにnamespaceなんてものを使ったりしなければならず、現実的には再利用できなくなってしまうので、頑張って名前を考えてください。

URLconfROOT_URLCONF = 'gumistudy.urls'

from django.conf.urls.defaults import *urlpatterns = patterns('', (r'^gumistudy/blog/', include('blog.urls')),)

from django.conf.urls.defaults import *from blog.views import show_entryurlpatterns = patterns('', (r'^entry/(?P<entry_id>\d+)/$', show_entry, name="blog_show_entry"),)

gumistudy/settings.py

gumistudy/urls.py

blog/urls.py

http://host/gumistudy/blog/entry/1/gumistudy/blog/entry/1/entry/1/

http://docs.djangoproject.com/en/1.2/topics/http/urls/

URLconfは、リクエストされたURLとビュー関数の関連を定義するものです。(改ページ)たとえば、このURLに対してリクエストが来たとします。settings.pyのROOT_URLCONFの設定は、gumistudy.urlsとしていますので、(改ページ)、gumistudyディレクトリ直下のurls.pyを参照します。この時点でスキーマとホストに当たる部分は剥ぎ取られています。urlpatternsにそれっぽい文字列がありました。blog.urlsがincludeされており、移譲されているため、blog/urls.pyを参照します。(改ページ)、この時点で、すでにマッチした文字列は剥ぎ取られています。はい。一致する文字列を見つけました。設定されているビュー関数を呼び出します。ちなみに、ここに書かれているnameはURLの逆引き参照で利用します。(改ページ)URLはアプリケーションをどのように使うかによって決定するため、PythonコードやTemplateに直に書くことができません。ですから、このURLの逆引きは非常に重要な意味を持っています。逆引きの際、view関数のフルネームでも呼べますが、Djangoは最初に見つけたurlpatternを使おうとするので、同じView関数を2箇所で呼べなくなってしまいます。このnameが他とかぶると、さらにnamespaceなんてものを使ったりしなければならず、現実的には再利用できなくなってしまうので、頑張って名前を考えてください。

URLconfROOT_URLCONF = 'gumistudy.urls'

from django.conf.urls.defaults import *urlpatterns = patterns('', (r'^gumistudy/blog/', include('blog.urls')),)

from django.conf.urls.defaults import *from blog.views import show_entryurlpatterns = patterns('', (r'^entry/(?P<entry_id>\d+)/$', show_entry, name="blog_show_entry"),)

gumistudy/settings.py

gumistudy/urls.py

blog/urls.py

http://host/gumistudy/blog/entry/1/gumistudy/blog/entry/1/entry/1/

Pythonコード:reverse('blog_show_entry', entry_id=1)

Templateコード:{% url blog_show_entry entry_id=1 %}

named url pattern

http://docs.djangoproject.com/en/1.2/topics/http/urls/

URLconfは、リクエストされたURLとビュー関数の関連を定義するものです。(改ページ)たとえば、このURLに対してリクエストが来たとします。settings.pyのROOT_URLCONFの設定は、gumistudy.urlsとしていますので、(改ページ)、gumistudyディレクトリ直下のurls.pyを参照します。この時点でスキーマとホストに当たる部分は剥ぎ取られています。urlpatternsにそれっぽい文字列がありました。blog.urlsがincludeされており、移譲されているため、blog/urls.pyを参照します。(改ページ)、この時点で、すでにマッチした文字列は剥ぎ取られています。はい。一致する文字列を見つけました。設定されているビュー関数を呼び出します。ちなみに、ここに書かれているnameはURLの逆引き参照で利用します。(改ページ)URLはアプリケーションをどのように使うかによって決定するため、PythonコードやTemplateに直に書くことができません。ですから、このURLの逆引きは非常に重要な意味を持っています。逆引きの際、view関数のフルネームでも呼べますが、Djangoは最初に見つけたurlpatternを使おうとするので、同じView関数を2箇所で呼べなくなってしまいます。このnameが他とかぶると、さらにnamespaceなんてものを使ったりしなければならず、現実的には再利用できなくなってしまうので、頑張って名前を考えてください。

View

http://docs.djangoproject.com/en/1.2/topics/http/views/ビュー、は他のMVCフレームワークと明らかに違う名前の使い方をしています。MVCのVはWebの場合テンプレートを表すことが多いのですが、Djangoの場合は、ユーザに何を提示するかを決める場所、としています。(改ページ)で、View関数は、requestを受け取ってdjango.http.HttpResponseのインスタンスを返せばいいだけです。簡単です。ただし、重要なのはこのHttpResponseに渡す文字列で、まぁ、その文字列を生成するために日夜戦うことになります。ちなみにクラスベースのアクションをどうしても作りたいんだって人は、クラスベースでも作れるようになっているので頑張ってください。興味ないのでスルーします。(改ページ)テンプレートを使う、普段書くことになる定形文はこんな感じです。きちんとテンプレートのオブジェクトを生成してレンダリングして、という手順を踏んでも良いのですが、この例ではrender_to_responseというショートカットを使っています。ビュー関数で生成した値をテンプレートに渡しています。最後の引数、context_instanceにRequestContextというものを渡していますね。これはなんでしょう。

View

http://docs.djangoproject.com/en/1.2/topics/http/views/

from django.http import HttpResponse

def show_entry(request, entry_id): return HttpResponse(u'I got some entry_id')

ビュー、は他のMVCフレームワークと明らかに違う名前の使い方をしています。MVCのVはWebの場合テンプレートを表すことが多いのですが、Djangoの場合は、ユーザに何を提示するかを決める場所、としています。(改ページ)で、View関数は、requestを受け取ってdjango.http.HttpResponseのインスタンスを返せばいいだけです。簡単です。ただし、重要なのはこのHttpResponseに渡す文字列で、まぁ、その文字列を生成するために日夜戦うことになります。ちなみにクラスベースのアクションをどうしても作りたいんだって人は、クラスベースでも作れるようになっているので頑張ってください。興味ないのでスルーします。(改ページ)テンプレートを使う、普段書くことになる定形文はこんな感じです。きちんとテンプレートのオブジェクトを生成してレンダリングして、という手順を踏んでも良いのですが、この例ではrender_to_responseというショートカットを使っています。ビュー関数で生成した値をテンプレートに渡しています。最後の引数、context_instanceにRequestContextというものを渡していますね。これはなんでしょう。

View

http://docs.djangoproject.com/en/1.2/topics/http/views/

from django.http import HttpResponse

def show_entry(request, entry_id): return HttpResponse(u'I got some entry_id')

return render_to_response('blog/comment_form.html', dict(form=form, object=entry), context_instance=RequestContext(request))

ビュー、は他のMVCフレームワークと明らかに違う名前の使い方をしています。MVCのVはWebの場合テンプレートを表すことが多いのですが、Djangoの場合は、ユーザに何を提示するかを決める場所、としています。(改ページ)で、View関数は、requestを受け取ってdjango.http.HttpResponseのインスタンスを返せばいいだけです。簡単です。ただし、重要なのはこのHttpResponseに渡す文字列で、まぁ、その文字列を生成するために日夜戦うことになります。ちなみにクラスベースのアクションをどうしても作りたいんだって人は、クラスベースでも作れるようになっているので頑張ってください。興味ないのでスルーします。(改ページ)テンプレートを使う、普段書くことになる定形文はこんな感じです。きちんとテンプレートのオブジェクトを生成してレンダリングして、という手順を踏んでも良いのですが、この例ではrender_to_responseというショートカットを使っています。ビュー関数で生成した値をテンプレートに渡しています。最後の引数、context_instanceにRequestContextというものを渡していますね。これはなんでしょう。

RequestContext

http://docs.djangoproject.com/en/1.2/ref/templates/api/#subclassing-context-requestcontextRequestContextはdjango.template.Contextのサブクラスで、インスタンス生成時の第一引数にrequestオブジェクトを取ります。第二引数以降にRequestContextに登録したいデータを辞書で渡します。そもそもdjango.template.Contextというのは、テンプレートのレンダリング時に使える変数を詰めたものですね。Contextイコール文脈なので、わかりますね。(改ページ)実は、RequestContextが自動で保持するデータを定義できます。settings.pyの親玉、global_settings.pyにその記述があります。TEMPLATE_CONTEXT_PROCESSORSにコンテキストプロセッサという関数を登録しています。コンテキストプロセッサ自体の定義は非常に簡単です。(改ページ)requestオブジェクトを受け取って、辞書を返すだけ。(改ページ)RequestContextのインスタンスに常にデータを格納しておきたい場合には、settings.pyにTEMPLATE_CONTEXT_PROCESSORSを定義します。(改ページ)場合によるのであれば、RequestContextを生成する際に指定します。

RequestContext

http://docs.djangoproject.com/en/1.2/ref/templates/api/#subclassing-context-requestcontext

TEMPLATE_CONTEXT_PROCESSORS = ( 'django.contrib.auth.context_processors.auth', 'django.core.context_processors.debug', 'django.core.context_processors.i18n', 'django.core.context_processors.media',# 'django.core.context_processors.request', 'django.contrib.messages.context_processors.messages',)

django/conf/global_settings.py

RequestContextはdjango.template.Contextのサブクラスで、インスタンス生成時の第一引数にrequestオブジェクトを取ります。第二引数以降にRequestContextに登録したいデータを辞書で渡します。そもそもdjango.template.Contextというのは、テンプレートのレンダリング時に使える変数を詰めたものですね。Contextイコール文脈なので、わかりますね。(改ページ)実は、RequestContextが自動で保持するデータを定義できます。settings.pyの親玉、global_settings.pyにその記述があります。TEMPLATE_CONTEXT_PROCESSORSにコンテキストプロセッサという関数を登録しています。コンテキストプロセッサ自体の定義は非常に簡単です。(改ページ)requestオブジェクトを受け取って、辞書を返すだけ。(改ページ)RequestContextのインスタンスに常にデータを格納しておきたい場合には、settings.pyにTEMPLATE_CONTEXT_PROCESSORSを定義します。(改ページ)場合によるのであれば、RequestContextを生成する際に指定します。

RequestContext

http://docs.djangoproject.com/en/1.2/ref/templates/api/#subclassing-context-requestcontext

def ip_address_processor(request): return {'ip_address': request.META['REMOTE_ADDR']}

blog/context_processors.py

RequestContextはdjango.template.Contextのサブクラスで、インスタンス生成時の第一引数にrequestオブジェクトを取ります。第二引数以降にRequestContextに登録したいデータを辞書で渡します。そもそもdjango.template.Contextというのは、テンプレートのレンダリング時に使える変数を詰めたものですね。Contextイコール文脈なので、わかりますね。(改ページ)実は、RequestContextが自動で保持するデータを定義できます。settings.pyの親玉、global_settings.pyにその記述があります。TEMPLATE_CONTEXT_PROCESSORSにコンテキストプロセッサという関数を登録しています。コンテキストプロセッサ自体の定義は非常に簡単です。(改ページ)requestオブジェクトを受け取って、辞書を返すだけ。(改ページ)RequestContextのインスタンスに常にデータを格納しておきたい場合には、settings.pyにTEMPLATE_CONTEXT_PROCESSORSを定義します。(改ページ)場合によるのであれば、RequestContextを生成する際に指定します。

RequestContext

http://docs.djangoproject.com/en/1.2/ref/templates/api/#subclassing-context-requestcontext

def ip_address_processor(request): return {'ip_address': request.META['REMOTE_ADDR']}

blog/context_processors.pyTEMPLATE_CONTEXT_PROCESSORS = \ ('blog.context_processors.ip_address_processor', ) + TEMPLATE_CONTEXT_PROCESSORS

settings.py

RequestContextはdjango.template.Contextのサブクラスで、インスタンス生成時の第一引数にrequestオブジェクトを取ります。第二引数以降にRequestContextに登録したいデータを辞書で渡します。そもそもdjango.template.Contextというのは、テンプレートのレンダリング時に使える変数を詰めたものですね。Contextイコール文脈なので、わかりますね。(改ページ)実は、RequestContextが自動で保持するデータを定義できます。settings.pyの親玉、global_settings.pyにその記述があります。TEMPLATE_CONTEXT_PROCESSORSにコンテキストプロセッサという関数を登録しています。コンテキストプロセッサ自体の定義は非常に簡単です。(改ページ)requestオブジェクトを受け取って、辞書を返すだけ。(改ページ)RequestContextのインスタンスに常にデータを格納しておきたい場合には、settings.pyにTEMPLATE_CONTEXT_PROCESSORSを定義します。(改ページ)場合によるのであれば、RequestContextを生成する際に指定します。

RequestContext

http://docs.djangoproject.com/en/1.2/ref/templates/api/#subclassing-context-requestcontext

def ip_address_processor(request): return {'ip_address': request.META['REMOTE_ADDR']}

blog/context_processors.pyTEMPLATE_CONTEXT_PROCESSORS = \ ('blog.context_processors.ip_address_processor', ) + TEMPLATE_CONTEXT_PROCESSORS

settings.pyreturn render_to_response('blog/comment_form.html', dict(form=form, object=entry), context_instance=RequestContext(request,{}, [ip_address_processor]))

RequestContextはdjango.template.Contextのサブクラスで、インスタンス生成時の第一引数にrequestオブジェクトを取ります。第二引数以降にRequestContextに登録したいデータを辞書で渡します。そもそもdjango.template.Contextというのは、テンプレートのレンダリング時に使える変数を詰めたものですね。Contextイコール文脈なので、わかりますね。(改ページ)実は、RequestContextが自動で保持するデータを定義できます。settings.pyの親玉、global_settings.pyにその記述があります。TEMPLATE_CONTEXT_PROCESSORSにコンテキストプロセッサという関数を登録しています。コンテキストプロセッサ自体の定義は非常に簡単です。(改ページ)requestオブジェクトを受け取って、辞書を返すだけ。(改ページ)RequestContextのインスタンスに常にデータを格納しておきたい場合には、settings.pyにTEMPLATE_CONTEXT_PROCESSORSを定義します。(改ページ)場合によるのであれば、RequestContextを生成する際に指定します。

Template! <html>!!! <head>{% block title %}プロジェクト名:{% endblock title %}</head> <script type="text/javascript" src="common.js"></script> {% block custom_js %}{% endblock custom_js %}!!! <body>!!! <div id="menu">{% block menu %}{% endblock menu %}</div>!!! <div id="content">{% block content %}{% endblock content %}</div> <div id=”copyright”>{% block copyright %}everes{% endblock %}</div>!!! </body>! </html>

! {% extends 'base.html% %}! {% block title %}{{ block.super }} アプリ名とか{% endblock title %}! {% block menu %}アプリレベルのメニューとか{% endblock menu %}

! {% extends 'app/base.html' %}! {% block content %}内容内容内容無いよう{% endblock content %}

base.html

app/base.html

app/some.htmlさて、個人的にDjangoで最重要なテクノロジだと思っています、いよいよTemplateの継承を見ていきましょう。base.htmlというテンプレートがあるとします。blockとendblockというテンプレートタグで囲まれた部分が幾つかありますよね。(改ページ)、まずはblock titleに注目しましょう。で、このbase.htmlを継承しているapp/base.htmlというテンプレートがあります。(改ページ)こっちのテンプレートにもblock titleがありますね。上のbase.htmlを使った場合にはプロジェクト名コロンという文字列が出力されます。app/base.htmlを使った場合には、block.superという親の結果を出力する変数を使っているので、プロジェクト名コロン アプリ名とか という文字列が出力されます。(改ページ)同様に、base.htmlのblock contentをapp/some.htmlのblock contentが上書きをしています。通常のPythonでクラスの継承をするのと考え方は同じなので、試しに使ってみてください。さっき話しに出てきたテンプレートローダーの話と組み合わせると夢広がりませんか?

Template! <html>!!! <head>{% block title %}プロジェクト名:{% endblock title %}</head> <script type="text/javascript" src="common.js"></script> {% block custom_js %}{% endblock custom_js %}!!! <body>!!! <div id="menu">{% block menu %}{% endblock menu %}</div>!!! <div id="content">{% block content %}{% endblock content %}</div> <div id=”copyright”>{% block copyright %}everes{% endblock %}</div>!!! </body>! </html>

! {% extends 'base.html% %}! {% block title %}{{ block.super }} アプリ名とか{% endblock title %}! {% block menu %}アプリレベルのメニューとか{% endblock menu %}

! {% extends 'app/base.html' %}! {% block content %}内容内容内容無いよう{% endblock content %}

!!! <head>{% block title %}プロジェクト名:{% endblock title %}</head>

base.html

app/base.html

app/some.htmlさて、個人的にDjangoで最重要なテクノロジだと思っています、いよいよTemplateの継承を見ていきましょう。base.htmlというテンプレートがあるとします。blockとendblockというテンプレートタグで囲まれた部分が幾つかありますよね。(改ページ)、まずはblock titleに注目しましょう。で、このbase.htmlを継承しているapp/base.htmlというテンプレートがあります。(改ページ)こっちのテンプレートにもblock titleがありますね。上のbase.htmlを使った場合にはプロジェクト名コロンという文字列が出力されます。app/base.htmlを使った場合には、block.superという親の結果を出力する変数を使っているので、プロジェクト名コロン アプリ名とか という文字列が出力されます。(改ページ)同様に、base.htmlのblock contentをapp/some.htmlのblock contentが上書きをしています。通常のPythonでクラスの継承をするのと考え方は同じなので、試しに使ってみてください。さっき話しに出てきたテンプレートローダーの話と組み合わせると夢広がりませんか?

Template! <html>!!! <head>{% block title %}プロジェクト名:{% endblock title %}</head> <script type="text/javascript" src="common.js"></script> {% block custom_js %}{% endblock custom_js %}!!! <body>!!! <div id="menu">{% block menu %}{% endblock menu %}</div>!!! <div id="content">{% block content %}{% endblock content %}</div> <div id=”copyright”>{% block copyright %}everes{% endblock %}</div>!!! </body>! </html>

! {% extends 'base.html% %}! {% block title %}{{ block.super }} アプリ名とか{% endblock title %}! {% block menu %}アプリレベルのメニューとか{% endblock menu %}

! {% extends 'app/base.html' %}! {% block content %}内容内容内容無いよう{% endblock content %}

!!! <head>{% block title %}プロジェクト名:{% endblock title %}</head>

! {% block title %}{{ block.super }} アプリ名とか{% endblock title %} 

base.html

app/base.html

app/some.htmlさて、個人的にDjangoで最重要なテクノロジだと思っています、いよいよTemplateの継承を見ていきましょう。base.htmlというテンプレートがあるとします。blockとendblockというテンプレートタグで囲まれた部分が幾つかありますよね。(改ページ)、まずはblock titleに注目しましょう。で、このbase.htmlを継承しているapp/base.htmlというテンプレートがあります。(改ページ)こっちのテンプレートにもblock titleがありますね。上のbase.htmlを使った場合にはプロジェクト名コロンという文字列が出力されます。app/base.htmlを使った場合には、block.superという親の結果を出力する変数を使っているので、プロジェクト名コロン アプリ名とか という文字列が出力されます。(改ページ)同様に、base.htmlのblock contentをapp/some.htmlのblock contentが上書きをしています。通常のPythonでクラスの継承をするのと考え方は同じなので、試しに使ってみてください。さっき話しに出てきたテンプレートローダーの話と組み合わせると夢広がりませんか?

Template! <html>!!! <head>{% block title %}プロジェクト名:{% endblock title %}</head> <script type="text/javascript" src="common.js"></script> {% block custom_js %}{% endblock custom_js %}!!! <body>!!! <div id="menu">{% block menu %}{% endblock menu %}</div>!!! <div id="content">{% block content %}{% endblock content %}</div> <div id=”copyright”>{% block copyright %}everes{% endblock %}</div>!!! </body>! </html>

! {% extends 'base.html% %}! {% block title %}{{ block.super }} アプリ名とか{% endblock title %}! {% block menu %}アプリレベルのメニューとか{% endblock menu %}

! {% extends 'app/base.html' %}! {% block content %}内容内容内容無いよう{% endblock content %}

! {% block title %}{{ block.super }} アプリ名とか{% endblock title %} 

! {% block content %}内容内容内容無いよう{% endblock content %}

!!! <head>{% block title %}プロジェクト名:{% endblock title %}</head>

!!! <div id="content">{% block content %}{% endblock content %}</div>

base.html

app/base.html

app/some.htmlさて、個人的にDjangoで最重要なテクノロジだと思っています、いよいよTemplateの継承を見ていきましょう。base.htmlというテンプレートがあるとします。blockとendblockというテンプレートタグで囲まれた部分が幾つかありますよね。(改ページ)、まずはblock titleに注目しましょう。で、このbase.htmlを継承しているapp/base.htmlというテンプレートがあります。(改ページ)こっちのテンプレートにもblock titleがありますね。上のbase.htmlを使った場合にはプロジェクト名コロンという文字列が出力されます。app/base.htmlを使った場合には、block.superという親の結果を出力する変数を使っているので、プロジェクト名コロン アプリ名とか という文字列が出力されます。(改ページ)同様に、base.htmlのblock contentをapp/some.htmlのblock contentが上書きをしています。通常のPythonでクラスの継承をするのと考え方は同じなので、試しに使ってみてください。さっき話しに出てきたテンプレートローダーの話と組み合わせると夢広がりませんか?

Template Filters

{{ variable|filter }}

TemplateFilterは値をパイプで関数に渡して、出力をいじるものです。(改ページ)capfirstフィルターを使うと、渡した文字列の最初の文字が大文字になります。日付のフォーマットなんかはフィルターでやれば良いでしょう。

Template Filters

{{ variable|filter }}

{{ 'hoge'|capfirst }} → Hoge

TemplateFilterは値をパイプで関数に渡して、出力をいじるものです。(改ページ)capfirstフィルターを使うと、渡した文字列の最初の文字が大文字になります。日付のフォーマットなんかはフィルターでやれば良いでしょう。

build-in Template Filtersadd

addslashes

capfirst

center

cut

date

default

default_if_none

dictsort

dictsortreversed

divisibleby

escape

escapejs

filesizeformat

first

fix_ampersands

floatformat

force_escape

get_digit

iriencode

join

last

length

length_is

linebreaks

linebreaksbr

linenumbers

ljust

lower

make_list

phone2numeric

pluralize

pprint

random

removetags

rjust

safe

safeseq

slice

slugify

stringformat

striptags

time

timesince

timeuntil

title

truncatewords

truncatewords_html

unordered_list

upper

urlencode

urlize

urlizetrunc

wordcount

wordwrap

yesno

http://docs.djangoproject.com/en/1.2/ref/templates/builtins/デフォルトのフィルターたちです。ドキュメントがあるので、参照すると良いと思います。

Template Tags

{% tag something %}

テンプレートタグは、コンテキスト変数をいじれます。というか、ifタグやらなにやらロジカルなものはみんなテンプレートタグで出来ています。(改ページ)例えばregroupというタグをこう使うと、peopleという辞書のリストが入った変数を、genderというキーで並べ直してgender_listというコンテキスト変数に格納します。Djangoのテンプレートはウェブデザイナーが危険なことや無茶をしないように、ロジックの記述を極限まで排しています。ですから、ウェブデザイナが求めるロジカルな機能はプログラマがテンプレートタグを書いて提供することで実現します。

Template Tags

{% tag something %}

{% regroup people by gender as gender_list %}

テンプレートタグは、コンテキスト変数をいじれます。というか、ifタグやらなにやらロジカルなものはみんなテンプレートタグで出来ています。(改ページ)例えばregroupというタグをこう使うと、peopleという辞書のリストが入った変数を、genderというキーで並べ直してgender_listというコンテキスト変数に格納します。Djangoのテンプレートはウェブデザイナーが危険なことや無茶をしないように、ロジックの記述を極限まで排しています。ですから、ウェブデザイナが求めるロジカルな機能はプログラマがテンプレートタグを書いて提供することで実現します。

build-in Template Tagsautoescape

block

comment

csrf_token

cycle

debug

extends

filter

firstof

for

if

not in

ifchanged

ifequal

ifnotequal

include

load

now

regroup

spaceless

ssi

templatetag

url

widthratio

with

http://docs.djangoproject.com/en/1.2/ref/templates/builtins/デフォルトのテンプレートタグたちです。ドキュメントがあるので参照するといいと思います。

Template Filterを作成する

http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/

.├── __init__.py├── admin.py├── models.py├── templatetags│ ├── __init__.py│ └── mytags.py├── tests.py└── views.py

テンプレートフィルターは自分で定義できます。アプリケーションディレクトリの直下にtemplatetagsというディレクトリを掘って、その中に置きます。__init__.pyはPythonのお約束なのでとにかく作ってください。フィルターの実態を定義するモジュール名は決まりがありませんので、適当な名前を考えてください。今回は最悪な名前mytags.pyにしました。(改ページ)この例のようなファイル名にした場合は、各テンプレートで load mytags として、自作フィルターの一群をテンプレートで使えるように宣言します。変な名前にすると恥ずかしい思いをします。

Template Filterを作成する

http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/

.├── __init__.py├── admin.py├── models.py├── templatetags│ ├── __init__.py│ └── mytags.py├── tests.py└── views.py

{% load mytags %}

テンプレートフィルターは自分で定義できます。アプリケーションディレクトリの直下にtemplatetagsというディレクトリを掘って、その中に置きます。__init__.pyはPythonのお約束なのでとにかく作ってください。フィルターの実態を定義するモジュール名は決まりがありませんので、適当な名前を考えてください。今回は最悪な名前mytags.pyにしました。(改ページ)この例のようなファイル名にした場合は、各テンプレートで load mytags として、自作フィルターの一群をテンプレートで使えるように宣言します。変な名前にすると恥ずかしい思いをします。

Template Filterを作成する

http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/

from django import template

register = template.Library()

def caplast(value): return value and value[:-1] + value[-1].upper()capfirst.is_safe=Truecapfirst = stringfilter(caplast)register.filter(caplast)

blog/tempatetags/mytags.py

実際のコードはこんな感じになります。今回は追加の引数なしにしましたが、ひとつだけ追加で引数をとるようにもできます。valueにはパイプで渡されてきた値が入ってきます。django.template.Libraryのインスタンスをregisterという名前に割り当てて、フィルター用に作成した関数を、registerのfilterメソッドに渡すと、loadして使えるようになります。関数のプロパティis_safeはオートエスケープに絡むもので、HTMLとして危険な文字列をこのフィルター内で生成していない場合にはTrueをセットしておきます。詳しくはカスタムフィルター作成のドキュメントを参照してください。django.template.defaultfilters.pyにビルトインのフィルターが定義されていますので、そちらも参考にしてください。

Template Tagを作成する

http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/

from django import templateregister = template.Library()

class WithNode(template.Node): def __init__(self, var, name, nodelist): self.var = var self.name = name self.nodelist = nodelist def __repr__(self): return "<WithNode>" def render(self, context): val = self.var.resolve(context) context.push() context[self.name] = val output = self.nodelist.render(context) context.pop() return output

タグもフィルターと同じ場所に定義します。タグを作るには、django.template.Nodeを継承したクラスにrenderというメソッドを定義します。renderにはcontextが渡ってきますので、contextに値を格納することで、テンプレートから変数を使えます。このWithNodeの場合はwithのブロック内限定で変数を使えるようにしています。(改ページ)実際にテンプレートから使えるタグは、do_withという関数で、テンプレートから渡ってきたタグの文字列を元にWithNodeを組み立てて返しています。フィルターと同様、registerのtagメソッドに関数を登録します。より詳しくは、ドキュメントを参照してください。django.template.defaulttags.pyにビルトインのタグが定義されていますので、そちらも参考にしてみてください。

Template Tagを作成する

http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/

from django import templateregister = template.Library()

class WithNode(template.Node): def __init__(self, var, name, nodelist): self.var = var self.name = name self.nodelist = nodelist def __repr__(self): return "<WithNode>" def render(self, context): val = self.var.resolve(context) context.push() context[self.name] = val output = self.nodelist.render(context) context.pop() return output

def do_with(parser, token): bits = list(token.split_contents()) if len(bits) != 4 or bits[2] != "as": raise TemplateSyntaxError("...") var = parser.compile_filter(bits[1]) name = bits[3] nodelist = parser.parse(('endwith',)) parser.delete_first_token() return WithNode(var, name, nodelist)do_with = register.tag('with', do_with)

タグもフィルターと同じ場所に定義します。タグを作るには、django.template.Nodeを継承したクラスにrenderというメソッドを定義します。renderにはcontextが渡ってきますので、contextに値を格納することで、テンプレートから変数を使えます。このWithNodeの場合はwithのブロック内限定で変数を使えるようにしています。(改ページ)実際にテンプレートから使えるタグは、do_withという関数で、テンプレートから渡ってきたタグの文字列を元にWithNodeを組み立てて返しています。フィルターと同様、registerのtagメソッドに関数を登録します。より詳しくは、ドキュメントを参照してください。django.template.defaulttags.pyにビルトインのタグが定義されていますので、そちらも参考にしてみてください。

Generic Viewfrom django.conf.urls.defaults import *from django.views.generic.date_based import object_detailfrom everes_photo.models import Shot

urlpatterns = patterns('', url(r'^(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<object_id>\d+)/', object_detail, dict( queryset=Shot.objects.all(), date_field='published_from', month_format='%m'), name='photo_detail'),)

http://docs.djangoproject.com/en/1.2/ref/generic-views/GenericViewはWebでよく使われるパターンに関して、改めてView関数を書かずに済ませるための汎用的なView関数です。使い方は、通常のView関数と同様、URLconfに設定をします。この例の場合は、日付ベースでモデルの詳細を表示する汎用ビューを使っています。引数に汎用ビューがオブジェクトの抽出を行うベースとなるQuerysetとモデルのどの日付フィールドを使うかを渡しています。また、汎用ビューはそこら中で使われるので、かならず名前をつけることにしましょう。汎用ビューは独自のビュー関数の中で利用しても非常に便利です。

build-in Generic Viewsdjango.views.generic.simple.

direct_to_templateredirect_to

django.views.generic.list_detail.object_listobject_detail

django.views.generic.create_update.create_objectupdate_objectdelete_object

django.views.generic.date_based.archive_indexarchive_yeararchive_montharchive_weekarchive_dayarchive_todayobject_detail

http://docs.djangoproject.com/en/1.2/ref/generic-views/

表示だけでなく、モデルに対応した「追加」「変更」「削除」のビュー関数も用意されています。各々、引数やデフォルトのテンプレート名が決まっていますので、ドキュメントを参照してください。

Form/ModelForm入力検証と表示

さて、チュートリアルでform処理の節を見たとき、少し面倒だと感じましたか?

ModelForm

http://docs.djangoproject.com/en/1.2/topics/forms/modelforms/手っ取り早いモデルフォームの説明からしましょう。(改ページ)モデルフォームは、モデルの情報を元に入力フォームを生成する仕組みです。django.forms.ModelFormを継承したクラスの内部Metaクラスにどのモデルを元にするかの指定を行ないます。すると、モデルのフィールドの種類や入力制限に基づいて、入力フォームのクラスが出来ます。単にモデルフォームクラスをインスタンス化して、プリントすると…(改ページ)Tableタグの中身のHTMLが出力されます。

ModelForm>>> from django.forms import ModelForm>>> from blog.models import Entry

>>> class EntryModelForm(ModelForm):... class Meta:... model = Entry... >>> entry_form = EntryModelForm()

http://docs.djangoproject.com/en/1.2/topics/forms/modelforms/手っ取り早いモデルフォームの説明からしましょう。(改ページ)モデルフォームは、モデルの情報を元に入力フォームを生成する仕組みです。django.forms.ModelFormを継承したクラスの内部Metaクラスにどのモデルを元にするかの指定を行ないます。すると、モデルのフィールドの種類や入力制限に基づいて、入力フォームのクラスが出来ます。単にモデルフォームクラスをインスタンス化して、プリントすると…(改ページ)Tableタグの中身のHTMLが出力されます。

ModelForm>>> from django.forms import ModelForm>>> from blog.models import Entry

>>> class EntryModelForm(ModelForm):... class Meta:... model = Entry... >>> entry_form = EntryModelForm()

>>> print unicode(entry_form)u'<tr><th><label for="id_slug">\u30b9\u30e9\u30b0:</label></th><td><input id="id_slug" type="text" name="slug" maxlength="50" /><br />\u5185\u5bb9\u3092\u8aac\u660e\u3059\u308bURL\u306e\u4e00\u90e8\u306b\u4f7f\u308f\u308c\u307e\u3059</td></tr>\n<tr><th><label for="id_title">\u30bf\u30a4\u30c8\u30eb:</label></th><td><input id="id_title" type="text" name="title" maxlength="128" /></td></tr>…

http://docs.djangoproject.com/en/1.2/topics/forms/modelforms/手っ取り早いモデルフォームの説明からしましょう。(改ページ)モデルフォームは、モデルの情報を元に入力フォームを生成する仕組みです。django.forms.ModelFormを継承したクラスの内部Metaクラスにどのモデルを元にするかの指定を行ないます。すると、モデルのフィールドの種類や入力制限に基づいて、入力フォームのクラスが出来ます。単にモデルフォームクラスをインスタンス化して、プリントすると…(改ページ)Tableタグの中身のHTMLが出力されます。

ModelForm

http://docs.djangoproject.com/en/1.2/topics/forms/modelforms/モデルフォームのインスタンスは、フィールド単位での出力や、もっと細かい単位での出力もできます。これをテンプレートで使えば好きなレベルでformの出力ができます。

ModelForm>>> print entry_form['slug']<input id="id_slug" type="text" name="slug" maxlength="50" />>>> print entry_form['slug'].as_textarea()<textarea id="id_slug" rows="10" cols="40" name="slug"></textarea>

>>> print entry_form['slug'].nameslug>>> print entry_form['slug'].labelスラグ

http://docs.djangoproject.com/en/1.2/topics/forms/modelforms/モデルフォームのインスタンスは、フィールド単位での出力や、もっと細かい単位での出力もできます。これをテンプレートで使えば好きなレベルでformの出力ができます。

ModelForm>>> dummy_post = {'slug': 'testcode', 'title': 'testtitle', 'open_date': '2010-09-28 19:00:00'}>>> entry_form = EntryModelForm(dummy_post)>>> entry_form.is_valid()False>>> entry_form.errors{'author': [u'This field is required.']}

>>> dummy_post = {'slug': 'testcode', 'title': 'testtitle', 'open_date': '2010-09-28 19:00:00', 'author': '1'}>>> entry_form = EntryModelForm(dummy_post)>>> entry_form.is_valid()True

>>> entry_form.save()<Entry: testtitle>

http://docs.djangoproject.com/en/1.2/topics/forms/modelforms/モデルフォームが面白いのは、どのフォームに対応しているのかを知っているため、バリデーションチェックや、DBへの保存ができることです。データをバインドするには、モデルフォームのインスタンス化時に、辞書データを渡します。実際はrequest.POSTを渡すことになるでしょう。その後、バリデーションチェックで問題がなければ、saveメソッドを呼び出すことでデータベースに保存され、保存したモデルのインスタンスが返ります。ModelFormは更新にも使えます。

ModelFormclass EntryModelForm(ModelForm): open_date = DateField(label='Publication date') class Meta: model = Entry fields = ('slug', 'open_date') widgets = { 'title': Textarea(attrs={'cols': 30, 'rows': 1}), }

class EntryModelForm(ModelForm): class Meta: model = Entry exclude = ('open_date')

http://docs.djangoproject.com/en/1.2/topics/forms/modelforms/モデルフォームに利用するフィールドを制限したり、フィールドの表示ウィジェットを変更したり、フィールド自体を変更したりと、いろんなことができます。

Form

http://docs.djangoproject.com/en/1.2/topics/forms/Formです。モデルと対応しない入力を扱うときに使います。(改ページ)おおまかに言えば、やはりモデルと対応しないフォームです。

Formfrom django import forms

class ContactForm(forms.Form): subject = forms.CharField(max_length=100) message = forms.CharField() sender = forms.EmailField() cc_myself = forms.BooleanField(required=False)

>>> contact_form = ContactForm(request.POST)>>> contact_form.is_valid()True

http://docs.djangoproject.com/en/1.2/topics/forms/Formです。モデルと対応しない入力を扱うときに使います。(改ページ)おおまかに言えば、やはりモデルと対応しないフォームです。

FieldsBooleanFieldCharFieldChoiceFieldTypedChoiceFieldDateFieldDateTimeFieldDecimalFieldEmailFieldFileFieldFilePathFieldFloatFieldImageFieldIntegerField

IPAddressFieldMultipleChoiceFieldNullBooleanFieldRegexFieldSlugFieldTimeFieldURLFieldComboFieldMultiValuefieldSplitDateTimeFieldModelChoiceFieldModelMultipleChoiceField

http://docs.djangoproject.com/en/1.2/ref/forms/fields/標準のフィールドです。もちろん、このフィールドも自作できます。ドキュメントがあるので参照したらいいと思います。

WidgetTextInputPasswordInputHiddenInputMultipleHiddenInputFileInputDateInputDateTimeInputTimeInputTextarea

CheckboxInputSelectNullBooleanSelectSelectMultipleRadioSelectCheckboxSelectMultipleMultiWidgetSplitDateTimeWidgetSelectDateWidget

http://docs.djangoproject.com/en/1.2/ref/forms/widgets/ウィジェットです。アトリビュートの指定をすると表示時に出力できます。 ドキュメントがあるので参照したらいいと思います。

4048672096

477413760X

…、…、…、(改ページ)、Amazonでこの二つを入手したらいいと思います。

4048672096

477413760X

amazon.co.jpで検索!

…、…、…、(改ページ)、Amazonでこの二つを入手したらいいと思います。

No Question!

質問は受け付けないよ!、と思ったんですが、非常に悔しいことに時間の調整に失敗したため、質問を受け付ける時間があります。本当は一服しに行きたいので、やっぱり質問はなしでいいですか?質問がある人は懇親会で聞いてくれたら、一緒にコード読みます。

Recommended