Search code examples
djangouuiddjango-generic-relations

GenericForeignKey gets wrong id when used with model with UUIDField


When using GenericForeignKey together with UUIDField, what is the recommended way to get a queryset of the "real model" from a queryset of generic objects?

Here are the models I'm testing with:

import uuid
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models

class Foo(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4)

class Generic(models.Model):
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.CharField(max_length=255)
    content_object = GenericForeignKey()

and this is what I've tried so far:

>>> from django.db.models import Subquery
>>> from foo.models import Foo, Generic

>>> f = Foo.objects.create()
>>> g = Generic.objects.create(content_object=f)
>>> Foo.objects.filter(id__in=Subquery(Generic.objects.all().values('object_id')))
<QuerySet []>

>>> Generic.objects.get().object_id
'997eaf64-a115-4f48-b3ac-8cbcc21274a8'
>>> Foo.objects.get().pk
UUID('997eaf64-a115-4f48-b3ac-8cbcc21274a8')

I'm guessing this has to do with the UUIDs being saved without the hyphens for the UUIDField. I can't make object_id into a UUIDField either since I need other models that have integers and strings as primary keys.

I'm using Django 1.11 but I've also tested Django 2.0 which has the same problem.


Solution

  • the main trouble is in the explicit type casts

    so, the idea of @Alasdair, you can to try:

    foo_content_type = ContentType.objects.get_for_model(Foo)
    gids = Generic.objects.filter(content_type=foo_content_type)
    # create list of uuid strings
    gids = list(gids.values_list('object_id', flat=True))
    Foo.objects.filter(pk__in=gids)
    

    Other solution: you can add uuid field to the Generic models. For example:

    class Generic(models.Model):
        content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
        object_id = models.CharField(max_length=255)
        content_object = GenericForeignKey()
        uuid4 = models.UUIDField(blank=True, null=True)
    
        def save(self, *args, **kwargs):
            try:
                self.uuid4 = uuid.UUID(self.object_id)
            except Exception as e:
                pass
            super().save(*args, **kwargs)
    

    and queryset will look:

    foo_content_type = ContentType.objects.get_for_model(Foo)
    gids = Generic.objects.filter(content_type=foo_content_type).values('uuid4')
    Foo.objects.filter(pk__in=gids)