Ticket #7539: 7539.on_delete.r12009.diff

File 7539.on_delete.r12009.diff, 45.8 KB (added by Johannes Dollinger, 14 years ago)
  • tests/modeltests/invalid_models/models.py

     
    181181class UniqueM2M(models.Model):
    182182    """ Model to test for unique ManyToManyFields, which are invalid. """
    183183    unique_people = models.ManyToManyField( Person, unique=True )
     184   
     185class InvalidSetNull(models.Model):
     186    fk = models.ForeignKey('self', on_delete=models.SET_NULL)
     187   
     188class InvalidSetDefault(models.Model):
     189    fk = models.ForeignKey('self', on_delete=models.SET_DEFAULT)
    184190
    185191
    186192model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute.
     
    279285invalid_models.abstractrelationmodel: 'fk1' has a relation with model AbstractModel, which has either not been installed or is abstract.
    280286invalid_models.abstractrelationmodel: 'fk2' has an m2m relation with model AbstractModel, which has either not been installed or is abstract.
    281287invalid_models.uniquem2m: ManyToManyFields cannot be unique.  Remove the unique argument on 'unique_people'.
     288invalid_models.invalidsetnull: 'fk' specifies on_delete=SET_NULL, but cannot be null.
     289invalid_models.invalidsetdefault: 'fk' specifies on_delete=SET_DEFAULT, but has no default value.
    282290"""
  • tests/modeltests/on_delete/__init__.py

     
     1#
     2 No newline at end of file
  • tests/modeltests/on_delete/models.py

     
     1from django.test import TestCase
     2from django.db import models, IntegrityError
     3
     4class R(models.Model):
     5    is_default = models.BooleanField(default=False)
     6
     7    def __str__(self):
     8        return "%s" % self.pk
     9
     10get_default_r = lambda: R.objects.get_or_create(is_default=True)[0]
     11   
     12class S(models.Model):
     13    r = models.ForeignKey(R)
     14   
     15class T(models.Model):
     16    s = models.ForeignKey(S)
     17
     18class U(models.Model):
     19    t = models.ForeignKey(T)
     20
     21
     22class A(models.Model):
     23    name = models.CharField(max_length=10)   
     24
     25    auto = models.ForeignKey(R, related_name="auto_set")
     26    auto_nullable = models.ForeignKey(R, null=True, related_name='auto_nullable_set')
     27    setnull = models.ForeignKey(R, on_delete=models.SET_NULL, null=True, related_name='setnull_set')
     28    setdefault = models.ForeignKey(R, on_delete=models.SET_DEFAULT, default=get_default_r, related_name='setdefault_set')
     29    setdefault_none = models.ForeignKey(R, on_delete=models.SET_DEFAULT, default=None, null=True, related_name='setnull_nullable_set')
     30    cascade = models.ForeignKey(R, on_delete=models.CASCADE, related_name='cascade_set')
     31    cascade_nullable = models.ForeignKey(R, on_delete=models.CASCADE, null=True, related_name='cascade_nullable_set')
     32    protect = models.ForeignKey(R, on_delete=models.PROTECT, null=True)
     33    donothing = models.ForeignKey(R, on_delete=models.DO_NOTHING, null=True, related_name='donothing_set')
     34   
     35def create_a(name):
     36    a = A(name=name)
     37    for name in ('auto', 'auto_nullable', 'setnull', 'setdefault', 'setdefault_none', 'cascade', 'cascade_nullable', 'protect', 'donothing'):
     38        r = R.objects.create()
     39        setattr(a, name, r)
     40    a.save()
     41    return a
     42   
     43class M(models.Model):
     44    m2m = models.ManyToManyField(R, related_name="m_set")   
     45    m2m_through = models.ManyToManyField(R, through="MR", related_name="m_through_set")
     46    m2m_through_null = models.ManyToManyField(R, through="MRNull", related_name="m_through_null_set")
     47   
     48class MR(models.Model):
     49    m = models.ForeignKey(M)
     50    r = models.ForeignKey(R)
     51
     52class MRNull(models.Model):
     53    m = models.ForeignKey(M)
     54    r = models.ForeignKey(R, null=True, on_delete=models.SET_NULL)
     55
     56class OnDeleteTests(TestCase):
     57    def test_basics(self):
     58        DEFAULT = get_default_r()
     59       
     60        a = create_a('auto')
     61        a.auto.delete()
     62        self.failIf(A.objects.filter(name='auto').exists())
     63       
     64        a = create_a('auto_nullable')
     65        a.auto_nullable.delete()
     66        self.failIf(A.objects.filter(name='auto_nullable').exists())
     67       
     68        a = create_a('setnull')
     69        a.setnull.delete()
     70        a = A.objects.get(pk=a.pk)
     71        self.failUnlessEqual(None, a.setnull)
     72       
     73        a = create_a('setdefault')
     74        a.setdefault.delete()
     75        a = A.objects.get(pk=a.pk)
     76        self.failUnlessEqual(DEFAULT, a.setdefault)
     77       
     78        a = create_a('setdefault_none')
     79        a.setdefault_none.delete()
     80        a = A.objects.get(pk=a.pk)
     81        self.failUnlessEqual(None, a.setdefault_none)
     82       
     83        a = create_a('cascade')
     84        a.cascade.delete()
     85        self.failIf(A.objects.filter(name='cascade').exists())
     86       
     87        a = create_a('cascade_nullable')
     88        a.cascade_nullable.delete()
     89        self.failIf(A.objects.filter(name='cascade_nullable').exists())
     90       
     91        a = create_a('protect')
     92        self.assertRaises(IntegrityError, a.protect.delete)
     93       
     94        # Testing DO_NOTHING is a bit harder: It would raise IntegrityError for a normal model,
     95        # so we connect to pre_delete and set the fk to a known value.
     96        replacement_r = R.objects.create()
     97        def check_do_nothing(sender, **kwargs):
     98            obj = kwargs['instance']
     99            obj.donothing_set.update(donothing=replacement_r)
     100        models.signals.pre_delete.connect(check_do_nothing)
     101        a = create_a('do_nothing')
     102        a.donothing.delete()
     103        a = A.objects.get(pk=a.pk)
     104        self.failUnlessEqual(replacement_r, a.donothing)
     105        models.signals.pre_delete.disconnect(check_do_nothing)       
     106       
     107        A.objects.all().update(protect=None, donothing=None)
     108        R.objects.all().delete()
     109        self.failIf(A.objects.exists())
     110       
     111    def test_m2m(self):
     112        m = M.objects.create()
     113        r = R.objects.create()
     114        MR.objects.create(m=m, r=r)
     115        r.delete()
     116        self.failIf(MR.objects.exists())
     117       
     118        r = R.objects.create()
     119        MR.objects.create(m=m, r=r)
     120        m.delete()
     121        self.failIf(MR.objects.exists())
     122       
     123        m = M.objects.create()
     124        r = R.objects.create()
     125        m.m2m.add(r)
     126        r.delete()
     127        through = M._meta.get_field('m2m').rel.through
     128        self.failIf(through.objects.exists())
     129       
     130        r = R.objects.create()
     131        m.m2m.add(r)
     132        m.delete()
     133        self.failIf(through.objects.exists())
     134       
     135        m = M.objects.create()
     136        r = R.objects.create()
     137        MRNull.objects.create(m=m, r=r)       
     138        r.delete()
     139        self.failIf(not MRNull.objects.exists())
     140        self.failIf(m.m2m_through_null.exists())
     141       
     142   
     143    def assert_num_queries(self, num, func, *args, **kwargs):
     144        from django.conf import settings
     145        from django.db import connection
     146        old_debug = settings.DEBUG
     147        settings.DEBUG = True
     148        query_count = len(connection.queries)
     149        func(*args, **kwargs)
     150        self.failUnlessEqual(num, len(connection.queries) - query_count)
     151        connection.queries = connection.queries[:query_count]
     152        settings.DEBUG = old_debug
     153   
     154    def test_bulk(self):
     155        from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE
     156        s = S.objects.create(r=R.objects.create())
     157        for i in xrange(2*GET_ITERATOR_CHUNK_SIZE):
     158            T.objects.create(s=s)
     159        #   1 (select related `T` instances)
     160        # + 1 (select related `U` instances)
     161        # + 2 (delete `T` instances in batches)
     162        # + 1 (delete `s`)
     163        self.assert_num_queries(5, s.delete)
     164        self.failIf(S.objects.exists())
     165       
     166    def test_instance_update(self):
     167        deleted = []
     168        related_setnull_sets = []
     169        def pre_delete(sender, **kwargs):
     170            obj = kwargs['instance']
     171            deleted.append(obj)
     172            if isinstance(obj, R):
     173                related_setnull_sets.append(list(a.pk for a in obj.setnull_set.all()))
     174
     175        models.signals.pre_delete.connect(pre_delete)
     176        a = create_a('update_setnull')
     177        a.setnull.delete()
     178       
     179        a = create_a('update_cascade')
     180        a.cascade.delete()
     181       
     182        for obj in deleted:
     183            self.failUnlessEqual(None, obj.pk)
     184           
     185        for pk_list in related_setnull_sets:
     186            for a in A.objects.filter(id__in=pk_list):
     187                self.failUnlessEqual(None, a.setnull)
     188       
     189        models.signals.pre_delete.disconnect(pre_delete)
     190
     191    def test_deletion_order(self):
     192        pre_delete_order = []
     193        post_delete_order = []
     194
     195        def log_post_delete(sender, **kwargs):
     196            pre_delete_order.append((sender, kwargs['instance'].pk))
     197
     198        def log_pre_delete(sender, **kwargs):
     199            post_delete_order.append((sender, kwargs['instance'].pk))
     200       
     201        models.signals.post_delete.connect(log_post_delete)
     202        models.signals.pre_delete.connect(log_pre_delete)
     203       
     204        r = R.objects.create(pk=1)
     205        s1 = S.objects.create(pk=1, r=r)
     206        s2 = S.objects.create(pk=2, r=r)
     207        t1 = T.objects.create(pk=1, s=s1)
     208        t2 = T.objects.create(pk=2, s=s2)
     209        r.delete()
     210        self.failUnlessEqual(pre_delete_order, [(T, 2), (T, 1), (S, 2), (S, 1), (R, 1)])
     211        self.failUnlessEqual(post_delete_order, [(T, 1), (T, 2), (S, 1), (S, 2), (R, 1)])
     212       
     213        models.signals.post_delete.disconnect(log_post_delete)
     214        models.signals.post_delete.disconnect(log_pre_delete)
     215       
  • tests/modeltests/delete/models.py

     
    4444__test__ = {'API_TESTS': """
    4545### Tests for models A,B,C,D ###
    4646
    47 ## First, test the CollectedObjects data structure directly
    48 
    49 >>> from django.db.models.query import CollectedObjects
    50 
    51 >>> g = CollectedObjects()
    52 >>> g.add("key1", 1, "item1", None)
    53 False
    54 >>> g["key1"]
    55 {1: 'item1'}
    56 >>> g.add("key2", 1, "item1", "key1")
    57 False
    58 >>> g.add("key2", 2, "item2", "key1")
    59 False
    60 >>> g["key2"]
    61 {1: 'item1', 2: 'item2'}
    62 >>> g.add("key3", 1, "item1", "key1")
    63 False
    64 >>> g.add("key3", 1, "item1", "key2")
    65 True
    66 >>> g.ordered_keys()
    67 ['key3', 'key2', 'key1']
    68 
    69 >>> g.add("key2", 1, "item1", "key3")
    70 True
    71 >>> g.ordered_keys()
    72 Traceback (most recent call last):
    73     ...
    74 CyclicDependency: There is a cyclic dependency of items to be processed.
    75 
    76 
    77 ## Second, test the usage of CollectedObjects by Model.delete()
    78 
    7947# Due to the way that transactions work in the test harness,
    8048# doing m.delete() here can work but fail in a real situation,
    8149# since it may delete all objects, but not in the right order.
     
    11179>>> c1.save()
    11280>>> d1 = D(c=c1, a=a1)
    11381>>> d1.save()
    114 
    115 >>> o = CollectedObjects()
    116 >>> a1._collect_sub_objects(o)
    117 >>> o.keys()
    118 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]
    11982>>> a1.delete()
    12083
    12184# Same again with a known bad order
     
    13093>>> c2.save()
    13194>>> d2 = D(c=c2, a=a2)
    13295>>> d2.save()
    133 
    134 >>> o = CollectedObjects()
    135 >>> a2._collect_sub_objects(o)
    136 >>> o.keys()
    137 [<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]
    13896>>> a2.delete()
    13997
    14098### Tests for models E,F - nullable related fields ###
    14199
    142 ## First, test the CollectedObjects data structure directly
    143 
    144 >>> g = CollectedObjects()
    145 >>> g.add("key1", 1, "item1", None)
    146 False
    147 >>> g.add("key2", 1, "item1", "key1", nullable=True)
    148 False
    149 >>> g.add("key1", 1, "item1", "key2")
    150 True
    151 >>> g.ordered_keys()
    152 ['key1', 'key2']
    153 
    154 ## Second, test the usage of CollectedObjects by Model.delete()
    155 
    156100>>> e1 = E()
    157101>>> e1.save()
    158102>>> f1 = F(e=e1)
     
    163107# Since E.f is nullable, we should delete F first (after nulling out
    164108# the E.f field), then E.
    165109
    166 >>> o = CollectedObjects()
    167 >>> e1._collect_sub_objects(o)
    168 >>> o.keys()
    169 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]
    170 
    171110# temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first
    172111>>> import django.db.models.sql
    173112>>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery):
    174 ...     def clear_related(self, related_field, pk_list, using):
    175 ...         print "CLEARING FIELD",related_field.name
    176 ...         return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list, using)
     113...     def update_batch(self, pk_list, values, using):
     114...         if values == {'f': None}:
     115...             print "CLEARING FIELD f"
     116...         return super(LoggingUpdateQuery, self).update_batch(pk_list, values, using)
     117
    177118>>> original_class = django.db.models.sql.UpdateQuery
    178119>>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery
    179120>>> e1.delete()
     
    187128>>> e2.save()
    188129
    189130# Same deal as before, though we are starting from the other object.
    190 
    191 >>> o = CollectedObjects()
    192 >>> f2._collect_sub_objects(o)
    193 >>> o.keys()
    194 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]
    195 
    196131>>> f2.delete()
    197132CLEARING FIELD f
    198133
  • django/db/models/sql/subqueries.py

     
    2626        self.where = where
    2727        self.get_compiler(using).execute_sql(None)
    2828
    29     def delete_batch_related(self, pk_list, using):
     29    def delete_generic_relation_hack(self, pk_list, using):
    3030        """
    31         Set up and execute delete queries for all the objects related to the
    32         primary key values in pk_list. To delete the objects themselves, use
    33         the delete_batch() method.
    34 
    35         More than one physical query may be executed if there are a
    36         lot of values in pk_list.
     31        Delete objects related to self.model through a GenericRelation.
     32        This should be handled by a custom `on_delete` handler in django.contrib.contentttypes.
    3733        """
    3834        from django.contrib.contenttypes import generic
    3935        cls = self.model
    40         for related in cls._meta.get_all_related_many_to_many_objects():
    41             if not isinstance(related.field, generic.GenericRelation):
    42                 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    43                     where = self.where_class()
    44                     where.add((Constraint(None,
    45                             related.field.m2m_reverse_name(), related.field),
    46                             'in',
    47                             pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
    48                             AND)
    49                     self.do_query(related.field.m2m_db_table(), where, using=using)
    50 
    5136        for f in cls._meta.many_to_many:
     37            if not isinstance(f, generic.GenericRelation):
     38                continue
    5239            w1 = self.where_class()
    53             if isinstance(f, generic.GenericRelation):
    54                 from django.contrib.contenttypes.models import ContentType
    55                 field = f.rel.to._meta.get_field(f.content_type_field_name)
    56                 w1.add((Constraint(None, field.column, field), 'exact',
    57                         ContentType.objects.get_for_model(cls).id), AND)
     40            from django.contrib.contenttypes.models import ContentType
     41            field = f.rel.to._meta.get_field(f.content_type_field_name)
     42            w1.add((Constraint(None, field.column, field), 'exact', ContentType.objects.get_for_model(cls).id), AND)
    5843            for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    5944                where = self.where_class()
    6045                where.add((Constraint(None, f.m2m_column_name(), f), 'in',
    6146                        pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
    6247                        AND)
    63                 if w1:
    64                     where.add(w1, AND)
     48                where.add(w1, AND)
    6549                self.do_query(f.m2m_db_table(), where, using=using)
    6650
    67     def delete_batch(self, pk_list, using):
     51    def delete_batch(self, pk_list, using, field=None):
    6852        """
    69         Set up and execute delete queries for all the objects in pk_list. This
    70         should be called after delete_batch_related(), if necessary.
     53        Set up and execute delete queries for all the objects in pk_list.
    7154
    7255        More than one physical query may be executed if there are a
    7356        lot of values in pk_list.
    7457        """
     58        if not field:
     59            field = self.model._meta.pk
    7560        for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    7661            where = self.where_class()
    77             field = self.model._meta.pk
    7862            where.add((Constraint(None, field.column, field), 'in',
    7963                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
    8064            self.do_query(self.model._meta.db_table, where, using=using)
     
    10690                related_updates=self.related_updates.copy(), **kwargs)
    10791
    10892
    109     def clear_related(self, related_field, pk_list, using):
    110         """
    111         Set up and execute an update query that clears related entries for the
    112         keys in pk_list.
    113 
    114         This is used by the QuerySet.delete_objects() method.
    115         """
     93    def update_batch(self, pk_list, values, using):
     94        pk_field = self.model._meta.pk
     95        self.add_update_values(values)
    11696        for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    11797            self.where = self.where_class()
    118             f = self.model._meta.pk
    119             self.where.add((Constraint(None, f.column, f), 'in',
     98            self.where.add((Constraint(None, pk_field.column, pk_field), 'in',
    12099                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
    121100                    AND)
    122             self.values = [(related_field, None, None)]
    123101            self.get_compiler(using).execute_sql(None)
    124102
    125103    def add_update_values(self, values):
  • django/db/models/base.py

     
    66from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
    77from django.db.models.fields import AutoField, FieldDoesNotExist
    88from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField
    9 from django.db.models.query import delete_objects, Q
    10 from django.db.models.query_utils import CollectedObjects, DeferredAttribute
     9from django.db.models.query import Q
     10from django.db.models.query_utils import DeferredAttribute
     11from django.db.models.deletion import Collector
    1112from django.db.models.options import Options
    1213from django.db import connections, transaction, DatabaseError, DEFAULT_DB_ALIAS
    1314from django.db.models import signals
     
    534535
    535536    save_base.alters_data = True
    536537
    537     def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
    538         """
    539         Recursively populates seen_objs with all objects related to this
    540         object.
    541 
    542         When done, seen_objs.items() will be in the format:
    543             [(model_class, {pk_val: obj, pk_val: obj, ...}),
    544              (model_class, {pk_val: obj, pk_val: obj, ...}), ...]
    545         """
    546         pk_val = self._get_pk_val()
    547         if seen_objs.add(self.__class__, pk_val, self, parent, nullable):
    548             return
    549 
    550         for related in self._meta.get_all_related_objects():
    551             rel_opts_name = related.get_accessor_name()
    552             if isinstance(related.field.rel, OneToOneRel):
    553                 try:
    554                     sub_obj = getattr(self, rel_opts_name)
    555                 except ObjectDoesNotExist:
    556                     pass
    557                 else:
    558                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
    559             else:
    560                 # To make sure we can access all elements, we can't use the
    561                 # normal manager on the related object. So we work directly
    562                 # with the descriptor object.
    563                 for cls in self.__class__.mro():
    564                     if rel_opts_name in cls.__dict__:
    565                         rel_descriptor = cls.__dict__[rel_opts_name]
    566                         break
    567                 else:
    568                     # in the case of a hidden fkey just skip it, it'll get
    569                     # processed as an m2m
    570                     if not related.field.rel.is_hidden():
    571                         raise AssertionError("Should never get here.")
    572                     else:
    573                         continue
    574                 delete_qs = rel_descriptor.delete_manager(self).all()
    575                 for sub_obj in delete_qs:
    576                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
    577 
    578         # Handle any ancestors (for the model-inheritance case). We do this by
    579         # traversing to the most remote parent classes -- those with no parents
    580         # themselves -- and then adding those instances to the collection. That
    581         # will include all the child instances down to "self".
    582         parent_stack = [p for p in self._meta.parents.values() if p is not None]
    583         while parent_stack:
    584             link = parent_stack.pop()
    585             parent_obj = getattr(self, link.name)
    586             if parent_obj._meta.parents:
    587                 parent_stack.extend(parent_obj._meta.parents.values())
    588                 continue
    589             # At this point, parent_obj is base class (no ancestor models). So
    590             # delete it and all its descendents.
    591             parent_obj._collect_sub_objects(seen_objs)
    592 
    593538    def delete(self, using=None):
    594         using = using or self._state.db or DEFAULT_DB_ALIAS
     539        using = using or self._state.db
    595540        connection = connections[using]
    596541        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)
    597542
    598         # Find all the objects than need to be deleted.
    599         seen_objs = CollectedObjects()
    600         self._collect_sub_objects(seen_objs)
     543        collector = Collector()
     544        collector.collect([self])
     545        collector.delete(using=using)
    601546
    602         # Actually delete the objects.
    603         delete_objects(seen_objs, using)
    604 
    605547    delete.alters_data = True
    606548
    607549    def _get_FIELD_display(self, field):
  • django/db/models/options.py

     
    376376                    cache[obj] = parent
    377377                else:
    378378                    cache[obj] = model
    379         for klass in get_models():
     379        for klass in get_models(include_auto_created=True):
    380380            for f in klass._meta.local_fields:
    381381                if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
    382382                    cache[RelatedObject(f.rel.to, klass, f)] = None
  • django/db/models/fields/related.py

     
    66from django.db.models.related import RelatedObject
    77from django.db.models.query import QuerySet
    88from django.db.models.query_utils import QueryWrapper
     9from django.db.models.deletion import CASCADE
    910from django.utils.encoding import smart_unicode
    1011from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
    1112from django.utils.functional import curry
     
    644645        manager.add(*value)
    645646
    646647class ManyToOneRel(object):
    647     def __init__(self, to, field_name, related_name=None,
    648             limit_choices_to=None, lookup_overrides=None, parent_link=False):
     648    def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None):
    649649        try:
    650650            to._meta
    651651        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
     
    655655        if limit_choices_to is None:
    656656            limit_choices_to = {}
    657657        self.limit_choices_to = limit_choices_to
    658         self.lookup_overrides = lookup_overrides or {}
    659658        self.multiple = True
    660659        self.parent_link = parent_link
     660        self.on_delete = on_delete
    661661
    662662    def is_hidden(self):
    663663        "Should the related object be hidden?"
     
    675675        return data[0]
    676676
    677677class OneToOneRel(ManyToOneRel):
    678     def __init__(self, to, field_name, related_name=None,
    679             limit_choices_to=None, lookup_overrides=None, parent_link=False):
     678    def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None):
    680679        super(OneToOneRel, self).__init__(to, field_name,
    681680                related_name=related_name, limit_choices_to=limit_choices_to,
    682                 lookup_overrides=lookup_overrides, parent_link=parent_link)
     681                parent_link=parent_link, on_delete=on_delete
     682        )
    683683        self.multiple = False
    684684
    685685class ManyToManyRel(object):
     
    725725        kwargs['rel'] = rel_class(to, to_field,
    726726            related_name=kwargs.pop('related_name', None),
    727727            limit_choices_to=kwargs.pop('limit_choices_to', None),
    728             lookup_overrides=kwargs.pop('lookup_overrides', None),
    729             parent_link=kwargs.pop('parent_link', False))
     728            parent_link=kwargs.pop('parent_link', False),
     729            on_delete=kwargs.pop('on_delete', CASCADE),
     730        )
    730731        Field.__init__(self, **kwargs)
    731732
    732733        self.db_index = True
  • django/db/models/__init__.py

     
    1111from django.db.models.fields.subclassing import SubfieldBase
    1212from django.db.models.fields.files import FileField, ImageField
    1313from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel
     14from django.db.models.deletion import CASCADE, PROTECT, SET, SET_NULL, SET_DEFAULT, DO_NOTHING
    1415from django.db.models import signals
    1516
    1617# Admin stages.
  • django/db/models/deletion.py

     
     1from django.utils.datastructures import SortedDict
     2from django.utils.functional import wraps
     3from django.db import connections, transaction, IntegrityError, DEFAULT_DB_ALIAS
     4from django.db.models import signals, sql
     5from django.db.models.sql.constants import GET_ITERATOR_CHUNK_SIZE
     6
     7def CASCADE(collector, field, sub_objs):
     8    collector.collect(sub_objs, field.rel.to, field.null)
     9    if field.null:
     10        # FIXME: there should be a connection feature indicating whether nullable related fields should be nulled out before deletion
     11        collector.add_field_update(field, None, sub_objs)
     12
     13def PROTECT(collector, field, sub_objs):
     14    msg = "Cannot delete some instances of model '%s' because they are referenced through a protected foreign key: '%s.%s'" % (
     15        field.rel.to.__name__, sub_objs[0].__class__.__name__, field.name
     16    )
     17    raise IntegrityError(msg)
     18
     19def SET(value):
     20    def set_on_delete(collector, field, sub_objs):
     21        collector.add_field_update(field, value, sub_objs)
     22    return set_on_delete
     23
     24def SET_NULL(collector, field, sub_objs):
     25    collector.add_field_update(field, None, sub_objs)
     26
     27def SET_DEFAULT(collector, field, sub_objs):
     28    collector.add_field_update(field, field.get_default(), sub_objs)
     29
     30def DO_NOTHING(collector, field, sub_objs):
     31    pass
     32
     33def force_managed(func):
     34    @wraps(func)
     35    def decorated(*args, **kwargs):
     36        if not transaction.is_managed():
     37            transaction.enter_transaction_management()
     38            forced_managed = True
     39        else:
     40            forced_managed = False
     41        try:                   
     42            func(*args, **kwargs)
     43            if forced_managed:
     44                transaction.commit()
     45            else:
     46                transaction.commit_unless_managed()
     47        finally:
     48            if forced_managed:
     49                transaction.leave_transaction_management()
     50    return decorated
     51
     52class Collector(object):
     53    def __init__(self, previously_collected=None):
     54        self.data = {} # {model: [instances]}
     55        self.batches = {} # {model: {field: set([instances])}}
     56        self.field_updates = {} # {model: {(field, value): set([instances])}}       
     57        self.dependencies = {} # {model: set([models])}
     58        if previously_collected:
     59            self.blocked = set(previously_collected)
     60            self.blocked.update(previously_collected.blocked)
     61        else:
     62            self.blocked = set()
     63
     64    def add(self, objs, source=None, nullable=False):
     65        """
     66        Adds 'objs' to the collection of objects to be deleted.
     67        If the call is the result of a cascade, 'source' should be the model that caused it
     68        and 'nullable' should be set to True, if the relation can be null.
     69       
     70        Returns a list of all objects that were not already collected.
     71        """
     72        if not objs:
     73            return []
     74        new_objs = []
     75        model = objs[0].__class__
     76        instances = self.data.setdefault(model, [])
     77        for obj in objs:
     78            if obj not in instances and obj not in self.blocked:
     79                new_objs.append(obj)
     80        instances.extend(new_objs)
     81        # Nullable relationships can be ignored -- they are nulled out before
     82        # deleting, and therefore do not affect the order in which objects
     83        # have to be deleted.
     84        if new_objs and source is not None and not nullable:
     85            self.dependencies.setdefault(source, set()).add(model)
     86        return new_objs
     87       
     88    def add_batch(self, model, field, objs):
     89        """
     90        Schedules a batch delete. Every instance of 'model' that related to an instance of 'obj' through 'field' will be deleted.
     91        """
     92        self.batches.setdefault(model, {}).setdefault(field, set()).update(objs)
     93       
     94    def add_field_update(self, field, value, objs):
     95        """
     96        Schedules a field update. 'objs' must be a homogenous iterable collection of model instances (e.g. a QuerySet).
     97        """
     98        objs = list(objs)
     99        if not objs:
     100            return
     101        model = objs[0].__class__
     102        self.field_updates.setdefault(model, {}).setdefault((field, value), set()).update(objs)
     103       
     104    def collect(self, objs, source=None, nullable=False, collect_related=True):
     105        """
     106        Adds 'objs' to the collection of objects to be deleted as well as all parent instances.
     107        'objs' must be a homogenous iterable collection of model instances (e.g. a QuerySet).
     108        If 'collect_related' is True, related objects will be handled by their respective on_delete handler.
     109       
     110        If the call is the result of a cascade, 'source' should be the model that caused it
     111        and 'nullable' should be set to True, if the relation can be null.
     112        """
     113        new_objs = self.add(objs, source, nullable)
     114        if not new_objs:
     115            return
     116        model = new_objs[0].__class__
     117       
     118        # Recusively collect parent models, but not their related objects.
     119        for parent, ptr in model._meta.parents.items():
     120            if ptr:
     121                parent_objs = [getattr(obj, ptr.name) for obj in new_objs]
     122                self.collect(parent_objs, model, collect_related=False)
     123
     124        if collect_related:
     125            for related in model._meta.get_all_related_objects():
     126                if related.model._meta.auto_created:
     127                    self.add_batch(related.model, related.field, new_objs)
     128                else:
     129                    sub_objs = related.model._base_manager.filter(**{"%s__in" % related.field.name: new_objs})
     130                    if not sub_objs:
     131                        continue
     132                    related.field.rel.on_delete(self, related.field, sub_objs)
     133
     134    def instances_with_model(self):
     135        for model, instances in self.data.iteritems():
     136            for obj in instances:
     137                yield model, obj
     138               
     139    def __iter__(self):
     140        for model, obj in self.instances_with_model():
     141            yield obj
     142
     143    def __nonzero__(self):
     144        return bool(self.data)
     145
     146    def sort(self):
     147        sorted_models = []
     148        models = self.data.keys()
     149        while len(sorted_models) < len(models):
     150            found = False
     151            for model in models:
     152                if model in sorted_models:
     153                    continue
     154                dependencies = self.dependencies.get(model)
     155                if not dependencies or not dependencies.difference(sorted_models):
     156                    sorted_models.append(model)
     157                    found = True
     158            if not found:
     159                return
     160        self.data = SortedDict([(model, self.data[model]) for model in sorted_models])
     161   
     162    @force_managed
     163    def delete(self, using=None):
     164        using = using or DEFAULT_DB_ALIAS
     165        # sort instance collections
     166        for instances in self.data.itervalues():
     167            instances.sort(key=lambda obj: obj.pk)
     168
     169        # if possible, bring the models in an order suitable for databases that don't support transactions
     170        # or cannot defer contraint checks until the end of a transaction.
     171        self.sort()
     172       
     173        # send pre_delete signals
     174        for model, obj in self.instances_with_model():
     175            if not model._meta.auto_created:
     176                signals.pre_delete.send(sender=model, instance=obj)
     177
     178        # update fields
     179        for model, instances_for_fieldvalues in self.field_updates.iteritems():
     180            query = sql.UpdateQuery(model)
     181            for (field, value), instances in instances_for_fieldvalues.iteritems():
     182                query.update_batch([obj.pk for obj in instances], {field.name: value}, using)
     183
     184        # reverse instance collections
     185        for instances in self.data.itervalues():
     186            instances.reverse()
     187
     188        # delete batches
     189        for model, batches in self.batches.iteritems():
     190            query = sql.DeleteQuery(model)
     191            for field, instances in batches.iteritems():
     192                query.delete_batch([obj.pk for obj in instances], using, field)
     193
     194        # delete instances
     195        for model, instances in self.data.iteritems():
     196            query = sql.DeleteQuery(model)
     197            pk_list = [obj.pk for obj in instances]
     198            query.delete_generic_relation_hack(pk_list, using)
     199            query.delete_batch(pk_list, using)
     200       
     201        # send post_delete signals
     202        for model, obj in self.instances_with_model():
     203            if not model._meta.auto_created:
     204                signals.post_delete.send(sender=model, instance=obj)
     205       
     206        # update collected instances
     207        for model, instances_for_fieldvalues in self.field_updates.iteritems():
     208            for (field, value), instances in instances_for_fieldvalues.iteritems():
     209                for obj in instances:
     210                    setattr(obj, field.attname, value)
     211        for model, instances in self.data.iteritems():
     212            for instance in instances:
     213                setattr(instance, model._meta.pk.attname, None)
  • django/db/models/query.py

     
    77from django.db import connections, transaction, IntegrityError, DEFAULT_DB_ALIAS
    88from django.db.models.aggregates import Aggregate
    99from django.db.models.fields import DateField
    10 from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory, InvalidQuery
     10from django.db.models.query_utils import Q, select_related_descend, deferred_class_factory, InvalidQuery
     11from django.db.models.deletion import Collector
    1112from django.db.models import signals, sql
    1213from django.utils.copycompat import deepcopy
    1314
     
    419420
    420421        # Delete objects in chunks to prevent the list of related objects from
    421422        # becoming too long.
    422         seen_objs = None
     423        collector = None
    423424        while 1:
    424425            # Collect all the objects to be deleted in this chunk, and all the
    425426            # objects that are related to the objects that are to be deleted.
    426             seen_objs = CollectedObjects(seen_objs)
    427             for object in del_query[:CHUNK_SIZE]:
    428                 object._collect_sub_objects(seen_objs)
    429 
    430             if not seen_objs:
     427            collector = Collector(collector)
     428            collector.collect(del_query[:CHUNK_SIZE])
     429            if not collector:
    431430                break
    432             delete_objects(seen_objs, del_query.db)
     431            collector.delete(using=del_query.db)
    433432
    434433        # Clear the result cache, in case this QuerySet gets reused.
    435434        self._result_cache = None
     
    10741073                setattr(obj, f.get_cache_name(), rel_obj)
    10751074    return obj, index_end
    10761075
    1077 def delete_objects(seen_objs, using):
    1078     """
    1079     Iterate through a list of seen classes, and remove any instances that are
    1080     referred to.
    1081     """
    1082     connection = connections[using]
    1083     if not transaction.is_managed(using=using):
    1084         transaction.enter_transaction_management(using=using)
    1085         forced_managed = True
    1086     else:
    1087         forced_managed = False
    1088     try:
    1089         ordered_classes = seen_objs.keys()
    1090     except CyclicDependency:
    1091         # If there is a cyclic dependency, we cannot in general delete the
    1092         # objects.  However, if an appropriate transaction is set up, or if the
    1093         # database is lax enough, it will succeed. So for now, we go ahead and
    1094         # try anyway.
    1095         ordered_classes = seen_objs.unordered_keys()
    10961076
    1097     obj_pairs = {}
    1098     try:
    1099         for cls in ordered_classes:
    1100             items = seen_objs[cls].items()
    1101             items.sort()
    1102             obj_pairs[cls] = items
    1103 
    1104             # Pre-notify all instances to be deleted.
    1105             for pk_val, instance in items:
    1106                 if not cls._meta.auto_created:
    1107                     signals.pre_delete.send(sender=cls, instance=instance)
    1108 
    1109             pk_list = [pk for pk,instance in items]
    1110             del_query = sql.DeleteQuery(cls)
    1111             del_query.delete_batch_related(pk_list, using=using)
    1112 
    1113             update_query = sql.UpdateQuery(cls)
    1114             for field, model in cls._meta.get_fields_with_model():
    1115                 if (field.rel and field.null and field.rel.to in seen_objs and
    1116                         filter(lambda f: f.column == field.rel.get_related_field().column,
    1117                         field.rel.to._meta.fields)):
    1118                     if model:
    1119                         sql.UpdateQuery(model).clear_related(field, pk_list, using=using)
    1120                     else:
    1121                         update_query.clear_related(field, pk_list, using=using)
    1122 
    1123         # Now delete the actual data.
    1124         for cls in ordered_classes:
    1125             items = obj_pairs[cls]
    1126             items.reverse()
    1127 
    1128             pk_list = [pk for pk,instance in items]
    1129             del_query = sql.DeleteQuery(cls)
    1130             del_query.delete_batch(pk_list, using=using)
    1131 
    1132             # Last cleanup; set NULLs where there once was a reference to the
    1133             # object, NULL the primary key of the found objects, and perform
    1134             # post-notification.
    1135             for pk_val, instance in items:
    1136                 for field in cls._meta.fields:
    1137                     if field.rel and field.null and field.rel.to in seen_objs:
    1138                         setattr(instance, field.attname, None)
    1139 
    1140                 if not cls._meta.auto_created:
    1141                     signals.post_delete.send(sender=cls, instance=instance)
    1142                 setattr(instance, cls._meta.pk.attname, None)
    1143 
    1144         if forced_managed:
    1145             transaction.commit(using=using)
    1146         else:
    1147             transaction.commit_unless_managed(using=using)
    1148     finally:
    1149         if forced_managed:
    1150             transaction.leave_transaction_management(using=using)
    1151 
    11521077class RawQuerySet(object):
    11531078    """
    11541079    Provides an iterator which converts the results of raw SQL queries into
  • django/db/models/query_utils.py

     
    1010from django.utils.copycompat import deepcopy
    1111
    1212from django.utils import tree
    13 from django.utils.datastructures import SortedDict
    1413
    1514
    16 class CyclicDependency(Exception):
    17     """
    18     An error when dealing with a collection of objects that have a cyclic
    19     dependency, i.e. when deleting multiple objects.
    20     """
    21     pass
    22 
    2315class InvalidQuery(Exception):
    2416    """
    2517    The query passed to raw isn't a safe query to use with raw.
    2618    """
    2719    pass
    2820
    29 
    30 class CollectedObjects(object):
    31     """
    32     A container that stores keys and lists of values along with remembering the
    33     parent objects for all the keys.
    34 
    35     This is used for the database object deletion routines so that we can
    36     calculate the 'leaf' objects which should be deleted first.
    37 
    38     previously_seen is an optional argument. It must be a CollectedObjects
    39     instance itself; any previously_seen collected object will be blocked from
    40     being added to this instance.
    41     """
    42 
    43     def __init__(self, previously_seen=None):
    44         self.data = {}
    45         self.children = {}
    46         if previously_seen:
    47             self.blocked = previously_seen.blocked
    48             for cls, seen in previously_seen.data.items():
    49                 self.blocked.setdefault(cls, SortedDict()).update(seen)
    50         else:
    51             self.blocked = {}
    52 
    53     def add(self, model, pk, obj, parent_model, nullable=False):
    54         """
    55         Adds an item to the container.
    56 
    57         Arguments:
    58         * model - the class of the object being added.
    59         * pk - the primary key.
    60         * obj - the object itself.
    61         * parent_model - the model of the parent object that this object was
    62           reached through.
    63         * nullable - should be True if this relation is nullable.
    64 
    65         Returns True if the item already existed in the structure and
    66         False otherwise.
    67         """
    68         if pk in self.blocked.get(model, {}):
    69             return True
    70 
    71         d = self.data.setdefault(model, SortedDict())
    72         retval = pk in d
    73         d[pk] = obj
    74         # Nullable relationships can be ignored -- they are nulled out before
    75         # deleting, and therefore do not affect the order in which objects
    76         # have to be deleted.
    77         if parent_model is not None and not nullable:
    78             self.children.setdefault(parent_model, []).append(model)
    79         return retval
    80 
    81     def __contains__(self, key):
    82         return self.data.__contains__(key)
    83 
    84     def __getitem__(self, key):
    85         return self.data[key]
    86 
    87     def __nonzero__(self):
    88         return bool(self.data)
    89 
    90     def iteritems(self):
    91         for k in self.ordered_keys():
    92             yield k, self[k]
    93 
    94     def items(self):
    95         return list(self.iteritems())
    96 
    97     def keys(self):
    98         return self.ordered_keys()
    99 
    100     def ordered_keys(self):
    101         """
    102         Returns the models in the order that they should be dealt with (i.e.
    103         models with no dependencies first).
    104         """
    105         dealt_with = SortedDict()
    106         # Start with items that have no children
    107         models = self.data.keys()
    108         while len(dealt_with) < len(models):
    109             found = False
    110             for model in models:
    111                 if model in dealt_with:
    112                     continue
    113                 children = self.children.setdefault(model, [])
    114                 if len([c for c in children if c not in dealt_with]) == 0:
    115                     dealt_with[model] = None
    116                     found = True
    117             if not found:
    118                 raise CyclicDependency(
    119                     "There is a cyclic dependency of items to be processed.")
    120 
    121         return dealt_with.keys()
    122 
    123     def unordered_keys(self):
    124         """
    125         Fallback for the case where is a cyclic dependency but we don't  care.
    126         """
    127         return self.data.keys()
    128 
    12921class QueryWrapper(object):
    13022    """
    13123    A type that indicates the contents are an SQL fragment and the associate
  • django/core/management/validation.py

     
    2222    from django.db import models, connection
    2323    from django.db.models.loading import get_app_errors
    2424    from django.db.models.fields.related import RelatedObject
     25    from django.db.models.deletion import SET_NULL, SET_DEFAULT
    2526
    2627    e = ModelErrorCollection(outfile)
    2728
     
    6667            # Perform any backend-specific field validation.
    6768            connection.validation.validate_field(e, opts, f)
    6869
     70            # Check if the on_delete behavior is sane
     71            if f.rel and hasattr(f.rel, 'on_delete'):
     72                if f.rel.on_delete == SET_NULL and not f.null:
     73                    e.add(opts, "'%s' specifies on_delete=SET_NULL, but cannot be null." % f.name)
     74                elif f.rel.on_delete == SET_DEFAULT and not f.has_default():
     75                    e.add(opts, "'%s' specifies on_delete=SET_DEFAULT, but has no default value." % f.name)
     76
    6977            # Check to see if the related field will clash with any existing
    7078            # fields, m2m fields, m2m related objects or related objects
    7179            if f.rel:
Back to Top