#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.