前提
djangoはモデルを作成すると自動的にidというシーケンシャルなフィールドを追加し,
この値でリレーションを貼ってくれる.
class Blog(model.Model):
name = models.TextField(max_length=100)
class Entry(model.Model):
blog = models.ForeignKey(Blog)
body = models.TextField()
↑こんな感じで書いとくと↓こんな感じのテーブルができる
シーケンシャルな値でリレーションを貼るとデータの移動の際に少し困ったことになることがある.
例えば別々のDBで運用していて,片方のデータをもう一方で取り込みたいときなど.
DBが別々の場合,当然シーケンシャル番号が重複する.
で,そのEntryからはBlogのidでリレーションを張っているわけだから,
下図のように取り込んだ時の新しいIDでリレーションを貼り直して上げる必要がある.
これ,テーブル数が増えてリレーション関係が複雑になってくるとinsertの順番も気をつけないといけなくなって大変です.
uuidフィールド
デフォルトのシーケンシャルなpkじゃなくuuidを使えばこの問題が解決します.
uuidは簡単に(確率的に)絶対重複しないランダムな文字列です.
詳しく知りたい人はwikipediaさんでも見てください.
uuidの導入はdjango-uuidfieldパッケージをpipなどでインストールした後で,
from uuidfield import UUIDField
class Blog(models.Model):
uuid = UUIDField(auto=True, primary_key=True, editable=False)
こんな感じに書いとくとidの代わりにuuidフィールドが作成され,
uuidでリレーションを貼ってくれるようになります.
毎回主キーを書き換えるのは大変なのでベースモデルクラス作って継承して作るといいかもです.
from uuidfield import UUIDField
class MyModel(models.Model):
class Meta:
abstract = True
uuid = UUIDField(auto=True, primary_key=True, editable=False)
class Blog(MyModel):
...
class Entry(MyModel):
...
問題と原因
色々書きましたが,ここまでが前提です.
django-uuidfieldは保存時に自動でuuidを生成して突っ込んでくれるのですが,
ModelFormでinstanceを渡した際に初期値として前回リレーションを貼ったレコードが選択されません.
uuidfieldはデータベースに突っ込む時に(postgres以外なら)char型として突っ込まれ,
取り出す時にuuid.UUID型になります.
UUID型の値,str型にすると550e8400-e29b-41d4-a716-446655440000
みたいに
間にハイフンが入っちゃいます.
models.ForeignKeyで指定されているフィールドはModelFormにするとModelChoiceFieldになります.
ここで使われるselectリストの値は550e8400e29b41d4a716446655440000
のように
ハイフンなしの値になります.
instanceの値はハイフンあり,セレクトの選択肢はハイフンなし,ということで,
値が一致せず,結果としてリレーションが外れてしまった,というのが原因でした.
解決方法
値の比較をする前にprepare_valueをオーバーライドしてハイフンなしの値を返してあげるようにしましょう.
formに値を渡す際に,fieldの値はprepare_valueメソッドて適切な形に変換されます.
この時にUUID型ならハイフンなしの値を返せばいいわけです.
import uuid
from django.db import models
from uuidfield import UUIDField
class MyUUIDField(UUIDField):
def prepare_value(self, value):
if isinstance(value, uuid.UUID):
return value.hex
return value
class MyModel(models.Model):
uuid = MyUUIDField(auto=True, primary_key=True, editable=False)
...
もっと賢い解決方法
djangoでは1.8からUUIDFieldが標準で使えるようです.
https://docs.djangoproject.com/en/1.8/ref/models/fields/#django.db.models.UUIDField
リンク先の例に書いてくれてるように,default値をセットして上げてprimary_keyをTrueにすればそれで終わりでした...
import uuid
from django.db import models
class MyUUIDModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)