Ticket #13839: 13839-5.patch

File 13839-5.patch, 8.9 KB (added by Aymeric Augustin, 13 years ago)
  • django/db/models/fields/related.py

     
    249249        if instance is None:
    250250            return self
    251251        try:
    252             return getattr(instance, self.cache_name)
     252            rel_obj = getattr(instance, self.cache_name)
    253253        except AttributeError:
    254254            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)
    256261            setattr(instance, self.cache_name, rel_obj)
     262        if rel_obj is None:
     263            raise self.related.model.DoesNotExist
     264        else:
    257265            return rel_obj
    258266
    259267    def __set__(self, instance, value):
     
    331339    def __get__(self, instance, instance_type=None):
    332340        if instance is None:
    333341            return self
    334 
    335342        try:
    336             return getattr(instance, self.cache_name)
     343            rel_obj = getattr(instance, self.cache_name)
    337344        except AttributeError:
    338345            val = getattr(instance, self.field.attname)
    339346            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
    347348            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)
    351359            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:
    352363            return rel_obj
    353364
    354365    def __set__(self, instance, value):
     
    385396            # populated the cache, then we don't care - we're only accessing
    386397            # the object to invalidate the accessor cache, so there's no
    387398            # 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)
    389400
    390401            # If we've got an old related object, we need to clear out its
    391402            # cache. This cache also might not exist if the related object
    392403            # 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)
    399406
    400407        # Set the value of the related field
    401408        try:
     
    405412        setattr(instance, self.field.attname, val)
    406413
    407414        # 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 the
     415        # object caches now, too. This avoids another db hit if you get the
    409416        # 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)
    411420
    412421class ForeignRelatedObjectsDescriptor(object):
    413422    # This class provides the functionality that makes the related-object
  • tests/regressiontests/select_related_onetoone/tests.py

     
    7979        p1 = Product.objects.create(name="Django Plushie", image=im)
    8080        p2 = Product.objects.create(name="Talking Django Plushie")
    8181
    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

     
    132132                Target.objects.exclude(pointer2=None),
    133133                []
    134134        )
     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
Back to Top