#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 django-updates+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/067.2985f4416870e07d62debdbce24b19bb%40djangoproject.com.

Reply via email to