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:
|
||||
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 (
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue