LoginSignup
2
4

More than 3 years have passed since last update.

Django UUIDFieldのdefaultはuuid.uuid4()ではなく、uuid.uuid4と表記する

Last updated at Posted at 2020-10-24

結論😊

タイトル通りです。
Django UUIDFieldのdefaultはuuid.uuid4()ではなく、uuid.uuid4と表記しましょう。
ハマります。

困りごと😢

Djangoで画面側Formからのデータ登録です。

一回目は、登録がうまくいく。
二回目以降は、登録時にエラーが出てしまう。

runserverでサーバを動かし直すと、登録できた。
今考えるとこれが大ヒントだった。

エラー内容⚡

エラーは以下です。

created_atがNULL制約に違反しているYo!」
...まぁ、エラーの内容は全く関係なかったんですが。

django.db.utils.IntegrityError: NOT NULL constraint failed: iine_room.created_at

テーブル定義📅

created_atがNULL制約に違反しているYo!」
テーブル定義は以下。

created_at = models.DateTimeField(auto_now_add=True)にしているから、自動で更新されるはずなんだけど。。

models.py
class Room(models.Model):
    id = models.UUIDField('部屋ID', primary_key=True, default=uuid.uuid4())
    name = models.CharField('部屋名', max_length=100)

    start_at = models.DateTimeField('開始時刻', default=None, blank=True, null=True)
    end_at = models.DateTimeField('終了時刻', default=None, blank=True, null=True)

    created_at = models.DateTimeField(auto_now_add=True)  # 指定している。
    updated_at = models.DateTimeField(auto_now=True)

デバッグしていく👁‍🗨

デバッグして中身を追っていくYo

views.py
    def post(self, request, *args, **kwargs):
        self.object = None
        form = self.get_form()
        if form.is_valid():
            self.form_valid(form)

self.form_valid(form)を追うよ

edit.py
    def form_valid(self, form):
        """If the form is valid, save the associated model."""
        self.object = form.save()
        return super().form_valid(form)

self.object = form.save()を追うよ

forms\models.py
    def save(self, commit=True):
        """
        Save this form's self.instance object if commit=True. Otherwise, add
        a save_m2m() method to the form which can be called after the instance
        is saved manually at a later time. Return the model instance.
        """
        if self.errors:
            raise ValueError(
                "The %s could not be %s because the data didn't validate." % (
                    self.instance._meta.object_name,
                    'created' if self.instance._state.adding else 'changed',
                )
            )
        if commit:
            # If committing, save the instance and the m2m data immediately.
            self.instance.save()
            self._save_m2m()
        else:
            # If not committing, add a method to the form to allow deferred
            # saving of m2m data.
            self.save_m2m = self._save_m2m
        return self.instance
(省略)

self.instance.save()を追うよ

models\base.py
 def _save_table(self, raw=False, cls=None, force_insert=False,
                    force_update=False, using=None, update_fields=None):
        """
        Do the heavy-lifting involved in saving. Update or insert the data
        for a single table.
        """
        meta = cls._meta
        non_pks = [f for f in meta.local_concrete_fields if not f.primary_key]

        if update_fields:
            non_pks = [f for f in non_pks
                       if f.name in update_fields or f.attname in update_fields]

        pk_val = self._get_pk_val(meta)
        if pk_val is None:
            pk_val = meta.pk.get_pk_value_on_save(self)
            setattr(self, meta.pk.attname, pk_val)
        pk_set = pk_val is not None

(省略)

pk_val = self._get_pk_val(meta)

pk_valは付与されているよな?(確認)

pk_val = 18d19958-3e11-4444-ac51-3707ba366440

付与されているね。

あれ、もしかして。
主キーが既存のものと、かぶってたりしてない?
調査してみる。

>>> Room.objects.filter(pk='18d19958-3e11-4444-ac51-3707ba366440')
<QuerySet [<Room: 10/24 11:26:09: <te> 18d19958-3e11-4444-ac51-3707ba366440>]>

すでにおるやんけ。
見事に被ってる。
UUIDが被るという奇跡を起こしてしまったか!?

ちなみにUUIDが衝突する確率は?

たとえば、UUID を 3×10173×1017 回くらいつくると、1 % (p=0.01p=0.01) の確率で衝突するってことですね。
https://qiita.com/kiririmode/items/9ddf7f2aec6e8ba4dc7f

いや、ありえないやん。

解決へ✅

UUIDが起動時にしか生成されていない?
もう一回モデルを見てみる。

models.py
class Room(models.Model):
    id = models.UUIDField('部屋ID', primary_key=True, default=uuid.uuid4())
    name = models.CharField('部屋名', max_length=100)

    start_at = models.DateTimeField('開始時刻', default=None, blank=True, null=True)
    end_at = models.DateTimeField('終了時刻', default=None, blank=True, null=True)

    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

default=uuid.uuid4()
しっかりと表記してる。(と思い込んでいる)
一旦django default=uuid.uuid4()でぐぐってみる。

...

default=uuid.uuid4と書いてある。
コード内の表記はdefault=uuid.uuid4()
あ、カッコが不要やん。
起動時に実行されてdefault='18d19958-3e11-4444-ac51-3707ba366440'のような形になっていたようだ。

結論😊

Django UUIDFieldのdefaultはuuid.uuid4()ではなく、uuid.uuid4と表記しましょう。

関数のカッコには気をつけましょう。
関数によって毎回値が変わるものにはカッコはつけない!

エラー内容だけを頼りにするとその内容が正しくなかったときにハマります。
エラー内容は鵜呑みにしない!

2
4
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
2
4