Ticket #10262: delete_cascade_option_with_tests_r9834.diff

File delete_cascade_option_with_tests_r9834.diff, 7.1 KB (added by msaelices, 6 years ago)

With unit tests and with delete_cascade for OneToOneRel

  • tests/modeltests/delete/models.py

     
    4040class F(DefaultRepr, models.Model):
    4141    e = models.ForeignKey(E, related_name='f_rel')
    4242
     43# Standard many to one and one to one relation, with no delete cascade behaviour
     44class G(DefaultRepr, models.Model):
     45    pass
    4346
     47class H(DefaultRepr, models.Model):
     48    g = models.ForeignKey(G, null=True, delete_cascade=False)
     49
     50class I(DefaultRepr, models.Model):
     51    h = models.OneToOneField(H, null=True, delete_cascade=False)
     52
    4453__test__ = {'API_TESTS': """
    4554### Tests for models A,B,C,D ###
    4655
     
    186195
    187196>>> f2.delete()
    188197
     198# Testing non delete cascade behaviour.
     199
     200>>> g1 = G()
     201>>> g1.save()
     202>>> h1 = H(g=g1)
     203>>> h1.save()
     204>>> g1.delete()
     205>>> H.objects.count()
     2061
     207
     208>>> i = I(h=h1)
     209>>> i.save()
     210>>> h1.delete()
     211>>> I.objects.count()
     2121
     213
    189214"""
    190215}
  • django/db/models/base.py

     
    1111import django.db.models.manager     # Imported to register signal handler.
    1212from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
    1313from django.db.models.fields import AutoField, FieldDoesNotExist
    14 from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
     14from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField, RelatedObject
    1515from django.db.models.query import delete_objects, Q, CollectedObjects
    1616from django.db.models.options import Options
    1717from django.db import connection, transaction, DatabaseError
     
    456456    def delete(self):
    457457        assert self._get_pk_val() is not None, "%s object can't be deleted because its %s attribute is set to None." % (self._meta.object_name, self._meta.pk.attname)
    458458
     459        # Clear all related objects with no delete cascade relation behaviour
     460        related_objects_updated = False
     461        for field in self._meta.get_all_fields():
     462            if isinstance(field, RelatedObject) and \
     463               isinstance(field.field.rel, ManyToOneRel) and \
     464               not field.field.rel.delete_cascade:
     465                if isinstance(field.field.rel, OneToOneRel):
     466                    try:
     467                        rel_obj = getattr(self, field.get_accessor_name())
     468                        setattr(rel_obj, field.field.name, None)
     469                        rel_obj.save()
     470                    except ObjectDoesNotExist:
     471                        pass # nothing to do
     472                else: # ManyToOneRel
     473                    related_manager = getattr(self, field.get_accessor_name())
     474                    related_manager.clear()
     475                related_objects_updated = True
     476        if related_objects_updated:
     477            # we ensure that refill related object cache
     478            del self._meta._related_objects_cache
     479
    459480        # Find all the objects than need to be deleted.
    460481        seen_objs = CollectedObjects()
    461482        self._collect_sub_objects(seen_objs)
  • django/db/models/options.py

     
    283283            raise FieldDoesNotExist('%s has no field named %r'
    284284                    % (self.object_name, name))
    285285
     286    def get_all_fields(self):
     287        """
     288        Returns a list of all fields that are possible for this model
     289        (including reverse relation names).
     290        """
     291        try:
     292            cache = self._name_map
     293        except AttributeError:
     294            cache = self.init_name_map()
     295        fields = [ r[0] for r in cache.values() ]
     296        return fields
     297
    286298    def get_all_field_names(self):
    287299        """
    288300        Returns a list of all field names that are possible for this model
  • django/db/models/fields/related.py

     
    583583        manager.add(*value)
    584584
    585585class ManyToOneRel(object):
    586     def __init__(self, to, field_name, related_name=None,
    587             limit_choices_to=None, lookup_overrides=None, parent_link=False):
     586    def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
     587            lookup_overrides=None, parent_link=False, delete_cascade=True):
    588588        try:
    589589            to._meta
    590590        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
     
    597597        self.lookup_overrides = lookup_overrides or {}
    598598        self.multiple = True
    599599        self.parent_link = parent_link
     600        self.delete_cascade = delete_cascade
    600601
    601602    def get_related_field(self):
    602603        """
     
    610611        return data[0]
    611612
    612613class OneToOneRel(ManyToOneRel):
    613     def __init__(self, to, field_name, related_name=None,
    614             limit_choices_to=None, lookup_overrides=None, parent_link=False):
     614    def __init__(self, to, field_name, related_name=None, limit_choices_to=None,
     615            lookup_overrides=None, parent_link=False, delete_cascade=True):
    615616        super(OneToOneRel, self).__init__(to, field_name,
    616617                related_name=related_name, limit_choices_to=limit_choices_to,
    617                 lookup_overrides=lookup_overrides, parent_link=parent_link)
     618                lookup_overrides=lookup_overrides, parent_link=parent_link,
     619                delete_cascade=delete_cascade)
    618620        self.multiple = False
    619621
    620622class ManyToManyRel(object):
     
    645647            related_name=kwargs.pop('related_name', None),
    646648            limit_choices_to=kwargs.pop('limit_choices_to', None),
    647649            lookup_overrides=kwargs.pop('lookup_overrides', None),
    648             parent_link=kwargs.pop('parent_link', False))
     650            parent_link=kwargs.pop('parent_link', False),
     651            delete_cascade=kwargs.pop('delete_cascade', True))
     652
    649653        Field.__init__(self, **kwargs)
    650654
    651655        self.db_index = True
     656        assert self.null or self.rel.delete_cascade, "%s cannot define a not null relation (null=False) without deleting cascade (delete_cascade=False)" % self.__class__.__name__
    652657
    653658    def get_attname(self):
    654659        return '%s_id' % self.name
  • django/contrib/admin/util.py

     
    7171        if related.opts in opts_seen:
    7272            continue
    7373        opts_seen.append(related.opts)
     74        if not related.field.rel.delete_cascade:
     75            # Avoid deleting objects without a delete cascade relation
     76            continue
    7477        rel_opts_name = related.get_accessor_name()
     78
    7579        if isinstance(related.field.rel, models.OneToOneRel):
    7680            try:
    7781                sub_obj = getattr(obj, rel_opts_name)
Back to Top