mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[4.0.x] Fixed #33008 -- Fixed prefetch_related() for deleted GenericForeignKeys.
Thanks Simon Charette for the implementation idea.
Backport of cc4cb95bef from main
			
			
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							8ab95364b5
						
					
				
				
					commit
					dd8945d361
				
			| @@ -213,7 +213,7 @@ class GenericForeignKey(FieldCacheMixin): | |||||||
|             gfk_key, |             gfk_key, | ||||||
|             True, |             True, | ||||||
|             self.name, |             self.name, | ||||||
|             True, |             False, | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def __get__(self, instance, cls=None): |     def __get__(self, instance, cls=None): | ||||||
| @@ -229,6 +229,8 @@ class GenericForeignKey(FieldCacheMixin): | |||||||
|         pk_val = getattr(instance, self.fk_field) |         pk_val = getattr(instance, self.fk_field) | ||||||
|  |  | ||||||
|         rel_obj = self.get_cached_value(instance, default=None) |         rel_obj = self.get_cached_value(instance, default=None) | ||||||
|  |         if rel_obj is None and self.is_cached(instance): | ||||||
|  |             return rel_obj | ||||||
|         if rel_obj is not None: |         if rel_obj is not None: | ||||||
|             ct_match = ct_id == self.get_content_type(obj=rel_obj, using=instance._state.db).id |             ct_match = ct_id == self.get_content_type(obj=rel_obj, using=instance._state.db).id | ||||||
|             pk_match = rel_obj._meta.pk.to_python(pk_val) == rel_obj.pk |             pk_match = rel_obj._meta.pk.to_python(pk_val) == rel_obj.pk | ||||||
|   | |||||||
| @@ -2,14 +2,14 @@ import json | |||||||
|  |  | ||||||
| from django.contrib.contenttypes.fields import GenericForeignKey | from django.contrib.contenttypes.fields import GenericForeignKey | ||||||
| from django.db import models | from django.db import models | ||||||
| from django.test import SimpleTestCase, TestCase | from django.test import TestCase | ||||||
| from django.test.utils import isolate_apps | from django.test.utils import isolate_apps | ||||||
|  |  | ||||||
| from .models import Answer, Question | from .models import Answer, Post, Question | ||||||
|  |  | ||||||
|  |  | ||||||
| @isolate_apps('contenttypes_tests') | @isolate_apps('contenttypes_tests') | ||||||
| class GenericForeignKeyTests(SimpleTestCase): | class GenericForeignKeyTests(TestCase): | ||||||
|  |  | ||||||
|     def test_str(self): |     def test_str(self): | ||||||
|         class Model(models.Model): |         class Model(models.Model): | ||||||
| @@ -24,6 +24,19 @@ class GenericForeignKeyTests(SimpleTestCase): | |||||||
|         with self.assertRaisesMessage(ValueError, "Custom queryset can't be used for this lookup."): |         with self.assertRaisesMessage(ValueError, "Custom queryset can't be used for this lookup."): | ||||||
|             Answer.question.get_prefetch_queryset(Answer.objects.all(), Answer.objects.all()) |             Answer.question.get_prefetch_queryset(Answer.objects.all(), Answer.objects.all()) | ||||||
|  |  | ||||||
|  |     def test_get_object_cache_respects_deleted_objects(self): | ||||||
|  |         question = Question.objects.create(text='Who?') | ||||||
|  |         post = Post.objects.create(title='Answer', parent=question) | ||||||
|  |  | ||||||
|  |         question_pk = question.pk | ||||||
|  |         Question.objects.all().delete() | ||||||
|  |  | ||||||
|  |         post = Post.objects.get(pk=post.pk) | ||||||
|  |         with self.assertNumQueries(1): | ||||||
|  |             self.assertEqual(post.object_id, question_pk) | ||||||
|  |             self.assertIsNone(post.parent) | ||||||
|  |             self.assertIsNone(post.parent) | ||||||
|  |  | ||||||
|  |  | ||||||
| class GenericRelationTests(TestCase): | class GenericRelationTests(TestCase): | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1033,6 +1033,24 @@ class GenericRelationTests(TestCase): | |||||||
|         # instance returned by the manager. |         # instance returned by the manager. | ||||||
|         self.assertEqual(list(bookmark.tags.all()), list(bookmark.tags.all().all())) |         self.assertEqual(list(bookmark.tags.all()), list(bookmark.tags.all().all())) | ||||||
|  |  | ||||||
|  |     def test_deleted_GFK(self): | ||||||
|  |         TaggedItem.objects.create(tag='awesome', content_object=self.book1) | ||||||
|  |         TaggedItem.objects.create(tag='awesome', content_object=self.book2) | ||||||
|  |         ct = ContentType.objects.get_for_model(Book) | ||||||
|  |  | ||||||
|  |         book1_pk = self.book1.pk | ||||||
|  |         self.book1.delete() | ||||||
|  |  | ||||||
|  |         with self.assertNumQueries(2): | ||||||
|  |             qs = TaggedItem.objects.filter(tag='awesome').prefetch_related('content_object') | ||||||
|  |             result = [ | ||||||
|  |                 (tag.object_id, tag.content_type_id, tag.content_object) for tag in qs | ||||||
|  |             ] | ||||||
|  |             self.assertEqual(result, [ | ||||||
|  |                 (book1_pk, ct.pk, None), | ||||||
|  |                 (self.book2.pk, ct.pk, self.book2), | ||||||
|  |             ]) | ||||||
|  |  | ||||||
|  |  | ||||||
| class MultiTableInheritanceTest(TestCase): | class MultiTableInheritanceTest(TestCase): | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user