djangodjango-models

Implicit UUID auto fields for primary keys in Django


By default, Django adds integer primary keys as Autofields. This is annoying for many purposes, but especially makes debugging more difficult (code may accidently refer to the wrong "id", but instead of creating a runtime error, this might work in "some" instances because the IDs are accidently the same).

I want either unique integers across all tables or UUIDs for primary key. I do not want to specify either explicitly, since I want to only use this for debugging (and switch back to integers in production).

This is not a new proposal, but all answers seem to say "this is not performant" (blinding flash of the obvious) or advise to use explicit UUID fields (I don't want to do this because it means changing my model EVERYWHERE, then having to change it back later). Is there a way how to achieve this?

The proposals I've looked at (none of which answer by question) can be found here: Using a UUID as a primary key in Django models (generic relations impact) and Django: Unique ID's across tables with an open ticket for this feature here https://code.djangoproject.com/ticket/32577


Solution

  • A not very well known feature is that you can set the DEFAULT_AUTO_FIELD setting [Django-doc] to specify the primary key used by all models, except stated differently in the model itself, or the app config. Now one of the problems is that Django currently does not like the idea very much to plug in a UUIDField, but we can "force" this by specifying our own with:

    # app_name/fields.py
    
    import uuid
    
    from django.db.backends.base.operations import BaseDatabaseOperations
    from django.db.models import AutoField, UUIDField
    
    BaseDatabaseOperations.integer_field_ranges['UUIDField'] = (0, 0)
    
    
    class UUIDAutoField(UUIDField, AutoField):
        def __init__(self, *args, **kwargs):
            kwargs.setdefault('default', uuid.uuid4)
            kwargs.setdefault('editable', False)
            super().__init__(*args, **kwargs)

    and then plug the UUIDAutoField as default field:

    # settings.py
    
    # …
    
    DEFAULT_AUTO_FIELD = 'app_name.fields.UUIDAutoField'

    That being said, in my humble opinion, it was a bit of a misfeature to map an AutoField to an int and vice versa. One could make a separate type for each model like a UserPk class, that wraps an int and thus disables arithmetic on that field (since adding two UserPks together makes not much sense), and introduce type chekcs such that if you filter on User.objects.filter(pk=42), it errors if 42 is a simple int, or some primary key of another model. Haskell's esqueleto [hackage] follows this idea.