Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 834
Oskar Persson Avatar asked May 25 '18 10:05

Oskar Persson


1 Answers

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)
like image 182
Brown Bear Avatar answered Sep 17 '22 20:09

Brown Bear