View All Posts. MiCHiLU.com powered by Django ;-)

[Django]: Django和訳ドキュメント sync to r5754

Django オンラインドキュメント和訳 更新しました。 Revision 5754 (2007/07/25). 「オブジェクトの保存時に何が起きるのか」「生データの保存」「カスタムのフィールド型」「unittest を書く」「regroup」についての記述が更新されています。

以下、 diff -r 5692:5754 の主な変更分です。

オブジェクトの保存時に何が起きるのか

Django は以下の段階を踏んでオブジェクトを保存します:

  1. ``pre_save`` シグナルの発行 シグナルの発行によって,何らかのオブ ジェクトを保存しようとしていることを通知します.リスナ (listener) を 用意して登録しておけ, pre_save シグナルが発行された時に実行でき ます (シグナルのドキュメントはまだありません).

  2. データの前処理 オブジェクトの各フィールドについて,保存時に自動 的に実行する必要があるデータ修飾処理がないか調べ,あれば実行します.

    ほとんどのフィールドは前処理を 伴いません .フィールドのデータは そのまま保存されます.前処理が行われるのは,特殊な挙動を示すフィー ルドだけです.例えば, auto_now=True に設定された DateField の場合,前処理の段階で,フィールドの内容が現在の日付になるようデータ を置き換えます (現時点では,「特殊な」挙動を示すフィールドのリストを 全て列挙したドキュメントはありません).

  3. データベース保存用のデータ準備処理 各フィールドについて,フィー ルドの現在の値を元にデータベースに保存できる型のデータを生成します.

    ほとんどのフィールドはデータ準備処理を 伴いません .整数や文字列は Python オブジェクトとして「いつでもデータベースへの書き込みに使える」 形式になっています.ただ,より複雑なデータ型の場合,なにがしかの修飾 が必要なことがあります.

    例えば, DateField は,データの保存に Python の datetime 型 を使います.データベースは datetime オブジェクトを保存しないので, データベースに保存するには,フィールドの値を ISO 準拠の日付文字列に 変換せねばなりません.

  4. データベースへの保存 前処理と準備処理を経たデータが SQL 文に組み 込まれ,データベースに挿入されます.

  5. ``post_save`` シグナルの発行 pre_save シグナルと同じく,オブ ジェクトが成功理に保存されたことを通知するために post_save シグ ナルが発行されます (シグナルのドキュメントはまだありません).

生データの保存

開発版の Django で新たに追加された機能です

前処理段階 (全節の項目 #2) は便利ですが,フィールドに保存されているデータを 勝手に修飾してしまいます.指定したデータをそのまま保存させたい場合には,こ の仕様が問題を引き起こすかもしれません.

例えば,テストを実行するために何らかのテスト条件を構築するなら,テスト条件 に再現性をもたせる必要があるでしょう.前処理が実行されると,テスト条件に使 うデータが修飾されて,テストの実行ごとに違ったテスト条件をもたらしてしまう かも知れません.

こうした問題を防ぐには,オブジェクトの保存時に前処理を抑制する必要がありま す.前処理を抑制するには, save() メソッドに raw=True 引数を指定し て, 生データを保存 します:

b4.save(raw=True)  # オブジェクトを保存しますが,前処理は行いません

生データの保存を行うと,保存時に実行されるたいていの前処理を行いません. ただし, (pre_save シグナルの発行,データの準備処理,データベースへの挿 入処理, post_save シグナルの発行といった) 前処理以外の処理は通常通り実 行されます.

「生データ保存」の使いどころ

ざっくり言えば,生データ保存はまず使う必要はないでしょう.フィールドの前 処理抑制は,再現性のあるテスト環境の構築のように,よほど変わった状況でな いかぎり使うべきでない極端な手段なのです.

get_object_or_404()

カスタムマネジャにカスタムメソッド追加して使いたい場合は, get_object_or_404() にクエリセットを渡します:

# カスタムマネジャの 'published' メソッドが返すクエリセットを使って
# 主キーの値が 5 のオブジェクトを検索する
e = get_object_or_404(Entry.objects.published(), pk=5)

カスタムのフィールド型

開発版の Django で新たに追加された機能です

Django の組み込みフィールド型は,データベースのカラムとして利用できる全ての データ型をサポートしているわけではなく, VARCHARINTEGER のよう な一般的な型しかサポートしていません.幾何多角形 (geographic polygon) 型や, PostgreSQL カスタム型 のようなユーザ定義型といった特異な型を扱うには, Field 型のサブクラスを定義する必要があります.

Experimental territory

この分野は, Django の中でも伝統的にドキュメント化されて来なかった部分で すが,これからは少しづつドキュメントを増やしていく予定です.というわけで, 当面は薄い内容ですがご容赦くださいね.

最先端の世界に住むのが好きで,不安定でドキュメント化されていない API を使 うリスクを気にしないのなら, django/db/models/fields/__init__.py の コアの Field クラスのコードを読むとよいでしょう -- ただし,中身がごっ そり変わっていても,そんなの聞いてないよなんて言わないでくださいね.

カスタムのフィールド型を定義するには, django.db.models.Field をサブク ラス化します.サブクラスで実装せねばならないメソッドの一部を以下に示します:

db_type()

DATABASE_ENGINE 設定に指定されているデータベースで,フィールドのデータ を保存するために使うカラム型を返します.

例えば, PostgreSQL 固有のカスタム型 mytype を作成したとしましょう. Django からこの型を使うには,以下のように Field をサブクラス化して, db_type() メソッドを実装します

from django.db import models

class MytypeField(models.Field):
    def db_type(self):
        return 'mytype'

MytypeField を定義したら,他のフィールド型と同じようにモデルで利用でき ます

class Person(models.Model):
    name = models.CharField(maxlength=80)
    gender = models.CharField(maxlength=1)
    something_else = MytypeField()

データベースに依存しないアプリケーションを構築したいなら,データベースごと のカラム型の違いに注意しておかねばなりません.例えば, PostgreSQL では日付 や時間に関するカラム型は timestamp 型ですが, MySQL では datetime 型です.この違いを db_type() メソッドの中で吸収するには,Djangoの settings モジュールを import しておいて,以下のように DATABASE_ENGINE 設定をチェックします

class MyDateField(models.Field):
    def db_type(self):
        from django.conf import settings
        if settings.DATABASE_ENGINE == 'mysql':
            return 'datetime'
        else:
            return 'timestamp'

db_type() メソッドは,Django がアプリケーションのテーブルを生成するため の CREATE TABLE` 文を構築する瞬間,すなわち最初にテーブルを生成する瞬 間しか呼び出されません.それ以外の場合に呼び出されることはないので,上記の DATABASE_ENGINE をチェックする例のような多少ややこしいコードを書いても さして問題ではありません.

カラムのデータ型にはパラメタをとるものがあります.例えば CHAR(25) は, 25 がカラムの最大長を表しています.こうした場合には, db_type() メ ソッド内でハードコードしておくよりも,モデル内でパラメタを指定して使えるよ うになっていた方が柔軟性を高められます.例えば,以下に示すような CharMaxlength25Field はあまり便利ではありません

# パラメタをハードコードしているおバカな例
class CharMaxlength25Field(models.Field):
    def db_type(self):
        return 'char(25)'

# モデル内での使い方
class MyModel(models.Model):
    # ...
    my_field = CharMaxlength25Field()

実行時,すなわちフィールドクラスをインスタンス化する時にパラメタを指定でき た方がよいでしょう.パラメタを指定できるようにするには,以下のように __init__() を実装します

# より柔軟性のある例
class BetterCharField(models.Field):
    def __init__(self, maxlength, *args, **kwargs):
        self.maxlength = maxlength
        super(BetterCharField, self).__init__(*args, **kwargs)

    def db_type(self):
        return 'char(%s)' % self.maxlength

# モデル内での使い方
class MyModel(models.Model):
    # ...
    my_field = BetterCharField(25)

フィールドクラスの __init__() を実装する場合は,必ず親クラスの Field.__init__() すなわち親クラスの __init__() メソッドを呼び出して ください.

unittest を書く

テストを実行する と,テストユーティリティはデフォルトの動作として, models.pytests.py に入っている全てのテストケース (つまり unittest.TestCase のサブクラス) を捜し出し,そこからテストスイートを自 動的に構築して,スイートを実行します.

開発バージョンの Django で登場した機能

ただし, models.pytests.pysuite() メソッドを定義してい る場合,モジュールのテストスイートの構築は suite() メソッドを使って行い ます.この仕様は,ユニットテストにおいて 推奨されているテストスイートの構築方法 に従っています.複雑なテストスイー トの構築方法についての詳細は Python のドキュメントを参照してください.

unittest の詳細は 標準ライブラリドキュメントの unittest の項 を参照 してください.

assertContains(response, text, count=None, status_code=200)
ページの取得が行われ,指定の HTTP 状態コードが生成され,レスポンス のコンテンツ内に text が入っているかどうか調べるアサーションです. count を指定すると, text は正確に count 回出現せねばなりま せん.

慣習的に,テストランナは run_tests と呼ぶことになっていますが,実際には どんな名前にしてもかまいません.ただし,テストランナは以下のように Django テストランナと同じ引数をとらねばなりません:

run_tests(module_list, verbosity=1, interactive=True, extra_tests=[])

module_list はテスト対象のモデルが入った Python モジュールのリストで, django.db.models.get_apps() が返すのと同じフォーマットです. テストランナはこのリストに挙げたモジュールからテストを捜し出さねばなりま せん.

verbosity には,コンソールに出力される通知情報やデバッグ情報の量を指 定します. 0 にすると何も出力しません. 1 は通常の出力, 2 は多めの出力です.

開発バージョンの Django で新たに追加された機能 interactiveTrue にすると,テストスイートの実行時にテストラン ナがユーザに指示を尋ねられるようにします.例えば,テストデータベースを削 除してよいか尋ねるといった使い方です. interactiveFalse の場 合,テストスイートは手動操作の介在なく実行できねばなりません.

extra_tests には,このテストランナに追加で実行させたい TestCase インスタンスを指定します. extra_tests を指定すると, module_list から発見したテストに加えて,指定したテストを実行します.

このメソッドは失敗したテストの数を返さねばなりません.

regroup

{% regroup %} は入力をソートしないので注意してください! 上の例では, リスト people はあらかじめ gender でソート済みという前提に立ってい ます. peoplegender の順に並んで いない 場合,再グループは何 も考えずに一つの性別のグループを複数つくってしまいます.例えば, people が以下のように ('Male' がリスト内でまとまっていない状態に) なっていたと しましょう

people = [
    {'first_name': 'Bill', 'last_name': 'Clinton', 'gender': 'Male'},
    {'first_name': 'Pat', 'last_name': 'Smith', 'gender': 'Unknown'},
    {'first_name': 'Margaret', 'last_name': 'Thatcher', 'gender': 'Female'},
    {'first_name': 'George', 'last_name': 'Bush', 'gender': 'Male'},
    {'first_name': 'Condoleezza', 'last_name': 'Rice', 'gender': 'Female'},
]

この people を入力に使うと,上の {% regroup %} のテンプレートコード 例は以下のような出力を生成します:

* Male:
    * Bill Clinton
* Unknown:
    * Pat Smith
* Female:
    * Margaret Thatcher
* Male:
    * George Bush
* Female:
    * Condoleezza Rice

こうした落し穴を解決したければ,ビューコード内であらかじめデータを表示した い順番に並べておくのが最も簡単でしょう.

データが辞書の列の場合には,もう一つの解決策として,テンプレートの中で dictsort フィルタを使ってデータを並べ変えられます

{% regroup people|dictsort:"gender" by gender as gender_list %}
Tue, 7 Aug 2007 00:10:09 +0900 source edit
Creative Commons License
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 2.1 Japan License.
View All Posts. MiCHiLU.com powered by Django ;-)