mirror of
https://github.com/django/django.git
synced 2025-11-17 10:43:25 +00:00
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:
parent
ba72606760
commit
2dd64937f2
3 changed files with 108 additions and 2 deletions
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
|
|
@ -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')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue