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: else:
model = self.get_content_type(id=ct_id, model = self.get_content_type(id=ct_id,
using=obj._state.db).model_class() 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) model)
return ( return (

View file

@ -295,3 +295,19 @@ class Flea(models.Model):
current_room = models.ForeignKey(Room, models.SET_NULL, related_name='fleas', null=True) current_room = models.ForeignKey(Room, models.SET_NULL, related_name='fleas', null=True)
pets_visited = models.ManyToManyField(Pet, related_name='fleas_hosted') pets_visited = models.ManyToManyField(Pet, related_name='fleas_hosted')
people_visited = models.ManyToManyField(Person, 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 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): class UUIDPrefetchRelated(TestCase):
@ -102,3 +103,92 @@ class UUIDPrefetchRelatedLookups(TestCase):
redwood = House.objects.prefetch_related('rooms__fleas__pets_visited').get(name='Redwood') redwood = House.objects.prefetch_related('rooms__fleas__pets_visited').get(name='Redwood')
with self.assertNumQueries(0): with self.assertNumQueries(0):
self.assertEqual('Spooky', redwood.rooms.all()[0].fleas.all()[0].pets_visited.all()[0].name) 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')