Ticket #7539: 7539.on_delete.r12380.diff

File 7539.on_delete.r12380.diff, 46.1 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/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
  • 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       
  • 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
     35        from django.contrib.contenttypes.models import ContentType
    3936        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 
    5137        for f in cls._meta.many_to_many:
     38            if not isinstance(f, generic.GenericRelation):
     39                continue
    5240            w1 = self.where_class()
    53             db_prep_value = None
    54             if isinstance(f, generic.GenericRelation):
    55                 from django.contrib.contenttypes.models import ContentType
    56                 ct_field = f.rel.to._meta.get_field(f.content_type_field_name)
    57                 w1.add((Constraint(None, ct_field.column, ct_field), 'exact',
    58                         ContentType.objects.get_for_model(cls).id), AND)
    59                 id_field = f.rel.to._meta.get_field(f.object_id_field_name)
    60                 db_prep_value = id_field.get_db_prep_value
     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)
    6143            for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    6244                where = self.where_class()
    63                 where.add((Constraint(None, f.m2m_column_name(), f), 'in',
    64                         map(db_prep_value,
    65                             pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE])),
    66                         AND)
    67                 if w1:
    68                     where.add(w1, AND)
     45                where.add((Constraint(None, f.m2m_column_name(), f), 'in', pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
     46                where.add(w1, AND)
    6947                self.do_query(f.m2m_db_table(), where, using=using)
    7048
    71     def delete_batch(self, pk_list, using):
     49    def delete_batch(self, pk_list, using, field=None):
    7250        """
    73         Set up and execute delete queries for all the objects in pk_list. This
    74         should be called after delete_batch_related(), if necessary.
    75 
     51        Set up and execute delete queries for all the objects in pk_list.
     52       
    7653        More than one physical query may be executed if there are a
    7754        lot of values in pk_list.
    7855        """
     56        if not field:
     57            field = self.model._meta.pk
    7958        for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    8059            where = self.where_class()
    81             field = self.model._meta.pk
    8260            where.add((Constraint(None, field.column, field), 'in',
    8361                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
    8462            self.do_query(self.model._meta.db_table, where, using=using)
     
    11088                related_updates=self.related_updates.copy(), **kwargs)
    11189
    11290
    113     def clear_related(self, related_field, pk_list, using):
    114         """
    115         Set up and execute an update query that clears related entries for the
    116         keys in pk_list.
    117 
    118         This is used by the QuerySet.delete_objects() method.
    119         """
     91    def update_batch(self, pk_list, values, using):
     92        pk_field = self.model._meta.pk
     93        self.add_update_values(values)
    12094        for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    12195            self.where = self.where_class()
    122             f = self.model._meta.pk
    123             self.where.add((Constraint(None, f.column, f), 'in',
     96            self.where.add((Constraint(None, pk_field.column, pk_field), 'in',
    12497                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
    12598                    AND)
    126             self.values = [(related_field, None, None)]
    12799            self.get_compiler(using).execute_sql(None)
    128100
    129101    def add_update_values(self, values):
  • django/db/models/base.py

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

     
    77from django.db.models.related import RelatedObject
    88from django.db.models.query import QuerySet
    99from django.db.models.query_utils import QueryWrapper
     10from django.db.models.deletion import CASCADE
    1011from django.utils.encoding import smart_unicode
    1112from django.utils.translation import ugettext_lazy as _, string_concat, ungettext, ugettext
    1213from django.utils.functional import curry
     
    687688        manager.add(*value)
    688689
    689690class ManyToOneRel(object):
    690     def __init__(self, to, field_name, related_name=None,
    691             limit_choices_to=None, lookup_overrides=None, parent_link=False):
     691    def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None):
    692692        try:
    693693            to._meta
    694694        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
     
    698698        if limit_choices_to is None:
    699699            limit_choices_to = {}
    700700        self.limit_choices_to = limit_choices_to
    701         self.lookup_overrides = lookup_overrides or {}
    702701        self.multiple = True
    703702        self.parent_link = parent_link
     703        self.on_delete = on_delete
    704704
    705705    def is_hidden(self):
    706706        "Should the related object be hidden?"
     
    718718        return data[0]
    719719
    720720class OneToOneRel(ManyToOneRel):
    721     def __init__(self, to, field_name, related_name=None,
    722             limit_choices_to=None, lookup_overrides=None, parent_link=False):
     721    def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None):
    723722        super(OneToOneRel, self).__init__(to, field_name,
    724723                related_name=related_name, limit_choices_to=limit_choices_to,
    725                 lookup_overrides=lookup_overrides, parent_link=parent_link)
     724                parent_link=parent_link, on_delete=on_delete
     725        )
    726726        self.multiple = False
    727727
    728728class ManyToManyRel(object):
     
    771771        kwargs['rel'] = rel_class(to, to_field,
    772772            related_name=kwargs.pop('related_name', None),
    773773            limit_choices_to=kwargs.pop('limit_choices_to', None),
    774             lookup_overrides=kwargs.pop('lookup_overrides', None),
    775             parent_link=kwargs.pop('parent_link', False))
     774            parent_link=kwargs.pop('parent_link', False),
     775            on_delete=kwargs.pop('on_delete', CASCADE),
     776        )
    776777        Field.__init__(self, **kwargs)
    777778
    778779        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, router, transaction, IntegrityError
    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
     
    427428
    428429        # Delete objects in chunks to prevent the list of related objects from
    429430        # becoming too long.
    430         seen_objs = None
     431        collector = None
    431432        while 1:
    432433            # Collect all the objects to be deleted in this chunk, and all the
    433434            # objects that are related to the objects that are to be deleted.
    434             seen_objs = CollectedObjects(seen_objs)
    435             for object in del_query[:CHUNK_SIZE]:
    436                 object._collect_sub_objects(seen_objs)
    437 
    438             if not seen_objs:
     435            collector = Collector(collector)
     436            collector.collect(del_query[:CHUNK_SIZE])
     437            if not collector:
    439438                break
    440             delete_objects(seen_objs, del_query.db)
     439            collector.delete(using=del_query.db)
    441440
    442441        # Clear the result cache, in case this QuerySet gets reused.
    443442        self._result_cache = None
     
    12381237
    12391238    return obj, index_end
    12401239
    1241 def delete_objects(seen_objs, using):
    1242     """
    1243     Iterate through a list of seen classes, and remove any instances that are
    1244     referred to.
    1245     """
    1246     connection = connections[using]
    1247     if not transaction.is_managed(using=using):
    1248         transaction.enter_transaction_management(using=using)
    1249         forced_managed = True
    1250     else:
    1251         forced_managed = False
    1252     try:
    1253         ordered_classes = seen_objs.keys()
    1254     except CyclicDependency:
    1255         # If there is a cyclic dependency, we cannot in general delete the
    1256         # objects.  However, if an appropriate transaction is set up, or if the
    1257         # database is lax enough, it will succeed. So for now, we go ahead and
    1258         # try anyway.
    1259         ordered_classes = seen_objs.unordered_keys()
    12601240
    1261     obj_pairs = {}
    1262     try:
    1263         for cls in ordered_classes:
    1264             items = seen_objs[cls].items()
    1265             items.sort()
    1266             obj_pairs[cls] = items
    1267 
    1268             # Pre-notify all instances to be deleted.
    1269             for pk_val, instance in items:
    1270                 if not cls._meta.auto_created:
    1271                     signals.pre_delete.send(sender=cls, instance=instance)
    1272 
    1273             pk_list = [pk for pk,instance in items]
    1274             del_query = sql.DeleteQuery(cls)
    1275             del_query.delete_batch_related(pk_list, using=using)
    1276 
    1277             update_query = sql.UpdateQuery(cls)
    1278             for field, model in cls._meta.get_fields_with_model():
    1279                 if (field.rel and field.null and field.rel.to in seen_objs and
    1280                         filter(lambda f: f.column == field.rel.get_related_field().column,
    1281                         field.rel.to._meta.fields)):
    1282                     if model:
    1283                         sql.UpdateQuery(model).clear_related(field, pk_list, using=using)
    1284                     else:
    1285                         update_query.clear_related(field, pk_list, using=using)
    1286 
    1287         # Now delete the actual data.
    1288         for cls in ordered_classes:
    1289             items = obj_pairs[cls]
    1290             items.reverse()
    1291 
    1292             pk_list = [pk for pk,instance in items]
    1293             del_query = sql.DeleteQuery(cls)
    1294             del_query.delete_batch(pk_list, using=using)
    1295 
    1296             # Last cleanup; set NULLs where there once was a reference to the
    1297             # object, NULL the primary key of the found objects, and perform
    1298             # post-notification.
    1299             for pk_val, instance in items:
    1300                 for field in cls._meta.fields:
    1301                     if field.rel and field.null and field.rel.to in seen_objs:
    1302                         setattr(instance, field.attname, None)
    1303 
    1304                 if not cls._meta.auto_created:
    1305                     signals.post_delete.send(sender=cls, instance=instance)
    1306                 setattr(instance, cls._meta.pk.attname, None)
    1307 
    1308         if forced_managed:
    1309             transaction.commit(using=using)
    1310         else:
    1311             transaction.commit_unless_managed(using=using)
    1312     finally:
    1313         if forced_managed:
    1314             transaction.leave_transaction_management(using=using)
    1315 
    13161241class RawQuerySet(object):
    13171242    """
    13181243    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