3.2 KiB
title | tags |
---|---|
Changing the primary key of a model in Django | django, orm, migrations |
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](https:// docs.djangoproject.com/en/5.0/topics/db/models/#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
uuid
field/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 $$;
"""),