祢占堂

はい

Django + MySQL 5.5 でモデルオブジェクトの datetime の値が生成時と生成後で変わる

class Diavolo(models.Model):
    ctime = models.DateTimeField(auto_now_add=True)

っていう生成時に現在日時を自動的に保存してくれるやつを作るじゃないですか。

>>> d1 = Diavolo.objects.create()
>>> d2 = Diavolo.objects.get(pk=d1.pk)
>>> d1.ctime == d2.ctime
False

はい。

>>> d1.ctime
datetime.datetime(2013, 10, 25, 13, 32, 25, 244575)
>>> d2.ctime
datetime.datetime(2013, 10, 25, 13, 32, 25)

つらい。

MySQL を使っている場合、Django の ORM は models.DateTimeField を MySQL の TIMESTAMP 型として扱うけど、MySQL 5.5 はマイクロ秒をサポートしてない。

でも Model.objects.create() の戻り値のオブジェクトは INSERT するために生成されたやつなので、この時点ではマイクロ秒がそのまま残ってる。

対して、改めてデータベースから取得したやつは truncate されてる。なので

class DiavoloMapper(ModelMapper):
    class Meta:
        model = Diavolo

def get_diavolo_as_dict(id):
    obj = Diavolo.objects.get(pk=id)
    return DiavoloMapper(obj).as_dict()

みんな大好き bpmappers を使ったこんな Mapper があったとして、こういうテスト書くと落ちる。

from testfixtures import compare

class TestGetDiavoloAsDict(TestCase):
    def _getTarget(self):
        from mappers import get_diavolo_as_dict
        return get_diavolo_as_dict

    def _callFUT(self, id):
        func = self._getTarget()
        return func(id)

    def test_one(self):
        obj = Diavolo.objects.create(pk=1)
        dct = self._callFUT(id=1)

        compare(dct, dict(id=1, ctime=obj.ctime))

なので obj じゃなくて改めてモデルオブジェクトを取得してくるか、マイクロ秒を切り取るかしないといけない。まあ ctime みたいな auto_now_add されるやつはいっそテストしなくてもいいけど。

ちなみにローカルで sqlite 使ってたりするとこのテスト通る。こわいですね。