28

Feb

Filed in Code, Django |

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)

14 Responses to "Improved UUIDField in Django"

Subscribe to this topic with RSS or get the Trackback URL
codekoala (Feb 28th):

Interesting snippet!! Thanks for sharing.

One question: shouldn’t “kwargs['unique'] = True” be outside of the “if self.auto:” block?

David (Feb 28th):

No because it could just be a ForeignKey reference.

codekoala (Feb 28th):

ah, good point. I was thinking in terms of using a UUIDField as a model’s PK, without considering FK scenarios. Thanks again!

at9t (Mar 1st):

This one seems useful, thanks!

Brantley Harris (Mar 2nd):

Careful, the uuid module is not in 2.4 or below.

skabber (Mar 2nd):

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

trbs (Mar 3rd):

Any patched for useful improvements to the UUIDField in django-extensions would be much appreciated :)

Joe (Mar 8th):

What is the variable “name” here (undefined)?

elif version in (3, 5):
self.namespace, self.name = namespace, name

David (Mar 10th):

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.

Joe (Mar 11th):

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?

Joe (Mar 11th):

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

HenrikV (Mar 24th):

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?

EdMenendez (Jun 25th):

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)

David Underhill (Jan 28th):

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.

Leave A Reply

 Username (*required)

 Email Address (*private)

 Website (*optional)

Note: Comments moderation may be active so there is no need to resubmit your comment.