#32577: Add support for `UUIDAutoField` `DEFAULT_AUTO_FIELD`
-------------------------------------+-------------------------------------
Reporter: Tomasz Wójcik | Owner: nobody
Type: New feature | Status: new
Component: Database layer | Version: 3.2
(models, ORM) |
Severity: Normal | Resolution:
Keywords: | Triage Stage: Accepted
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Nick Pope):
I've been involved in some of the changes and discussions related to
`AutoField` improvements and `DEFAULT_AUTO_FIELD` and wanted to clarify a
few things, as well as what needs to be done to achieve the creation of
`UUIDAutoField`.
----
Some points about `DEFAULT_AUTO_FIELD` which was added in
b5e12d490af3debca8c55ab3c1698189fdedbbdb:
- The current implementation restricts to values that are a subclass of
`AutoField`. (See
[https://github.com/django/django/commit/b5e12d490af3debca8c55ab3c1698189fdedbbdb
#diff-11ee95e0230bcff70a542128891516b8022177e0af58f00257aee9e8ef83a933R245
here].)
- It was named `DEFAULT_AUTO_FIELD` to indicate that it referred to the
''automatically generated'' `id` field.
- This was instead of `DEFAULT_AUTOFIELD` which would imply that it
could only ever be related to `AutoField`.
- Another suggestion had been `DEFAULT_MODEL_PK`, but we weren't ready
to allow other fields without `AutoField`-like semantics.
- We could lift the restriction to allow other non-`AutoField` types, but
the concept of an ''automatically generated'' `id` field needs to be
decoupled from `AutoField` a bit.
----
Some history and thoughts about `AutoField`:
- Much of the logic related to the ''automatically generated'' `id` field
is hardwired to look for `AutoField` subclasses. This needs to be fixed.
- `AutoField` has essentially always been like an `IntegerField`, but
historically copy-pasted some of that rather than inherit from
`IntegerField` directly.
- `BigAutoField` inherited from `AutoField`, but overrode with parts of
`BigIntegerField`, but it didn't originally inherit from
`BigIntegerField`.
- As a result, these `*AutoFields`s didn't support the system checks or
validators of their `*IntegerField` equivalents.
- I addressed some of this in 21e559495b8255bba1e8a4429cd083246ab90457 for
#29979:
- Moved the logic about automatically generated fields into
`AutoFieldMixin`.
- Fixed the `*AutoField` classes to inherit from `AutoFieldMixin` and
their appropriate `*IntegerField` type.
- Added `AutoFieldMeta` to maintain backward compatibility for
`isinstance(..., AutoField)` checks and provide a hook point for
deprecation of it in the future.
'''One more thing to mention about an ''automatically generated'' `id`
field is that the value is generated by the database and returned - it
must not be generated in Python.'''
----
What needs to be done to get to `UUIDAutoField`? Well, here is what I
think the path is...
- Add a `db_generated`/`is_auto` (or another better name) flag to
`AutoFieldMixin`. (See
[https://code.djangoproject.com/ticket/24042#comment:5 my comment]. I'm
not convinced #24042 should have been closed.)
- Replace `isinstance(..., AutoField)` checks with the new flag. (I seem
to recall that `isinstance(..., AutoFieldMixin)` wasn't really favoured.)
- This will also open up `DEFAULT_AUTO_FIELD` to a wider range of non-
integer automatically generated field types.
- Deprecate `isinstance` (and `issubclass`?) checks on `AutoField` within
`AutoFieldMeta`. (The metaclass can be scrapped when the deprecation
period ends.)
- Implement support for `.db_default` so that we can specify a function
for generating a UUID on the database rather than in Python, e.g.
[https://docs.djangoproject.com/en/3.2/ref/contrib/postgres/functions/#randomuuid
RandomUUID].
- Work is ongoing in this [https://github.com/django/django/pull/13709
PR] to address #470 which will implement this.
- It is also possible that this
[https://github.com/django/django/pull/11783 PR] to address #30032 is an
acceptable alternative path.
- Make `AutoFieldMixin` part of the public API and document it so that
others can use it for their own non-integer, non-UUID implementations.
- Finally, implement `UUIDAutoField` itself. This is a start, there will
be more pieces to the puzzle:
{{{#!python
class UUIDAutoField(AutoFieldMixin, UUIDField):
def __init__(self, *args, **kwargs):
kwargs['db_default'] = RandomUUID()
super().__init__(*args, **kwargs)
def deconstruct(self):
name, path, args, kwargs = super().deconstruct()
del kwargs['db_default']
return name, path, args, kwargs
def get_internal_type(self):
return 'UUIDAutoField'
def rel_db_type(self, connection):
return UUIDField().db_type(connection=connection)
}}}
It is likely that `AutoFieldMixin.get_db_prep_value()` would need
attention because of `connection.ops.validate_autopk_value()`.
Note that this field would also be limited to databases that have a
function to generate a UUID.
As Mariusz said, implementing a non-integer auto field such as
`UUIDAutoField` would close #17337.
----
Here are some responses to some of the other things mentioned in the
comments above.
> First, the default auto field BigAutoField is not subclassing AutoField
so it's not consistent with the error message.
This is a little confusing as it is done a bit magically for now - see the
bit about `AutoFieldMeta` above.
There was, however, a bug for subclasses of `SmallAutoField` and
`BigAutoField` which was fixed by
45a58c31e64dbfdecab1178b1d00a3803a90ea2d.
> While I'm not sure it would make sense in an AutoField, string primary
keys are possible. For example, you could use a language code (en, fr, de,
etc) as a primary key.
Yes, you can create a textual primary key, but you have to override `id`
for every model, e.g.
{{{#!python
id = models.CharField(max_length=64, primary_key=True)
}}}
It is unlikely that it'd make sense as an "`AutoField`" unless you can
generate the value on the database-side.
> Could I argue that IDs based on human-readable strings, like coolname,
are a valid use case?
>
> It's easier to reference '"soft-cuddly-shrew-of-expertise" than
"a1e1b1c5-5d5e-4eef-9ee9-e74e87ce71d6" or "2467584".
>
> Would this be seen as bad practice and we should use a slugfield that's
not the primary key for something like this instead?
Again, unless this were generated by the database, you'd probably be
better off doing something like:
{{{#!python
def generate_coolname():
... # Implementation here.
class MyModel(models.Model):
id = models.CharField(default=generate_coolname, max_length=64,
primary_key=True)
}}}
Guaranteeing uniqueness is always a fun challenge...
> To my understanding there's no significant performance hit from using
UUID as a primary key. It is 4x larger than int32 (128bit). So also index
is bigger, cache will store less data etc.
I dredged up some articles on this topic I recalled seeing a while back:
- https://www.2ndquadrant.com/en/blog/sequential-uuid-generators/
- https://www.2ndquadrant.com/en/blog/sequential-uuid-generators-ssd/
> And as I said, I'm happy to open a PR as I want to use it with UUIDs in
my next project.
You can use UUID primary keys today. You just have to define a custom
primary key so that the auto field isn't generated:
{{{#!python
class MyModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4,
editable=False)
}}}
Granted, this is less convenient, but it is possible and
[https://docs.djangoproject.com/en/3.2/ref/models/fields/#uuidfield
documented].
--
Ticket URL: <https://code.djangoproject.com/ticket/32577#comment:11>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/067.2985f4416870e07d62debdbce24b19bb%40djangoproject.com.