Ticket #13839: 13839-5.patch
File 13839-5.patch, 8.9 KB (added by , 13 years ago) |
---|
-
django/db/models/fields/related.py
249 249 if instance is None: 250 250 return self 251 251 try: 252 re turngetattr(instance, self.cache_name)252 rel_obj = getattr(instance, self.cache_name) 253 253 except AttributeError: 254 254 params = {'%s__pk' % self.related.field.name: instance._get_pk_val()} 255 rel_obj = self.get_query_set(instance=instance).get(**params) 255 try: 256 rel_obj = self.get_query_set(instance=instance).get(**params) 257 except self.related.model.DoesNotExist: 258 rel_obj = None 259 else: 260 setattr(rel_obj, self.related.field.get_cache_name(), instance) 256 261 setattr(instance, self.cache_name, rel_obj) 262 if rel_obj is None: 263 raise self.related.model.DoesNotExist 264 else: 257 265 return rel_obj 258 266 259 267 def __set__(self, instance, value): … … 331 339 def __get__(self, instance, instance_type=None): 332 340 if instance is None: 333 341 return self 334 335 342 try: 336 re turngetattr(instance, self.cache_name)343 rel_obj = getattr(instance, self.cache_name) 337 344 except AttributeError: 338 345 val = getattr(instance, self.field.attname) 339 346 if val is None: 340 # If NULL is an allowed value, return it. 341 if self.field.null: 342 return None 343 raise self.field.rel.to.DoesNotExist 344 other_field = self.field.rel.get_related_field() 345 if other_field.rel: 346 params = {'%s__pk' % self.field.rel.field_name: val} 347 rel_obj = None 347 348 else: 348 params = {'%s__exact' % self.field.rel.field_name: val} 349 qs = self.get_query_set(instance=instance) 350 rel_obj = qs.get(**params) 349 other_field = self.field.rel.get_related_field() 350 if other_field.rel: 351 params = {'%s__pk' % self.field.rel.field_name: val} 352 else: 353 params = {'%s__exact' % self.field.rel.field_name: val} 354 qs = self.get_query_set(instance=instance) 355 # Assuming the database enforces foreign keys, this won't fail. 356 rel_obj = qs.get(**params) 357 if not self.field.rel.multiple: 358 setattr(rel_obj, self.field.related.get_cache_name(), instance) 351 359 setattr(instance, self.cache_name, rel_obj) 360 if rel_obj is None and not self.field.null: 361 raise self.field.rel.to.DoesNotExist 362 else: 352 363 return rel_obj 353 364 354 365 def __set__(self, instance, value): … … 385 396 # populated the cache, then we don't care - we're only accessing 386 397 # the object to invalidate the accessor cache, so there's no 387 398 # need to populate the cache just to expire it again. 388 related = getattr(instance, self. field.get_cache_name(), None)399 related = getattr(instance, self.cache_name, None) 389 400 390 401 # If we've got an old related object, we need to clear out its 391 402 # cache. This cache also might not exist if the related object 392 403 # hasn't been accessed yet. 393 if related: 394 cache_name = self.field.related.get_cache_name() 395 try: 396 delattr(related, cache_name) 397 except AttributeError: 398 pass 404 if related is not None: 405 setattr(related, self.field.related.get_cache_name(), None) 399 406 400 407 # Set the value of the related field 401 408 try: … … 405 412 setattr(instance, self.field.attname, val) 406 413 407 414 # Since we already know what the related object is, seed the related 408 # object cache now, too. This avoids another db hit if you get the415 # object caches now, too. This avoids another db hit if you get the 409 416 # object you just set. 410 setattr(instance, self.field.get_cache_name(), value) 417 setattr(instance, self.cache_name, value) 418 if value is not None and not self.field.rel.multiple: 419 setattr(value, self.field.related.get_cache_name(), instance) 411 420 412 421 class ForeignRelatedObjectsDescriptor(object): 413 422 # This class provides the functionality that makes the related-object -
tests/regressiontests/select_related_onetoone/tests.py
79 79 p1 = Product.objects.create(name="Django Plushie", image=im) 80 80 p2 = Product.objects.create(name="Talking Django Plushie") 81 81 82 self.assertEqual(len(Product.objects.select_related("image")), 2) 82 with self.assertNumQueries(1): 83 result = sorted(Product.objects.select_related("image"), key=lambda x: x.name) 84 self.assertEqual([p.name for p in result], ["Django Plushie", "Talking Django Plushie"]) 85 86 self.assertEqual(p1.image, im) 87 # Check for ticket #13839 88 self.assertIsNone(p2.image) 89 90 def test_missing_reverse(self): 91 """ 92 Ticket #13839: select_related() should NOT cache None 93 for missing objects on a reverse 1-1 relation. 94 """ 95 with self.assertNumQueries(1): 96 user = User.objects.select_related('userprofile').get(username='bob') 97 with self.assertRaises(UserProfile.DoesNotExist): 98 user.userprofile 99 100 def test_nullable_missing_reverse(self): 101 """ 102 Ticket #13839: select_related() should NOT cache None 103 for missing objects on a reverse 0-1 relation. 104 """ 105 Image.objects.create(name="imag1") 106 107 with self.assertNumQueries(1): 108 image = Image.objects.select_related('product').get() 109 with self.assertRaises(Product.DoesNotExist): 110 image.product -
tests/regressiontests/one_to_one_regress/tests.py
132 132 Target.objects.exclude(pointer2=None), 133 133 [] 134 134 ) 135 136 def test_reverse_object_does_not_exist_cache(self): 137 """ 138 Regression for #13839 and #17439. 139 140 DoesNotExist on a reverse one-to-one relation is cached. 141 """ 142 p = Place(name='Zombie Cats', address='Not sure') 143 p.save() 144 with self.assertNumQueries(1): 145 with self.assertRaises(Restaurant.DoesNotExist): 146 p.restaurant 147 with self.assertNumQueries(0): 148 with self.assertRaises(Restaurant.DoesNotExist): 149 p.restaurant 150 151 def test_reverse_object_cached_when_related_is_accessed(self): 152 """ 153 Regression for #13839 and #17439. 154 155 The target of a one-to-one relation is cached 156 when the origin is accessed through the reverse relation. 157 """ 158 # Use a fresh object without caches 159 r = Restaurant.objects.get(pk=self.r1.pk) 160 p = r.place 161 with self.assertNumQueries(0): 162 self.assertEqual(p.restaurant, r) 163 164 def test_related_object_cached_when_reverse_is_accessed(self): 165 """ 166 Regression for #13839 and #17439. 167 168 The origin of a one-to-one relation is cached 169 when the target is accessed through the reverse relation. 170 """ 171 # Use a fresh object without caches 172 p = Place.objects.get(pk=self.p1.pk) 173 r = p.restaurant 174 with self.assertNumQueries(0): 175 self.assertEqual(r.place, p) 176 177 def test_reverse_object_cached_when_related_is_set(self): 178 """ 179 Regression for #13839 and #17439. 180 181 The target of a one-to-one relation is always cached. 182 """ 183 p = Place(name='Zombie Cats', address='Not sure') 184 p.save() 185 self.r1.place = p 186 self.r1.save() 187 with self.assertNumQueries(0): 188 self.assertEqual(p.restaurant, self.r1) 189 190 def test_reverse_object_cached_when_related_is_unset(self): 191 """ 192 Regression for #13839 and #17439. 193 194 The target of a one-to-one relation is always cached. 195 """ 196 b = UndergroundBar(place=self.p1, serves_cocktails=True) 197 b.save() 198 with self.assertNumQueries(0): 199 self.assertEqual(self.p1.undergroundbar, b) 200 b.place = None 201 b.save() 202 with self.assertNumQueries(0): 203 with self.assertRaises(UndergroundBar.DoesNotExist): 204 self.p1.undergroundbar