Ticket #7539: 7539.on_delete.r11724.2.diff

File 7539.on_delete.r11724.2.diff, 45.8 KB (added by Johannes Dollinger, 15 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)
     
    162106
    163107# Since E.f is nullable, we should delete F first (after nulling out
    164108# the E.f field), then E.
    165 
    166 >>> o = CollectedObjects()
    167 >>> e1._collect_sub_objects(o)
    168 >>> o.keys()
    169 [<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]
    170 
    171109# temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first
    172110>>> import django.db.models.sql
    173111>>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery):
    174 ...     def clear_related(self, related_field, pk_list):
    175 ...         print "CLEARING FIELD",related_field.name
    176 ...         return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list)
     112...     def update_batch(self, pk_list, values):
     113...         if values == {'f': None}:
     114...             print "CLEARING FIELD f"
     115...         return super(LoggingUpdateQuery, self).update_batch(pk_list, values)
    177116>>> original_class = django.db.models.sql.UpdateQuery
    178117>>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery
    179118>>> e1.delete()
     
    187126>>> e2.save()
    188127
    189128# 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 
    196129>>> f2.delete()
    197130CLEARING FIELD f
    198131
  • django/db/models/sql/subqueries.py

     
    3434        self.where = where
    3535        self.execute_sql(None)
    3636
    37     def delete_batch_related(self, pk_list):
     37    def delete_generic_relation_hack(self, pk_list):
    3838        """
    39         Set up and execute delete queries for all the objects related to the
    40         primary key values in pk_list. To delete the objects themselves, use
    41         the delete_batch() method.
    42 
    43         More than one physical query may be executed if there are a
    44         lot of values in pk_list.
     39        Delete objects related to self.model through a GenericRelation.
     40        This should be handled by a custom `on_delete` handler in django.contrib.contentttypes.
    4541        """
    4642        from django.contrib.contenttypes import generic
    4743        cls = self.model
    48         for related in cls._meta.get_all_related_many_to_many_objects():
    49             if not isinstance(related.field, generic.GenericRelation):
    50                 for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    51                     where = self.where_class()
    52                     where.add((Constraint(None,
    53                             related.field.m2m_reverse_name(), related.field),
    54                             'in',
    55                             pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
    56                             AND)
    57                     self.do_query(related.field.m2m_db_table(), where)
    58 
    5944        for f in cls._meta.many_to_many:
    60             w1 = self.where_class()
    61             if isinstance(f, generic.GenericRelation):
    62                 from django.contrib.contenttypes.models import ContentType
    63                 field = f.rel.to._meta.get_field(f.content_type_field_name)
    64                 w1.add((Constraint(None, field.column, field), 'exact',
    65                         ContentType.objects.get_for_model(cls).id), AND)
     45            if not isinstance(f, generic.GenericRelation):
     46                continue
     47            w1 = self.where_class()           
     48            from django.contrib.contenttypes.models import ContentType
     49            field = f.rel.to._meta.get_field(f.content_type_field_name)
     50            w1.add((Constraint(None, field.column, field), 'exact',
     51                    ContentType.objects.get_for_model(cls).id), AND)
    6652            for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    6753                where = self.where_class()
    6854                where.add((Constraint(None, f.m2m_column_name(), f), 'in',
    6955                        pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
    7056                        AND)
    71                 if w1:
    72                     where.add(w1, AND)
     57                where.add(w1, AND)
    7358                self.do_query(f.m2m_db_table(), where)
    7459
    75     def delete_batch(self, pk_list):
     60    def delete_batch(self, pk_list, field=None):
    7661        """
    77         Set up and execute delete queries for all the objects in pk_list. This
    78         should be called after delete_batch_related(), if necessary.
     62        Set up and execute delete queries for all the objects in pk_list.
    7963
    8064        More than one physical query may be executed if there are a
    8165        lot of values in pk_list.
    8266        """
     67        if not field:
     68            field = self.model._meta.pk
    8369        for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    8470            where = self.where_class()
    85             field = self.model._meta.pk
    8671            where.add((Constraint(None, field.column, field), 'in',
    8772                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
    8873            self.do_query(self.model._meta.db_table, where)
     
    201186        for alias in self.tables[1:]:
    202187            self.alias_refcount[alias] = 0
    203188
    204     def clear_related(self, related_field, pk_list):
    205         """
    206         Set up and execute an update query that clears related entries for the
    207         keys in pk_list.
    208 
    209         This is used by the QuerySet.delete_objects() method.
    210         """
     189    def update_batch(self, pk_list, values):
     190        pk_field = self.model._meta.pk
     191        self.add_update_values(values)
    211192        for offset in range(0, len(pk_list), GET_ITERATOR_CHUNK_SIZE):
    212             self.where = self.where_class()
    213             f = self.model._meta.pk
    214             self.where.add((Constraint(None, f.column, f), 'in',
    215                     pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]),
    216                     AND)
    217             self.values = [(related_field.column, None, '%s')]
     193            self.where = self.where_class()           
     194            self.where.add((Constraint(None, pk_field.column, pk_field), 'in', pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
    218195            self.execute_sql(None)
    219196
    220197    def add_update_values(self, values):
  • django/db/models/base.py

     
    77from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError
    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 connection, transaction, DatabaseError
    1415from django.db.models import signals
     
    513514
    514515    save_base.alters_data = True
    515516
    516     def _collect_sub_objects(self, seen_objs, parent=None, nullable=False):
    517         """
    518         Recursively populates seen_objs with all objects related to this
    519         object.
    520 
    521         When done, seen_objs.items() will be in the format:
    522             [(model_class, {pk_val: obj, pk_val: obj, ...}),
    523              (model_class, {pk_val: obj, pk_val: obj, ...}), ...]
    524         """
    525         pk_val = self._get_pk_val()
    526         if seen_objs.add(self.__class__, pk_val, self, parent, nullable):
    527             return
    528 
    529         for related in self._meta.get_all_related_objects():
    530             rel_opts_name = related.get_accessor_name()
    531             if isinstance(related.field.rel, OneToOneRel):
    532                 try:
    533                     sub_obj = getattr(self, rel_opts_name)
    534                 except ObjectDoesNotExist:
    535                     pass
    536                 else:
    537                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
    538             else:
    539                 # To make sure we can access all elements, we can't use the
    540                 # normal manager on the related object. So we work directly
    541                 # with the descriptor object.
    542                 for cls in self.__class__.mro():
    543                     if rel_opts_name in cls.__dict__:
    544                         rel_descriptor = cls.__dict__[rel_opts_name]
    545                         break
    546                 else:
    547                     # in the case of a hidden fkey just skip it, it'll get
    548                     # processed as an m2m
    549                     if not related.field.rel.is_hidden():
    550                         raise AssertionError("Should never get here.")
    551                     else:
    552                         continue
    553                 delete_qs = rel_descriptor.delete_manager(self).all()
    554                 for sub_obj in delete_qs:
    555                     sub_obj._collect_sub_objects(seen_objs, self.__class__, related.field.null)
    556 
    557         # Handle any ancestors (for the model-inheritance case). We do this by
    558         # traversing to the most remote parent classes -- those with no parents
    559         # themselves -- and then adding those instances to the collection. That
    560         # will include all the child instances down to "self".
    561         parent_stack = [p for p in self._meta.parents.values() if p is not None]
    562         while parent_stack:
    563             link = parent_stack.pop()
    564             parent_obj = getattr(self, link.name)
    565             if parent_obj._meta.parents:
    566                 parent_stack.extend(parent_obj._meta.parents.values())
    567                 continue
    568             # At this point, parent_obj is base class (no ancestor models). So
    569             # delete it and all its descendents.
    570             parent_obj._collect_sub_objects(seen_objs)
    571 
    572517    def delete(self):
    573518        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)
     519        collector = Collector()
     520        collector.collect([self])
     521        collector.delete()
    574522
    575         # Find all the objects than need to be deleted.
    576         seen_objs = CollectedObjects()
    577         self._collect_sub_objects(seen_objs)
    578 
    579         # Actually delete the objects.
    580         delete_objects(seen_objs)
    581 
    582523    delete.alters_data = True
    583524
    584525    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

     
    1 from django.db import connection, transaction
     1from django.db import connection
    22from django.db.backends import util
    33from django.db.models import signals, get_model
    44from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist
    55from django.db.models.related import RelatedObject
    66from django.db.models.query import QuerySet
    77from django.db.models.query_utils import QueryWrapper
     8from django.db.models.deletion import CASCADE
    89from django.utils.encoding import smart_unicode
    910from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _
    1011from django.utils.functional import curry
     
    624625        manager.add(*value)
    625626
    626627class ManyToOneRel(object):
    627     def __init__(self, to, field_name, related_name=None,
    628             limit_choices_to=None, lookup_overrides=None, parent_link=False):
     628    def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None):
    629629        try:
    630630            to._meta
    631631        except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
     
    635635        if limit_choices_to is None:
    636636            limit_choices_to = {}
    637637        self.limit_choices_to = limit_choices_to
    638         self.lookup_overrides = lookup_overrides or {}
    639638        self.multiple = True
    640639        self.parent_link = parent_link
     640        self.on_delete = on_delete
    641641
    642642    def is_hidden(self):
    643643        "Should the related object be hidden?"
     
    655655        return data[0]
    656656
    657657class OneToOneRel(ManyToOneRel):
    658     def __init__(self, to, field_name, related_name=None,
    659             limit_choices_to=None, lookup_overrides=None, parent_link=False):
     658    def __init__(self, to, field_name, related_name=None, limit_choices_to=None, parent_link=False, on_delete=None):
    660659        super(OneToOneRel, self).__init__(to, field_name,
    661660                related_name=related_name, limit_choices_to=limit_choices_to,
    662                 lookup_overrides=lookup_overrides, parent_link=parent_link)
     661                parent_link=parent_link, on_delete=on_delete
     662        )
    663663        self.multiple = False
    664664
    665665class ManyToManyRel(object):
     
    700700        kwargs['rel'] = rel_class(to, to_field,
    701701            related_name=kwargs.pop('related_name', None),
    702702            limit_choices_to=kwargs.pop('limit_choices_to', None),
    703             lookup_overrides=kwargs.pop('lookup_overrides', None),
    704             parent_link=kwargs.pop('parent_link', False))
     703            parent_link=kwargs.pop('parent_link', False),
     704            on_delete=kwargs.pop('on_delete', CASCADE),
     705        )
    705706        Field.__init__(self, **kwargs)
    706707
    707708        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 connection, transaction, IntegrityError
     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):
     164        # sort instance collections
     165        for instances in self.data.itervalues():
     166            instances.sort(key=lambda obj: obj.pk)
     167
     168        # if possible, bring the models in an order suitable for databases that don't support transactions
     169        # or cannot defer contraint checks until the end of a transaction.
     170        self.sort()
     171       
     172        # send pre_delete signals
     173        for model, obj in self.instances_with_model():
     174            if not model._meta.auto_created:
     175                signals.pre_delete.send(sender=model, instance=obj)
     176
     177        # update fields
     178        for model, instances_for_fieldvalues in self.field_updates.iteritems():
     179            query = sql.UpdateQuery(model, connection)
     180            for (field, value), instances in instances_for_fieldvalues.iteritems():
     181                query.update_batch([obj.pk for obj in instances], {field.name: value})
     182
     183        # reverse instance collections
     184        for instances in self.data.itervalues():
     185            instances.reverse()
     186
     187        # delete batches
     188        for model, batches in self.batches.iteritems():
     189            query = sql.DeleteQuery(model, connection)
     190            for field, instances in batches.iteritems():
     191                query.delete_batch([obj.pk for obj in instances], field)
     192
     193        # delete instances
     194        for model, instances in self.data.iteritems():
     195            query = sql.DeleteQuery(model, connection)
     196            pk_list = [obj.pk for obj in instances]
     197            query.delete_generic_relation_hack(pk_list)           
     198            query.delete_batch(pk_list)
     199       
     200        # send post_delete signals
     201        for model, obj in self.instances_with_model():
     202            if not model._meta.auto_created:
     203                signals.post_delete.send(sender=model, instance=obj)
     204       
     205        # update collected instances
     206        for model, instances_for_fieldvalues in self.field_updates.iteritems():
     207            for (field, value), instances in instances_for_fieldvalues.iteritems():
     208                for obj in instances:
     209                    setattr(obj, field.attname, value)
     210        for model, instances in self.data.iteritems():
     211            for instance in instances:
     212                setattr(instance, model._meta.pk.attname, None)
  • django/db/models/query.py

     
    66from django.db import connection, transaction, IntegrityError
    77from django.db.models.aggregates import Aggregate
    88from django.db.models.fields import DateField
    9 from django.db.models.query_utils import Q, select_related_descend, CollectedObjects, CyclicDependency, deferred_class_factory
     9from django.db.models.query_utils import Q, select_related_descend, deferred_class_factory
     10from django.db.models.deletion import Collector
    1011from django.db.models import signals, sql
    1112
    1213# Used to control how many objects are worked with at once in some cases (e.g.
     
    379380
    380381        # Delete objects in chunks to prevent the list of related objects from
    381382        # becoming too long.
    382         seen_objs = None
     383        collector = None
    383384        while 1:
    384385            # Collect all the objects to be deleted in this chunk, and all the
    385386            # objects that are related to the objects that are to be deleted.
    386             seen_objs = CollectedObjects(seen_objs)
    387             for object in del_query[:CHUNK_SIZE]:
    388                 object._collect_sub_objects(seen_objs)
    389 
    390             if not seen_objs:
     387            collector = Collector(collector)
     388            collector.collect(del_query[:CHUNK_SIZE])
     389            if not collector:
    391390                break
    392             delete_objects(seen_objs)
     391            collector.delete()
    393392
    394393        # Clear the result cache, in case this QuerySet gets reused.
    395394        self._result_cache = None
     
    1000999                setattr(obj, f.get_cache_name(), rel_obj)
    10011000    return obj, index_end
    10021001
    1003 def delete_objects(seen_objs):
    1004     """
    1005     Iterate through a list of seen classes, and remove any instances that are
    1006     referred to.
    1007     """
    1008     if not transaction.is_managed():
    1009         transaction.enter_transaction_management()
    1010         forced_managed = True
    1011     else:
    1012         forced_managed = False
    1013     try:
    1014         ordered_classes = seen_objs.keys()
    1015     except CyclicDependency:
    1016         # If there is a cyclic dependency, we cannot in general delete the
    1017         # objects.  However, if an appropriate transaction is set up, or if the
    1018         # database is lax enough, it will succeed. So for now, we go ahead and
    1019         # try anyway.
    1020         ordered_classes = seen_objs.unordered_keys()
    10211002
    1022     obj_pairs = {}
    1023     try:
    1024         for cls in ordered_classes:
    1025             items = seen_objs[cls].items()
    1026             items.sort()
    1027             obj_pairs[cls] = items
    1028 
    1029             # Pre-notify all instances to be deleted.
    1030             for pk_val, instance in items:
    1031                 if not cls._meta.auto_created:
    1032                     signals.pre_delete.send(sender=cls, instance=instance)
    1033 
    1034             pk_list = [pk for pk,instance in items]
    1035             del_query = sql.DeleteQuery(cls, connection)
    1036             del_query.delete_batch_related(pk_list)
    1037 
    1038             update_query = sql.UpdateQuery(cls, connection)
    1039             for field, model in cls._meta.get_fields_with_model():
    1040                 if (field.rel and field.null and field.rel.to in seen_objs and
    1041                         filter(lambda f: f.column == field.rel.get_related_field().column,
    1042                         field.rel.to._meta.fields)):
    1043                     if model:
    1044                         sql.UpdateQuery(model, connection).clear_related(field,
    1045                                 pk_list)
    1046                     else:
    1047                         update_query.clear_related(field, pk_list)
    1048 
    1049         # Now delete the actual data.
    1050         for cls in ordered_classes:
    1051             items = obj_pairs[cls]
    1052             items.reverse()
    1053 
    1054             pk_list = [pk for pk,instance in items]
    1055             del_query = sql.DeleteQuery(cls, connection)
    1056             del_query.delete_batch(pk_list)
    1057 
    1058             # Last cleanup; set NULLs where there once was a reference to the
    1059             # object, NULL the primary key of the found objects, and perform
    1060             # post-notification.
    1061             for pk_val, instance in items:
    1062                 for field in cls._meta.fields:
    1063                     if field.rel and field.null and field.rel.to in seen_objs:
    1064                         setattr(instance, field.attname, None)
    1065 
    1066                 if not cls._meta.auto_created:
    1067                     signals.post_delete.send(sender=cls, instance=instance)
    1068                 setattr(instance, cls._meta.pk.attname, None)
    1069 
    1070         if forced_managed:
    1071             transaction.commit()
    1072         else:
    1073             transaction.commit_unless_managed()
    1074     finally:
    1075         if forced_managed:
    1076             transaction.leave_transaction_management()
    1077 
    1078 
    10791003def insert_query(model, values, return_id=False, raw_values=False):
    10801004    """
    10811005    Inserts a new record for the given model. This provides an interface to
  • django/db/models/query_utils.py

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