albertyw/albertyw.com

View on GitHub
app/notes/20231228-0130.md

Summary

Maintainability
Test Coverage
Updating UUIDField on MariaDB to Django 5

django-5-mariadb-uuidfield

1703727004

Updating from Django 4 to Django 5 comes with an incompatible change to how
Django `UUIDField`s are stored in MariaDB databases - in Django 4, Django
would store `UUIDField`s as `char(32)` types but in Django 5, Django would
store `UUIDField`s as `uuid` types.  However, the [Django 5 upgrade notes are
incomplete](https://docs.djangoproject.com/en/5.0/releases/5.0/#migrating-uuidfield).  Following
the directions on a nontrivial Django project, it's still likely that
you'll get database errors like
`django.db.utils.OperationalError: (4078, "Cannot cast 'int' as 'uuid' in assignment of test_database.table_column.id")`
and errors from fitting 36-character UUID strings into 32-character database
fields.

In order to fix this problem, I found that it's better to convert `char(32)`
MariaDB fields into `uuid` fields first before migrating to Django 5.  That
way, there are fewer changes during the update.  In order to do so,

1.  While still on Django 4, replace `django.models.UUIDField` with a uuid
    field using a `uuid` database type with:

    ```python
    # app/models.py

    from django.db import models


    class RealUUIDField(models.UUIDField):
        def db_type(self, connection):
            return "uuid"


    class Model(models.Model):
        id = RealUUIDField(primary_key=True, default=uuid.uuid4, editable=False)
        # Formerly:
        # id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    ```

2.  Explicitly declare older migrations on the uuid field as a `char(36)`
    field.  Note that this needs to be `char(36)` because Django will attempt to
    store UUIDs with dashes (`'06cb5e67-467f-4675-91f3-ca466bcee805'` instead of
    `'06cb5e67467f467591f3ca466bcee805'`).  In migration files, replace `models.UUIDField`
    with:

    ```python
    # app/migrations/0001_migration.py
    class Char36UUIDField(models.UUIDField):
        def db_type(self, connection):
            return "char(36)"

    class Migration(migrations.Migration):
        operations = [
            migrations.CreateModel(
                name='Model',
                fields=[
                    ('id', Char36UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
                    # Formerly:
                    # ('id', Char36UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
                ],
            ),
        ]
    ```

3.  Run `./manage.py makemigrations` to generate a migration that will convert
    the database column from a `char(32)` to a `uuid` type.

4.  Install the `django==5.0` pip package.  Fix any other incompatibilities.

5.  Replace `RealUUIDField` back to `models.UUIDField`.