LoginSignup
40
41

More than 5 years have passed since last update.

django-uuidfieldを主キーにして少し困った話

Posted at

前提

djangoはモデルを作成すると自動的にidというシーケンシャルなフィールドを追加し,
この値でリレーションを貼ってくれる.

class Blog(model.Model):
    name = models.TextField(max_length=100)

class Entry(model.Model):
    blog = models.ForeignKey(Blog)
    body = models.TextField()

↑こんな感じで書いとくと↓こんな感じのテーブルができる

スクリーンショット 2015-07-28 13.47.07.png

シーケンシャルな値でリレーションを貼るとデータの移動の際に少し困ったことになることがある.
例えば別々のDBで運用していて,片方のデータをもう一方で取り込みたいときなど.
DBが別々の場合,当然シーケンシャル番号が重複する.
で,そのEntryからはBlogのidでリレーションを張っているわけだから,
下図のように取り込んだ時の新しいIDでリレーションを貼り直して上げる必要がある.

スクリーンショット 2015-07-28 13.48.02.png

これ,テーブル数が増えてリレーション関係が複雑になってくると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)
40
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
41