Code

Ticket #14368: ticket-14368.patch

File ticket-14368.patch, 4.4 KB (added by gsakkis, 4 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')