Loading django/contrib/contenttypes/fields.py +24 −15 Original line number Diff line number Diff line Loading @@ -208,7 +208,7 @@ class GenericForeignKey(object): lambda obj: (obj._get_pk_val(), obj.__class__), gfk_key, True, self.cache_attr) self.name) def is_cached(self, instance): return hasattr(instance, self.cache_attr) Loading @@ -217,21 +217,30 @@ class GenericForeignKey(object): if instance is None: return self # Don't use getattr(instance, self.ct_field) here because that might # reload the same ContentType over and over (#5570). Instead, get the # content type ID here, and later when the actual instance is needed, # use ContentType.objects.get_for_id(), which has a global cache. f = self.model._meta.get_field(self.ct_field) ct_id = getattr(instance, f.get_attname(), None) pk_val = getattr(instance, self.fk_field) try: return getattr(instance, self.cache_attr) rel_obj = getattr(instance, self.cache_attr) except AttributeError: rel_obj = None else: if rel_obj and (ct_id != self.get_content_type(obj=rel_obj, using=instance._state.db).id or rel_obj._meta.pk.to_python(pk_val) != rel_obj._get_pk_val()): rel_obj = None if rel_obj is not None: return rel_obj # Make sure to use ContentType.objects.get_for_id() to ensure that # lookups are cached (see ticket #5570). This takes more code than # the naive ``getattr(instance, self.ct_field)``, but has better # performance when dealing with GFKs in loops and such. f = self.model._meta.get_field(self.ct_field) ct_id = getattr(instance, f.get_attname(), None) if ct_id is not None: ct = self.get_content_type(id=ct_id, using=instance._state.db) try: rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field)) rel_obj = ct.get_object_for_this_type(pk=pk_val) except ObjectDoesNotExist: pass setattr(instance, self.cache_attr, rel_obj) Loading tests/generic_relations/tests.py +17 −0 Original line number Diff line number Diff line Loading @@ -555,6 +555,23 @@ id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>""" % tag with self.assertRaises(IntegrityError): TaggedItem.objects.create(tag="shiny", content_object=quartz) def test_cache_invalidation_for_content_type_id(self): # Create a Vegetable and Mineral with the same id. new_id = max(Vegetable.objects.order_by('-id')[0].id, Mineral.objects.order_by('-id')[0].id) + 1 broccoli = Vegetable.objects.create(id=new_id, name="Broccoli") diamond = Mineral.objects.create(id=new_id, name="Diamond", hardness=7) tag = TaggedItem.objects.create(content_object=broccoli, tag="yummy") tag.content_type = ContentType.objects.get_for_model(diamond) self.assertEqual(tag.content_object, diamond) def test_cache_invalidation_for_object_id(self): broccoli = Vegetable.objects.create(name="Broccoli") cauliflower = Vegetable.objects.create(name="Cauliflower") tag = TaggedItem.objects.create(content_object=broccoli, tag="yummy") tag.object_id = cauliflower.id self.assertEqual(tag.content_object, cauliflower) class CustomWidget(forms.TextInput): pass Loading Loading
django/contrib/contenttypes/fields.py +24 −15 Original line number Diff line number Diff line Loading @@ -208,7 +208,7 @@ class GenericForeignKey(object): lambda obj: (obj._get_pk_val(), obj.__class__), gfk_key, True, self.cache_attr) self.name) def is_cached(self, instance): return hasattr(instance, self.cache_attr) Loading @@ -217,21 +217,30 @@ class GenericForeignKey(object): if instance is None: return self # Don't use getattr(instance, self.ct_field) here because that might # reload the same ContentType over and over (#5570). Instead, get the # content type ID here, and later when the actual instance is needed, # use ContentType.objects.get_for_id(), which has a global cache. f = self.model._meta.get_field(self.ct_field) ct_id = getattr(instance, f.get_attname(), None) pk_val = getattr(instance, self.fk_field) try: return getattr(instance, self.cache_attr) rel_obj = getattr(instance, self.cache_attr) except AttributeError: rel_obj = None else: if rel_obj and (ct_id != self.get_content_type(obj=rel_obj, using=instance._state.db).id or rel_obj._meta.pk.to_python(pk_val) != rel_obj._get_pk_val()): rel_obj = None if rel_obj is not None: return rel_obj # Make sure to use ContentType.objects.get_for_id() to ensure that # lookups are cached (see ticket #5570). This takes more code than # the naive ``getattr(instance, self.ct_field)``, but has better # performance when dealing with GFKs in loops and such. f = self.model._meta.get_field(self.ct_field) ct_id = getattr(instance, f.get_attname(), None) if ct_id is not None: ct = self.get_content_type(id=ct_id, using=instance._state.db) try: rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field)) rel_obj = ct.get_object_for_this_type(pk=pk_val) except ObjectDoesNotExist: pass setattr(instance, self.cache_attr, rel_obj) Loading
tests/generic_relations/tests.py +17 −0 Original line number Diff line number Diff line Loading @@ -555,6 +555,23 @@ id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>""" % tag with self.assertRaises(IntegrityError): TaggedItem.objects.create(tag="shiny", content_object=quartz) def test_cache_invalidation_for_content_type_id(self): # Create a Vegetable and Mineral with the same id. new_id = max(Vegetable.objects.order_by('-id')[0].id, Mineral.objects.order_by('-id')[0].id) + 1 broccoli = Vegetable.objects.create(id=new_id, name="Broccoli") diamond = Mineral.objects.create(id=new_id, name="Diamond", hardness=7) tag = TaggedItem.objects.create(content_object=broccoli, tag="yummy") tag.content_type = ContentType.objects.get_for_model(diamond) self.assertEqual(tag.content_object, diamond) def test_cache_invalidation_for_object_id(self): broccoli = Vegetable.objects.create(name="Broccoli") cauliflower = Vegetable.objects.create(name="Cauliflower") tag = TaggedItem.objects.create(content_object=broccoli, tag="yummy") tag.object_id = cauliflower.id self.assertEqual(tag.content_object, cauliflower) class CustomWidget(forms.TextInput): pass Loading