For a number of reasons^, I’d like to use a UUID as a primary key in some of my Django models. If I do so, will I still be able to use outside apps like “contrib.comments”, “django-voting” or “django-tagging” which use generic relations via ContentType?
Using “django-voting” as an example, the Vote model looks like this:
class Vote(models.Model): user = models.ForeignKey(User) content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() object = generic.GenericForeignKey('content_type', 'object_id') vote = models.SmallIntegerField(choices=SCORES)
This app seems to be assuming that the primary key for the model being voted on is an integer.
The built-in comments app seems to be capable of handling non-integer PKs, though:
class BaseCommentAbstractModel(models.Model): content_type = models.ForeignKey(ContentType, verbose_name=_('content type'), related_name="content_type_set_for_%(class)s") object_pk = models.TextField(_('object ID')) content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")
Is this “integer-PK-assumed” problem a common situation for third-party apps which would make using UUIDs a pain? Or, possibly, am I misreading this situation?
Is there a way to use UUIDs as primary keys in Django without causing too much trouble?
^ Some of the reasons: hiding object counts, preventing url “id crawling”, using multiple servers to create non-conflicting objects, …
A UUID primary key will cause problems not only with generic relations, but with efficiency in general: every foreign key will be significantly more expensive—both to store, and to join on—than a machine word.
However, nothing requires the UUID to be the primary key: just make it a secondary key, by supplementing your model with a uuid field with
unique=True. Use the implicit primary key as normal (internal to your system), and use the UUID as your external identifier.
Django 1.8 now has a built in UUID field. The performance differences when using a UUID vs integer are negligible.
import uuid from django.db import models class MyUUIDModel(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
I ran into a similar situation and found out in the official Django documentation, that the
object_id doesn’t have to be of the same type as the primary_key of the related model. For example, if you want your generic relationship to be valid for both IntegerField and CharField id’s, just set your
object_id to be a CharField. Since integers can coerce into strings it’ll be fine. Same goes for UUIDField.
class Vote(models.Model): user = models.ForeignKey(User) content_type = models.ForeignKey(ContentType) object_id = models.CharField(max_length=50) # <<-- This line was modified object = generic.GenericForeignKey('content_type', 'object_id') vote = models.SmallIntegerField(choices=SCORES)