Fix prefetch_related for GFKs to UUID primary keys

Ensure GenericForeignKey works with UUID PKs by using to_python for type conversion.
Fixes issue where prefetch_related returned None for GFKs to UUID models.
References: justquick/django-activity-stream#245
This commit is contained in:
utkarsh.arya@zomato.com 2025-11-15 22:55:43 +00:00
parent ba72606760
commit 2dd64937f2
3 changed files with 108 additions and 2 deletions

View file

@ -202,7 +202,7 @@ class GenericForeignKey(FieldCacheMixin):
else:
model = self.get_content_type(id=ct_id,
using=obj._state.db).model_class()
return (model._meta.pk.get_prep_value(getattr(obj, self.fk_field)),
return (model._meta.pk.to_python(getattr(obj, self.fk_field)),
model)
return (

View file

@ -295,3 +295,19 @@ class Flea(models.Model):
current_room = models.ForeignKey(Room, models.SET_NULL, related_name='fleas', null=True)
pets_visited = models.ManyToManyField(Pet, related_name='fleas_hosted')
people_visited = models.ManyToManyField(Person, related_name='fleas_hosted')
# Models for UUID primary key with GenericForeignKey tests:
class UUIDItem(models.Model):
"""Model with UUID primary key to be referenced by GenericForeignKey."""
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=100)
class TaggedUUIDItem(models.Model):
"""Model with GenericForeignKey pointing to UUIDItem."""
tag = models.CharField(max_length=50)
content_type = models.ForeignKey(ContentType, models.CASCADE)
object_id = models.CharField(max_length=255)
content_object = GenericForeignKey('content_type', 'object_id')

View file

@ -1,6 +1,7 @@
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from .models import Flea, House, Person, Pet, Room
from .models import Flea, House, Person, Pet, Room, TaggedUUIDItem, UUIDItem
class UUIDPrefetchRelated(TestCase):
@ -102,3 +103,92 @@ class UUIDPrefetchRelatedLookups(TestCase):
redwood = House.objects.prefetch_related('rooms__fleas__pets_visited').get(name='Redwood')
with self.assertNumQueries(0):
self.assertEqual('Spooky', redwood.rooms.all()[0].fleas.all()[0].pets_visited.all()[0].name)
class UUIDGenericForeignKeyTests(TestCase):
"""
Tests for prefetch_related with GenericForeignKey pointing to models
with UUID primary keys.
"""
def test_prefetch_generic_foreign_key_with_uuid(self):
"""
Test that prefetch_related works correctly when a GenericForeignKey
points to a model with a UUID primary key.
"""
# Create UUIDItem instances
item1 = UUIDItem.objects.create(name='Item 1')
item2 = UUIDItem.objects.create(name='Item 2')
item3 = UUIDItem.objects.create(name='Item 3')
# Get ContentType for UUIDItem
ct = ContentType.objects.get_for_model(UUIDItem)
# Create TaggedUUIDItem instances pointing to UUIDItem instances
tag1 = TaggedUUIDItem.objects.create(
tag='tag1',
content_type=ct,
object_id=str(item1.id)
)
tag2 = TaggedUUIDItem.objects.create(
tag='tag2',
content_type=ct,
object_id=str(item2.id)
)
tag3 = TaggedUUIDItem.objects.create(
tag='tag3',
content_type=ct,
object_id=str(item3.id)
)
# Test prefetch_related
# Should do 2 queries: one for TaggedUUIDItem and one for UUIDItem
with self.assertNumQueries(2):
tags = list(TaggedUUIDItem.objects.prefetch_related('content_object'))
# Now accessing content_object should not hit the database
with self.assertNumQueries(0):
self.assertEqual(tags[0].content_object.name, 'Item 1')
self.assertEqual(tags[1].content_object.name, 'Item 2')
self.assertEqual(tags[2].content_object.name, 'Item 3')
# Verify the objects are properly linked
self.assertEqual(tags[0].content_object.id, item1.id)
self.assertEqual(tags[1].content_object.id, item2.id)
self.assertEqual(tags[2].content_object.id, item3.id)
def test_prefetch_generic_foreign_key_uuid_multiple_content_types(self):
"""
Test that prefetch_related works correctly when GenericForeignKey
points to multiple models, some with UUID primary keys.
"""
# Create UUIDItem instances
uuid_item = UUIDItem.objects.create(name='UUID Item')
# Create a Bookmark instance (has integer pk)
from .models import Bookmark
bookmark = Bookmark.objects.create(url='http://example.com')
# Get ContentTypes
uuid_ct = ContentType.objects.get_for_model(UUIDItem)
bookmark_ct = ContentType.objects.get_for_model(Bookmark)
# Create TaggedUUIDItem instances pointing to different models
tag1 = TaggedUUIDItem.objects.create(
tag='uuid_tag',
content_type=uuid_ct,
object_id=str(uuid_item.id)
)
tag2 = TaggedUUIDItem.objects.create(
tag='bookmark_tag',
content_type=bookmark_ct,
object_id=str(bookmark.id)
)
# Test prefetch_related with multiple content types
with self.assertNumQueries(3): # TaggedUUIDItem, UUIDItem, Bookmark
tags = list(TaggedUUIDItem.objects.prefetch_related('content_object'))
with self.assertNumQueries(0):
self.assertEqual(tags[0].content_object.name, 'UUID Item')
self.assertEqual(tags[1].content_object.url, 'http://example.com')