結論😊
タイトル通りです。
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)
にしているから、自動で更新されるはずなんだけど。。
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
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)
を追うよ
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()
を追うよ
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()
を追うよ
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が起動時にしか生成されていない?
もう一回モデルを見てみる。
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
と表記しましょう。
関数のカッコには気をつけましょう。
関数によって毎回値が変わるものにはカッコはつけない!
エラー内容だけを頼りにするとその内容が正しくなかったときにハマります。
エラー内容は鵜呑みにしない!