blog.notmyidea.org/content/code/2024-02-22-django-uuid-migrations.md
2024-02-26 22:32:20 +01:00

103 lines
3.2 KiB
Markdown

---
title: Changing the primary key of a model in Django
tags: 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:
```python
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:
```python
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()`.
```python
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.
```python
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 $$;
"""),
```