From b018128ea5f9ad494afb0007c597bc61630dc087 Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 15 Mar 2012 15:06:57 +0000 Subject: [PATCH] Fixed #17838 - prefetch_related fails for GenericForeignKeys when related object id is not a CharField/TextField Thanks to mkai for the report and debugging, and tmitchell and Przemek Lewandowski for their work on the patch. git-svn-id: http://code.djangoproject.com/svn/django/trunk@17744 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/contenttypes/generic.py | 10 +++++----- tests/modeltests/prefetch_related/models.py | 9 +++++++++ tests/modeltests/prefetch_related/tests.py | 10 +++++++++- .../regressiontests/comment_tests/tests/model_tests.py | 7 +++++++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index 5ed81a3664..a6732d7a2e 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -85,16 +85,16 @@ class GenericForeignKey(object): ret_val.extend(ct.get_all_objects_for_this_type(pk__in=fkeys)) # For doing the join in Python, we have to match both the FK val and the - # content type, so the 'attr' vals we return need to be callables that - # will return a (fk, class) pair. + # content type, so we use a callable that returns a (fk, class) pair. def gfk_key(obj): ct_id = getattr(obj, ct_attname) if ct_id is None: return None else: - return (getattr(obj, self.fk_field), - self.get_content_type(id=ct_id, - using=obj._state.db).model_class()) + 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)), + model) return (ret_val, lambda obj: (obj._get_pk_val(), obj.__class__), diff --git a/tests/modeltests/prefetch_related/models.py b/tests/modeltests/prefetch_related/models.py index e6e4f89053..1dc034fa6c 100644 --- a/tests/modeltests/prefetch_related/models.py +++ b/tests/modeltests/prefetch_related/models.py @@ -125,6 +125,15 @@ class Bookmark(models.Model): tags = generic.GenericRelation(TaggedItem) +class Comment(models.Model): + comment = models.TextField() + + # Content-object field + content_type = models.ForeignKey(ContentType) + object_pk = models.TextField() + content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk") + + ## Models for lookup ordering tests diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/modeltests/prefetch_related/tests.py index 0a757271e2..b0bc435941 100644 --- a/tests/modeltests/prefetch_related/tests.py +++ b/tests/modeltests/prefetch_related/tests.py @@ -5,7 +5,7 @@ from django.test import TestCase from .models import (Author, Book, Reader, Qualification, Teacher, Department, TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge, - BookWithYear, Person, House, Room, Employee) + BookWithYear, Person, House, Room, Employee, Comment) class PrefetchRelatedTests(TestCase): @@ -254,6 +254,14 @@ class GenericRelationTests(TestCase): qs = TaggedItem.objects.prefetch_related('content_object') list(qs) + def test_prefetch_GFK_nonint_pk(self): + Comment.objects.create(comment="awesome", content_object=self.book1) + + # 1 for Comment table, 1 for Book table + with self.assertNumQueries(2): + qs = Comment.objects.prefetch_related('content_object') + [c.content_object for c in qs] + def test_traverse_GFK(self): """ Test that we can traverse a 'content_object' with prefetch_related() and diff --git a/tests/regressiontests/comment_tests/tests/model_tests.py b/tests/regressiontests/comment_tests/tests/model_tests.py index c7eaa4cef3..69c1a8118f 100644 --- a/tests/regressiontests/comment_tests/tests/model_tests.py +++ b/tests/regressiontests/comment_tests/tests/model_tests.py @@ -49,3 +49,10 @@ class CommentManagerTests(CommentTestCase): author_comments = list(Comment.objects.for_model(Author.objects.get(pk=1))) self.assertEqual(article_comments, [c1, c3]) self.assertEqual(author_comments, [c2]) + + def testPrefetchRelated(self): + c1, c2, c3, c4 = self.createSomeComments() + # one for comments, one for Articles, one for Author + with self.assertNumQueries(3): + qs = Comment.objects.prefetch_related('content_object') + [c.content_object for c in qs]