Django

Code

Ticket #7539: 7539.on_delete.r12288.diff

File 7539.on_delete.r12288.diff, 45.8 kB (added by emulbreh, 2 months ago)
  • tests/modeltests/invalid_models/models.py

    old new  
    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

    old new  
    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

    old new  
  • tests/modeltests/on_delete/models.py

    old new  
     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

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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
    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

    old new  
     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

    old new  
    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 
     
    11661165                setattr(obj, f.get_cache_name(), rel_obj) 
    11671166    return obj, index_end 
    11681167 
    1169 def delete_objects(seen_objs, using): 
    1170     """ 
    1171     Iterate through a list of seen classes, and remove any instances that are 
    1172     referred to. 
    1173     """ 
    1174     connection = connections[using] 
    1175     if not transaction.is_managed(using=using): 
    1176         transaction.enter_transaction_management(using=using) 
    1177         forced_managed = True 
    1178     else: 
    1179         forced_managed = False 
    1180     try: 
    1181         ordered_classes = seen_objs.keys() 
    1182     except CyclicDependency: 
    1183         # If there is a cyclic dependency, we cannot in general delete the 
    1184         # objects.  However, if an appropriate transaction is set up, or if the 
    1185         # database is lax enough, it will succeed. So for now, we go ahead and 
    1186         # try anyway. 
    1187         ordered_classes = seen_objs.unordered_keys() 
    11881168 
    1189     obj_pairs = {} 
    1190     try: 
    1191         for cls in ordered_classes: 
    1192             items = seen_objs[cls].items() 
    1193             items.sort() 
    1194             obj_pairs[cls] = items 
    1195  
    1196             # Pre-notify all instances to be deleted. 
    1197             for pk_val, instance in items: 
    1198                 if not cls._meta.auto_created: 
    1199                     signals.pre_delete.send(sender=cls, instance=instance) 
    1200  
    1201             pk_list = [pk for pk,instance in items] 
    1202             del_query = sql.DeleteQuery(cls) 
    1203             del_query.delete_batch_related(pk_list, using=using) 
    1204  
    1205             update_query = sql.UpdateQuery(cls) 
    1206             for field, model in cls._meta.get_fields_with_model(): 
    1207                 if (field.rel and field.null and field.rel.to in seen_objs and 
    1208                         filter(lambda f: f.column == field.rel.get_related_field().column, 
    1209                         field.rel.to._meta.fields)): 
    1210                     if model: 
    1211                         sql.UpdateQuery(model).clear_related(field, pk_list, using=using) 
    1212                     else: 
    1213                         update_query.clear_related(field, pk_list, using=using) 
    1214  
    1215         # Now delete the actual data. 
    1216         for cls in ordered_classes: 
    1217             items = obj_pairs[cls] 
    1218             items.reverse() 
    1219  
    1220             pk_list = [pk for pk,instance in items] 
    1221             del_query = sql.DeleteQuery(cls) 
    1222             del_query.delete_batch(pk_list, using=using) 
    1223  
    1224             # Last cleanup; set NULLs where there once was a reference to the 
    1225             # object, NULL the primary key of the found objects, and perform 
    1226             # post-notification. 
    1227             for pk_val, instance in items: 
    1228                 for field in cls._meta.fields: 
    1229                     if field.rel and field.null and field.rel.to in seen_objs: 
    1230                         setattr(instance, field.attname, None) 
    1231  
    1232                 if not cls._meta.auto_created: 
    1233                     signals.post_delete.send(sender=cls, instance=instance) 
    1234                 setattr(instance, cls._meta.pk.attname, None) 
    1235  
    1236         if forced_managed: 
    1237             transaction.commit(using=using) 
    1238         else: 
    1239             transaction.commit_unless_managed(using=using) 
    1240     finally: 
    1241         if forced_managed: 
    1242             transaction.leave_transaction_management(using=using) 
    1243  
    12441169class RawQuerySet(object): 
    12451170    """ 
    12461171    Provides an iterator which converts the results of raw SQL queries into 
  • django/db/models/query_utils.py

    old new  
    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

    old new  
    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: