I had to change the primary key of a django model, and I wanted to create a migration for this.
The previous model was using django automatic primary key fields
I firstly changed the model to include the new uuid field, and added the id
field (the old primary key), like this:
uuid = models.UUIDField(
unique=True, primary_key=True, default=uuid.uuid4, editable=False
)
id = models.IntegerField(null=True, blank=True)
Then I created the migration, it:
- Adds a new
uuidfield/column in the database - Iterate over the existing items in the table, and generates an uuid for them
- Change the old primary key to a different type
- Drop the old index
- Mark the new uuid as a primary key.
To generate the migrations I did django-admin makemigrations, and iterated on
it. Here is the migration I ended up with:
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("umap", "0017_migrate_to_openstreetmap_oauth2"),
]
operations = [
# Add the new uuid field
migrations.AddField(
model_name="datalayer",
name="uuid",
field=models.UUIDField(
default=uuid.uuid4, editable=False, null=True, serialize=False
),
),
# Generate UUIDs for existing records
migrations.RunSQL("UPDATE umap_datalayer SET uuid = gen_random_uuid()"),
# Remove the primary key constraint
migrations.RunSQL("ALTER TABLE umap_datalayer DROP CONSTRAINT umap_datalayer_pk"),
# Drop the "id" primary key…
migrations.AlterField(
"datalayer", name="id", field=models.IntegerField(null=True, blank=True)
),
# … to put it back on the "uuid"
migrations.AlterField(
model_name="datalayer",
name="uuid",
field=models.UUIDField(
default=uuid.uuid4,
editable=False,
unique=True,
primary_key=True,
serialize=False,
),
),
]
Generating UUIDs in pure python
The uuid generation can also be done with pure python, like this. It works with all databases, but might be slower. Use it with migrations.RunPython().
def gen_uuid(apps, schema_editor):
DataLayer = apps.get_model("umap", "DataLayer")
for row in DataLayer.objects.all():
row.uuid = uuid.uuid4()
row.save(update_fields=["uuid"])
Getting the constraint name
One of the things that took me some time is to have a way to get the constraint name before removing it. I wanted to do this with the Django ORM, but I didn't find how. So here is how in plain SQL. This only works with PostgreSQL, though.
migrations.RunSQL("""
DO $$
BEGIN
EXECUTE 'ALTER TABLE umap_datalayer DROP CONSTRAINT ' || (
SELECT indexname
FROM pg_indexes
WHERE tablename = 'umap_datalayer' AND indexname LIKE '%pkey'
);
END $$;
"""),