-
-
Notifications
You must be signed in to change notification settings - Fork 32.7k
Fixed #19463 -- Add UUIDField #2923
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
import uuid | ||
|
||
from django.core.exceptions import ValidationError | ||
from django import forms |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd alphabetize this before 'django.core'
LGTM. |
I don't feel that good about adding UUID field for PostgreSQL only - UUID field isn't at all postgresql specific feature. OTOH I do understand why you want to implement it as a PostgreSQL only feature. Also, UUID field is almost always used as a primary key, so it should be tested as a primary key. The field should get an automatic value when saving a new object into the database (the value can be generated in Python). I recall a random discussion some time ago on IRC about problems with custom UUID field and admin - if we add UUID field, it should work automatically in admin, too. Unfortunately I don't recall details about this discussion, it was likely about using UUID as a primary key in admin. Maybe we need some simple tests with UUID as primary key in admin? |
The simplest solution I can think of for getting automatically a new primary key UUID value when saving a new object is addition of a new method generate_new_pk_value(self, connection) to Field. Then, if it returns something not None, the return value is used as primary key value for the model when inserting a new model. Addition of the generated UUID to the model can be done in Model._save_table(), somewhere around "if not pk_set: fields = [f for f in fields ...]. |
I wouldn't put that in contrib, too. When you look at the code you would see, that its not "that" hard to add to other databases. We would only changed the field based on the connection. Like postgresql uses the db_type uuid, while sqlite3 uses a char field, while mysql uses uuid, too. wouldn't be too hard since the uuid getting created in python and not in the database. |
from django.utils.translation import ugettext_lazy as _ | ||
|
||
|
||
class UUIDField(six.with_metaclass(SubfieldBase, Field)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest an auto
keyword to the __init__
for this field type.
Actually, the more I think about it, perhaps it should be even more aggressive:
def get_default(self):
default = super(UUIDField, self).get_default()
if not default and not self.null:
default = uuid.uuid4()
return default
That is, unless we are allowing NULL, always create a value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm. It occurs to me that if primary_key=True
, and we do this, it would look like the object already exists in the database before it actually is saved.
Although, since postgres cannot create UUIDs, that's going to present a problem anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Postges can create uuids, when installing the extension.
Am 19.07.2014 15:20 schrieb "Matthew Schinckel" notifications@github.com:
In django/contrib/postgres/fields/uuid_field.py:
@@ -0,0 +1,43 @@
+import uuid
+
+from django.contrib.postgres import forms
+from django.core.exceptions import ValidationError
+from django.db.models import Field, SubfieldBase
+from django.utils import six
+from django.utils.translation import ugettext_lazy as _
+
+
+class UUIDField(six.with_metaclass(SubfieldBase, Field)):Hmm. It occurs to me that if primary_key=True, and we do this, it would
look like the object already exists in the database before it actually is
saved.Although, since postgres cannot create UUIDs, that's going to present a
problem anyway.—
Reply to this email directly or view it on GitHub
https://github.com/django/django/pull/2923/files#r15143651.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no justification to be this aggressive in the field definition. Making an auto-generating uuid is as simple as UUIDField(default=uuid.uuid4)
. This should probably be a documented pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I was wound up on the fact that my similar field was not setting the value correctly on a default, but I was doing something stupid. The default=
is a better pattern (and is extensible to dates, etc).
I've added some tests and notes in the docs about As for the admin, I found a number of closed (fixed) tickets relating to non-integer primary keys. If we find any issues with this in the admin I'll happily resolve them. |
@akaariai @timgraham further review would be appreciated. |
super(UUIDField, self).__init__(**kwargs) | ||
|
||
def get_internal_type(self): | ||
return "CharField" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marc, did you try to store by default in a binary column? AFAIK PostgreSQL is using a binary field internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a few issues with using BinaryField
:
- MySQL-Python for python 3 has no support
- According to the docs they can't be filtered on, which would be a fairly significant flaw
- They data format stored would be different to that likely used in string representations (i.e. in URLs, json etc) which would be the hex format. This means a user (or Django) would have to convert that string into a
uuid.UUID
object and then get the binary representation. I guess we might be doing this anyway. - Aside from space in the database, I don't see any particular benefit if doing this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TBH, I haven't tried it out, it may prove to be a good idea, but I'd want some significant motivation for it.
This looks really good. The only things I scared about are Forms with UUIDs, thats somewhat unnormal to enter a UUID as a user. Also the explicit need for default= is somewhat "strange". And it's too bad that this won't go into 1.7 |
def get_internal_type(self): | ||
return "CharField" | ||
|
||
def db_type(self, connection): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this is in core, should it be using internal types instead?
Updated fixing docs issues. |
the build is failing hard due to git errors, not sure why only some are failing.. |
description = 'Universally unique identifier' | ||
|
||
def __init__(self, **kwargs): | ||
kwargs['max_length'] = 36 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason that I'm missing (probably) that the field's length is 36 (and each DB's backend definition is 36, with the exception of postgres, which has the native datatype)?
Removing the dashes (if a string) or calling .hex
turns the value into 32 chars, which both uuid.UUID()
and postgres' datatype support as a input format, and both use the dash-inclusive output format.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh that's what i've missed. thats true there is something wrong on that line
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, the 36 is with the hyphens. I'll correct that
Same project using It seems the correct signature for a primary key is: |
@@ -6,12 +6,14 @@ | |||
import datetime | |||
import decimal | |||
import math | |||
import uuid |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to lazy-load uuid as most projects won't need it? It could be done with just one more line of code in this file and in django/forms/fields.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could make the same argument for all kinds of modules throughout django - starting with the import of decimal
two lines above.
Python's uuid
is a single python file. The overhead to import it at server-start time is so negligible it's not worth doing anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's true, but decimal and datetime are much more popular.
Rebased against master. Should be considered blocked by #3047 so I can remove |
value = super(UUIDField, self).to_python(value) | ||
if value in self.empty_values: | ||
return None | ||
if isinstance(value, six.string_types): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(same pattern as above, if you change it)
Please add 1.8 release notes, otherwise LGTM. |
|
I thought my last commit should fix that (ed970d4)... I'm not sure if the CI did something weird though because the list of commits on the oracle build is not the same as the list on the main build page... |
Hm, I don't have insight. Push some changes and try it again. |
Squashed into a single commit with release notes. IMO, this is now RFC. |
@@ -92,7 +92,8 @@ below for information on how to set up your database correctly. | |||
PostgreSQL notes | |||
================ | |||
|
|||
Django supports PostgreSQL 9.0 and higher. | |||
Django supports PostgreSQL 9.0 and higher. It requires the use of Psycopg2 | |||
2.0.9 or higher. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add to release notes as well.
Ship it! |
Uses native support in postgres, and char(32) on other backends.
Merged in ed78212 |
Added UUIDField. Not particularly complicated this one - no custom lookups or anything. Casts all values to
uuid.UUID
instances in python.TODO:
Squashed patch should be marked as fixing #19463.