So today I went ahead and cleaned up my UUIDField implementation. Originally it was just based off of a snippet. I wanted to swap it over to use a BINARY column in MySQL, after reading on how FriendFeed had been using UUIDs. Due to using Django however, you can’t use binary data or BLOB columns effectively. It tries to coerce everything to unicode so it ends up failing on any kind of binary data.
Instead of using a BINARY(16) column, I just switched over to a CHAR(32) (previously a CHAR(36)). In doing this I also converted the field to keep everything in the UUID format (to better handle passing around the values, and converting to any format you want).
All in all, the new UUIDField works as intended, and is a nice improvement over the simple “automatically insert a UUID here”, which is what it was before.
Here’s the code:
from django.db import models import uuid class UUIDField(models.CharField): """ A field which stores a UUID value in hex format. This may also have the Boolean attribute 'auto' which will set the value on initial save to a new UUID value (calculated using the UUID1 method). Note that while all UUIDs are expected to be unique we enforce this with a DB constraint. """ __metaclass__ = models.SubfieldBase def __init__(self, version=4, node=None, clock_seq=None, namespace=None, auto=False, name=None, *args, **kwargs): assert(version in (1, 3, 4, 5), "UUID version %s is not supported." % (version,)) self.auto = auto self.version = version # Set this as a fixed value, we store UUIDs in text. kwargs['max_length'] = 32 if auto: # Do not let the user edit UUIDs if they are auto-assigned. kwargs['editable'] = False kwargs['blank'] = True kwargs['unique'] = True if version == 1: self.node, self.clock_seq = node, clock_seq elif version in (3, 5): self.namespace, self.name = namespace, name super(UUIDField, self).__init__(*args, **kwargs) def _create_uuid(self): if self.version == 1: args = (self.node, self.clock_seq) elif self.version in (3, 5): args = (self.namespace, self.name) else: args = () return getattr(uuid, 'uuid%s' % (self.version,))(*args) def db_type(self): return 'char' def pre_save(self, model_instance, add): """ see CharField.pre_save This is used to ensure that we auto-set values if required. """ value = getattr(model_instance, self.attname, None) if not value and self.auto: # Assign a new value for this attribute if required. value = self._create_uuid() setattr(model_instance, self.attname, value) return value def to_python(self, value): if not value: return if not isinstance(value, uuid.UUID): value = uuid.UUID(value) return value def get_db_prep_save(self, value): if not value: return assert(isinstance(value, uuid.UUID)) return value.hex def get_db_prep_value(self, value): if not value: return assert(isinstance(value, uuid.UUID)) return unicode(value)

View Comments Responses to "Improved UUIDField in Django"
Interesting snippet!! Thanks for sharing.
One question: shouldn’t “kwargs['unique'] = True” be outside of the “if self.auto:” block?
No because it could just be a ForeignKey reference.
ah, good point. I was thinking in terms of using a UUIDField as a model’s PK, without considering FK scenarios. Thanks again!
This one seems useful, thanks!
Careful, the uuid module is not in 2.4 or below.
Very nice. Do you know how this compares to the UUIDField in django extensions?
http://code.google.com/p/django-command-extensions/source/browse/trunk/django_extensions/db/fields/__init__.py
Any patched for useful improvements to the UUIDField in django-extensions would be much appreciated
What is the variable “name” here (undefined)?
elif version in (3, 5):
self.namespace, self.name = namespace, name
Sorry the original code has been fixed.
However, there seems to be an issue in Django where it’s not quite passing in UUID instances everywhere. I’m currently looking into why.
Yeah, for some reason my __init__ doesn’t seem to be called in trunk.
In other words with,
class MyModel(models.Model):
uuid = UUIDField(primary_key=True, db_index=True, auto=True)
I get a character(1) in my DB (postgresql) and thus everything fails… so weird. I can even import the class and UUIDField() with a 1/0 in the init and nothing happens? I assume this has to do with the metaclass or something?
Ah, sorry, I need varchar(length) on Postgres… but it supports uuid anyway, so I’m using —
def db_type(self):
return ‘uuid’
Although it seems (for me, at least) that get_db_prep_value assumes it’s dealing with a uuid.UUID, when in the admin change view it’s a unicode (and postgresql and other DBs seemingly want a 36 char string anyway…) so I’m using the following,
def get_db_prep_value(self, value):
if not value: return
print type(value)
if isinstance(value, uuid.UUID):
return unicode(value)
return value
I originally started out using UUID as primary keys on my old project. I had some trouble with uuid imports in Python 2.5
I seem to recall that if you for some reason try to import uuid in settings.py python will silently crash.
Did you have any trouble like that?
I’m using the following for Postgres 8.2.
def db_type(self):
return ‘character varying(32)’
def get_db_prep_value(self, value):
if not value: return
if not isinstance(value, uuid.UUID):
value = uuid.UUID(value)
return value.hex #unicode(value)
Thanks for sharing – this code works well for me! I’m also experimenting with just using a regular CharField and auto-populating the value using a post_init signal handler.
I'm also using 'uuid', and have the following methods for Django 1.2 support:
def get_prep_value(self, value): if not value: return assert(isinstance(value, uuid.UUID)) return value.hex def get_prep_lookup(self, lookup_type, value): if lookup_type == 'exact': return value.hex elif lookup_type == 'in': return [u.hex for u in value] raise ValueError, "UUIDField does not support '%s' queries" % lookup_typeRemoved features, and updated for 1.2 and South: http://gist.github.com/374662
Thanks for the code. I have a question about getting an object based on the guid field. Here is what I have
from myproj.myapp.models import mymodel
import uuid
u = uuid.UUID('eb7e2cc4-db24-4c3d-b8f6-73bed6468135')
w = mymodel.objects.get(guid=u)
This results in a DoesNotExist exception.
If I change the get_db_prep_value function of UUIDField.py to the following, it works:
def get_db_prep_value(self, value):
if not value: return
assert(isinstance(value, uuid.UUID))
#return unicode(value)
return value.hex
Did I miss something? Is there another way to do this? Thanks.
Well , the view of the passage is totally correct ,your details is really reasonable and you guy give us valuable informative post, I totally agree the standpoint of upstairs. I often surfing on this forum when I m free and I find there are so much good information we can learn in this forum! http://spoon8.net/
Leave A Reply