| 1 | from django.db import models
|
|---|
| 2 | from django.contrib.auth.models import User
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 | # #
|
|---|
| 6 | # THE SETUP
|
|---|
| 7 | # #
|
|---|
| 8 |
|
|---|
| 9 | class Human(models.Model):
|
|---|
| 10 | """A Human MAY have a user assigned"""
|
|---|
| 11 | name = models.CharField(blank=False,null=False,max_length=100)
|
|---|
| 12 | auth_user = models.ForeignKey(User,blank=True,null=True)
|
|---|
| 13 | def __unicode__(self):
|
|---|
| 14 | return self.name
|
|---|
| 15 |
|
|---|
| 16 | class Joke(models.Model):
|
|---|
| 17 | """a Joke must have owner """
|
|---|
| 18 | text = models.TextField(blank=False,null=False)
|
|---|
| 19 | owner = models.ForeignKey(Human,blank=False,null=False)
|
|---|
| 20 |
|
|---|
| 21 | # #
|
|---|
| 22 | # THE TEST
|
|---|
| 23 | # #
|
|---|
| 24 |
|
|---|
| 25 | from django.test import TestCase
|
|---|
| 26 | from django.db.models.query_utils import CollectedObjects
|
|---|
| 27 |
|
|---|
| 28 | class SimpleTest(TestCase):
|
|---|
| 29 |
|
|---|
| 30 | def setUp(self):
|
|---|
| 31 | user = User(username='Authtester',password="secret")
|
|---|
| 32 | user.save()
|
|---|
| 33 |
|
|---|
| 34 | human = Human(name='HumanTester',auth_user=user)
|
|---|
| 35 | human.save()
|
|---|
| 36 |
|
|---|
| 37 | joke = Joke(text='phunny Joke....',owner=human)
|
|---|
| 38 | joke.save()
|
|---|
| 39 |
|
|---|
| 40 | self.user ,self.human,self.joke= user,human,joke
|
|---|
| 41 |
|
|---|
| 42 | def test1(self):
|
|---|
| 43 | """
|
|---|
| 44 | The deletion of a User should disconnect it from a Human, but should not delete the Human, neither its Joke)
|
|---|
| 45 | """
|
|---|
| 46 | user, human, joke = self.user, self.human, self.joke
|
|---|
| 47 | user.delete()
|
|---|
| 48 |
|
|---|
| 49 | self.assertTrue(len(Human.objects.all()), u'there is no human left')
|
|---|
| 50 | self.assertTrue(len(Joke.objects.all()), u'there is no Joke left')
|
|---|
| 51 |
|
|---|
| 52 | def test2(self):
|
|---|
| 53 | """
|
|---|
| 54 | inspecting the objects getting collected by calling delete on a user
|
|---|
| 55 | """
|
|---|
| 56 | user, human, joke = self.user, self.human, self.joke
|
|---|
| 57 |
|
|---|
| 58 | seen = CollectedObjects()
|
|---|
| 59 | # calling the collection method used in delete
|
|---|
| 60 | user._collect_sub_objects(seen)
|
|---|
| 61 |
|
|---|
| 62 | collected_instances = []
|
|---|
| 63 | for cls,instances in seen.items():
|
|---|
| 64 | collected_instances.extend(instances.values())
|
|---|
| 65 |
|
|---|
| 66 | self.assertFalse(joke in collected_instances,u'%s should not get collected because the human.user can be set to "NULL".'%joke)
|
|---|
| 67 | # _collect_sub_objects keeps collection relations and their subrelations
|
|---|
| 68 | # There should be no futher evaluation of sub-relations if a relation can be set to null
|
|---|
| 69 |
|
|---|
| 70 | def test3(self):
|
|---|
| 71 | """
|
|---|
| 72 | printing out what happens in the delete_objects funciton
|
|---|
| 73 | """
|
|---|
| 74 | user, human, joke = self.user, self.human, self.joke
|
|---|
| 75 |
|
|---|
| 76 | seen = CollectedObjects()
|
|---|
| 77 | user._collect_sub_objects(seen)
|
|---|
| 78 | copy_of_delete_objects(seen)
|
|---|
| 79 |
|
|---|
| 80 |
|
|---|
| 81 | # ############################################################################## #
|
|---|
| 82 | # # this is copy of db.models.query.py delete_objects (django version: 1.1.1.)
|
|---|
| 83 | # # spiced with some ugly prints to see what happens there
|
|---|
| 84 | # ############################################################################## #
|
|---|
| 85 | from django.db import connection, transaction
|
|---|
| 86 | from django.db.models import signals, sql
|
|---|
| 87 |
|
|---|
| 88 | def copy_of_delete_objects(seen_objs):
|
|---|
| 89 | print
|
|---|
| 90 | print "."*40
|
|---|
| 91 | print " the delte_object slow-mo"
|
|---|
| 92 | print "."*40
|
|---|
| 93 | """
|
|---|
| 94 | Iterate through a list of seen classes, and remove any instances that are
|
|---|
| 95 | referred to.
|
|---|
| 96 | """
|
|---|
| 97 | if not transaction.is_managed():
|
|---|
| 98 | transaction.enter_transaction_management()
|
|---|
| 99 | forced_managed = True
|
|---|
| 100 | else:
|
|---|
| 101 | forced_managed = False
|
|---|
| 102 | try:
|
|---|
| 103 | ordered_classes = seen_objs.keys()
|
|---|
| 104 | except CyclicDependency:
|
|---|
| 105 | # If there is a cyclic dependency, we cannot in general delete the
|
|---|
| 106 | # objects. However, if an appropriate transaction is set up, or if the
|
|---|
| 107 | # database is lax enough, it will succeed. So for now, we go ahead and
|
|---|
| 108 | # try anyway.
|
|---|
| 109 | ordered_classes = seen_objs.unordered_keys()
|
|---|
| 110 |
|
|---|
| 111 | obj_pairs = {}
|
|---|
| 112 | print
|
|---|
| 113 | try:
|
|---|
| 114 | print "1. LOOP over ordered_classes"
|
|---|
| 115 | for cls in ordered_classes:
|
|---|
| 116 | print 'Class: ',cls
|
|---|
| 117 | items = seen_objs[cls].items()
|
|---|
| 118 | items.sort()
|
|---|
| 119 | obj_pairs[cls] = items
|
|---|
| 120 |
|
|---|
| 121 | # Pre-notify all instances to be deleted.
|
|---|
| 122 | for pk_val, instance in items:
|
|---|
| 123 | signals.pre_delete.send(sender=cls, instance=instance)
|
|---|
| 124 |
|
|---|
| 125 | pk_list = [pk for pk,instance in items]
|
|---|
| 126 | print cls.__name__,"Primarykeys used for delete_batch_related: ",tuple(pk_list)
|
|---|
| 127 | del_query = sql.DeleteQuery(cls, connection)
|
|---|
| 128 | del_query.delete_batch_related(pk_list)
|
|---|
| 129 |
|
|---|
| 130 | update_query = sql.UpdateQuery(cls, connection)
|
|---|
| 131 | print cls.__name__, 'UPDATE Queries used'
|
|---|
| 132 | for field, model in cls._meta.get_fields_with_model():
|
|---|
| 133 | if (field.rel and field.null and field.rel.to in seen_objs and
|
|---|
| 134 | filter(lambda f: f.column == field.rel.get_related_field().column,
|
|---|
| 135 | field.rel.to._meta.fields)):
|
|---|
| 136 | if model:
|
|---|
| 137 | sql.UpdateQuery(model, connection).clear_related(field,
|
|---|
| 138 | pk_list)
|
|---|
| 139 | else:
|
|---|
| 140 | update_query.clear_related(field, pk_list)
|
|---|
| 141 |
|
|---|
| 142 | print update_query or 'NONE'
|
|---|
| 143 | print
|
|---|
| 144 | print "2. LOOP over ordered_classes"
|
|---|
| 145 | print "# 'ordered_classes' has not been modified. objectes updated previousely are still in that list"
|
|---|
| 146 | # Now delete the actual data.
|
|---|
| 147 | for cls in ordered_classes:
|
|---|
| 148 | print 'Class: ',cls
|
|---|
| 149 | items = obj_pairs[cls]
|
|---|
| 150 | items.reverse()
|
|---|
| 151 |
|
|---|
| 152 | pk_list = [pk for pk,instance in items]
|
|---|
| 153 | del_query = sql.DeleteQuery(cls, connection)
|
|---|
| 154 | del_query.delete_batch(pk_list)
|
|---|
| 155 | print ' ',del_query
|
|---|
| 156 | # Last cleanup; set NULLs where there once was a reference to the
|
|---|
| 157 | # object, NULL the primary key of the found objects, and perform
|
|---|
| 158 | # post-notification.
|
|---|
| 159 | for pk_val, instance in items:
|
|---|
| 160 | for field in cls._meta.fields:
|
|---|
| 161 | if field.rel and field.null and field.rel.to in seen_objs:
|
|---|
| 162 | setattr(instance, field.attname, None)
|
|---|
| 163 | print ' ','the object %s gets its %s set to None'%(instance,field.attname)
|
|---|
| 164 |
|
|---|
| 165 | signals.post_delete.send(sender=cls, instance=instance)
|
|---|
| 166 | print ' ',"post_delete signal sent"
|
|---|
| 167 | setattr(instance, cls._meta.pk.attname, None)
|
|---|
| 168 | print ' ','the object %s gets its %s set to None'%(instance,cls._meta.pk.attname)
|
|---|
| 169 | if forced_managed:
|
|---|
| 170 | transaction.commit()
|
|---|
| 171 | else:
|
|---|
| 172 | transaction.commit_unless_managed()
|
|---|
| 173 | finally:
|
|---|
| 174 | if forced_managed:
|
|---|
| 175 | transaction.leave_transaction_management()
|
|---|
| 176 | print "."*40
|
|---|
| 177 |
|
|---|