Ticket #14368: ticket-14368.patch

File ticket-14368.patch, 4.4 KB (added by gsakkis, 5 years ago)

Sets NULL the related_field of the existing child object (if any)

  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    a b  
    253253                if not router.allow_relation(value, instance):
    254254                    raise ValueError('Cannot assign "%r": instance is on database "%s", value is on database "%s"' %
    255255                                        (value, instance._state.db, value._state.db))
     256            # Since we already know what the related object is, seed the related
     257            # object caches now, too. This avoids another db hit if you get the
     258            # object you just set.
     259            setattr(instance, self.cache_name, value)
     260            setattr(value, self.related.field.name, instance)
     261        else:
     262            # update the cached related instance (if any) and clear the cache
     263            try:
     264                rel_obj = getattr(instance, self.cache_name)
     265            except AttributeError:
     266                pass
     267            else:
     268                delattr(instance, self.cache_name)
     269                setattr(rel_obj, self.related.field.name, None)
    256270
    257         # Set the value of the related field to the value of the related object's related field
    258         setattr(value, self.related.field.attname, getattr(instance, self.related.field.rel.get_related_field().attname))
    259 
    260         # Since we already know what the related object is, seed the related
    261         # object caches now, too. This avoids another db hit if you get the
    262         # object you just set.
    263         setattr(instance, self.cache_name, value)
    264         setattr(value, self.related.field.get_cache_name(), instance)
     271        if self.related.field.null:
     272            # break the link to the existing value (if any)
     273            filter_params = {self.related.field.name: instance._get_pk_val()}
     274            update_params = {self.related.field.name: None}
     275            db = router.db_for_write(self.related.model, instance=instance)
     276            self.related.model._base_manager.using(db).filter(**filter_params).update(**update_params)
    265277
    266278class ReverseSingleRelatedObjectDescriptor(object):
    267279    # This class provides the functionality that makes the related-object
  • tests/regressiontests/one_to_one_regress/tests.py

    diff --git a/tests/regressiontests/one_to_one_regress/tests.py b/tests/regressiontests/one_to_one_regress/tests.py
    a b  
    128128                Target.objects.exclude(pointer2=None),
    129129                []
    130130        )
     131
     132    def test_modify_related(self):
     133        """
     134        Setting the reverse relationship of a O2O null field to a new instance
     135        should succeed and remove the link from the current instance (if any).
     136        """
     137        b1 = UndergroundBar.objects.create(place=self.p1, serves_cocktails=False)
     138        b2 = UndergroundBar(serves_cocktails=True)
     139        self.p1.undergroundbar = b2
     140        b2.save()
     141        self.assertEqual(UndergroundBar.objects.get(pk=b1.pk).place, None)
     142        self.assertEqual(UndergroundBar.objects.get(pk=b2.pk).place, self.p1)
     143
     144    def test_unset_related(self):
     145        """Regression test for #14368 [1]
     146
     147        Setting the reverse relationship of a O2O null field to None should
     148        succeed and remove the link from the current instance (if any).
     149        """
     150        bar = UndergroundBar.objects.create(place=self.p1, serves_cocktails=False)
     151        self._unset_related(self.p1, bar)
     152
     153    def test_unset_related_after_set(self):
     154        """Regression test for #14368 [2]
     155
     156        Setting the reverse relationship of a O2O null field to None should
     157        succeed and remove the link from the current instance (if any).
     158
     159        In this test the related object is set to point to an instance before
     160        set to None.
     161        """
     162        bar = UndergroundBar(serves_cocktails=False)
     163        self.p1.undergroundbar = bar
     164        bar.save()
     165        self._unset_related(self.p1, bar)
     166        self.assertEqual(bar.place, None)
     167
     168    def _unset_related(self, place, undergroundbar):
     169        self.assertEqual(UndergroundBar.objects.filter(place=place).count(), 1)
     170        self.assertEqual(place.undergroundbar, undergroundbar)
     171        place.undergroundbar = None
     172        self.assertEqual(UndergroundBar.objects.filter(place=place).count(), 0)
     173        self.assertRaises(UndergroundBar.DoesNotExist, getattr, place, 'undergroundbar')
Back to Top