DjangoのmodelでUUIDでuniqueなフィールドを追加した手順

:::note warn
素人の備忘録です。
:::

## 参考にしたページ
公式ドキュメント「Migrations that add unique fields」

https://docs.djangoproject.com/en/4.1/howto/writing-migrations/#migrations-that-add-unique-fields

https://www.denzow.me/entry/2017/12/23/150501

## 何も考えずにしたら重複するって怒られた。

```python
uuid_key = models.UUIDField(
    default=uuid.uuid4,
    unique=True,
    editable=False
)
```
既存のモデルにこれを追加して `python manage.py makemigrations`して`python manage.py migrate`したら値が重複するから駄目と怒られました。

DBみたら追加したフィールドに同じUUIDの値をいれようと処理してたみたい。

## こうしたら行けた
公式ドキュメントを参考にしつつ、denzowさんのサイトのように1ファイル記述してみた。

概要としては、makemigrationsして出来たmigrationファイルを編集して以下が順に処理されるようにする：
1. uniqueを外してnull=TrueでAddFieldする。
1. nullで追加された既存データに対して、それぞれのデータにuuidを生成しつつupdateして埋める。
1. null=Trueをunique=TrueにAlterFieldする。

### 準備
- models.pyに上記のuuid_keyの記述を追加する。
- とりあえず`python manage.py makemigrations`する。

### 出来上がったmigrationファイルを編集する。

最初の状況
```python:0011_add_uuid_field.py
# Generated by Django 4.1.7 on 2023-04-13 01:37

from django.db import migrations, models
import uuid

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0010_hitotsumaeno_file'),
    ]

    operations = [
        migrations.AddField(
            model_name='contentsinfo',
            name='uuid_key',
            field=models.UUIDField(
                default=uuid.uuid4,
                editable=False,
                null=True), # unique=Trueをnull=Trueに変更。
        ),
    ]

```

まずは field 指定部分のuniqueをnullに変える。
```diff_python
+ field=models.UUIDField(default=uuid.uuid4, editable=False, null=True),
- field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
```
公式サイトを参考にUUID生成する記述を追加。
```diff_python:0011_add_uuid_field.py
# Generated by Django 4.1.7 on 2023-04-13 01:37

from django.db import migrations, models
import uuid

+ def gen_uuid(apps, schema_editor):
+     MyModel = apps.get_model('myapp', 'ContentsInfo') #ここはmodels.pyのクラス名。
+     for row in MyModel.objects.all():
+         row.uuid_key = uuid.uuid4() #ここでrow.<field_name>にするのを忘れずに。
+         row.save(update_fields=['uuid_key']) #ここでも<field_name>を指定。

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0010_hitotsumaeno_file'),
    ]

    operations = [
        migrations.AddField(
            model_name='contentsinfo',
            name='uuid_key',
            field=models.UUIDField(default=uuid.uuid4, editable=False, null=True),
        ),
+        #nullなフィールドに都度UUID生成してupdate処理。
+        migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
    ]

```

最後に`null=True`だった箇所を`unique=True`に変更してAlterFieldする指示を書く。

```diff_python:0011_add_uuid_field.py
# Generated by Django 4.1.7 on 2023-04-13 01:37

from django.db import migrations, models
import uuid

 def gen_uuid(apps, schema_editor):
     MyModel = apps.get_model('myapp', 'ContentsInfo') #ここはmodels.pyのクラス名。
     for row in MyModel.objects.all():
         row.uuid_key = uuid.uuid4() #ここでrow.<field_name>にするのを忘れずに。
         row.save(update_fields=['uuid_key']) #ここでも<field_name>を指定。

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0010_hitotsumaeno_file'),
    ]

    operations = [
        migrations.AddField(
            model_name='contentsinfo',
            name='uuid_key',
            field=models.UUIDField(
                default=uuid.uuid4,
                editable=False,
                null=True), # unique=Trueをnull=Trueに変更。
        ),
        #nullなフィールドに都度UUID生成してupdate処理。
        migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
+        # 最初のAddFieldからコピペしてコメント部分を修正する。
+        migrations.AlterField( # AddFieldをAlterFieldにする。
+            model_name='contentsinfo',
+            name='uuid_key',
+            field=models.UUIDField(
+                default=uuid.uuid4,
+                editable=False,
+                unique=True), # null=Trueをunique=Trueに変更。
+        ),
    ]

```
できあがったら保存。

### できたマイグレーションファイル

```python:0011_add_uuid_field.py
# Generated by Django 4.1.7 on 2023-04-13 01:37

from django.db import migrations, models
import uuid

 def gen_uuid(apps, schema_editor):
     MyModel = apps.get_model('myapp', 'ContentsInfo') #ここはmodels.pyのクラス名。
     for row in MyModel.objects.all():
         row.uuid_key = uuid.uuid4() #ここでrow.<field_name>にするのを忘れずに。
         row.save(update_fields=['uuid_key']) #ここでも<field_name>を指定。

class Migration(migrations.Migration):

    dependencies = [
        ('myapp', '0010_hitotsumaeno_file'),
    ]

    operations = [
        migrations.AddField(
            model_name='contentsinfo',
            name='uuid_key',
            field=models.UUIDField(
                default=uuid.uuid4,
                editable=False,
                null=True), # unique=Trueをnull=Trueに変更。
        ),
        #nullなフィールドに都度UUID生成してupdate処理。
        migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
        # 最初のAddFieldからコピペしてコメント部分を修正する。
        migrations.AlterField( # AddFieldをAlterFieldにする。
            model_name='contentsinfo',
            name='uuid_key',
            field=models.UUIDField(
                default=uuid.uuid4,
                editable=False,
                unique=True), # null=Trueをunique=Trueに変更。
        ),
    ]

```

### 実行してみる。
`python manage.py migrate`したらうまくいきました。やったー。