diff --git a/tests/modeltests/delete/models.py b/tests/modeltests/delete/models.py index 0e063fd8f0..9c81f6b8f8 100644 --- a/tests/modeltests/delete/models.py +++ b/tests/modeltests/delete/models.py @@ -40,168 +40,3 @@ class E(DefaultRepr, models.Model): class F(DefaultRepr, models.Model): e = models.ForeignKey(E, related_name='f_rel') - -__test__ = {'API_TESTS': """ -### Tests for models A,B,C,D ### - -## First, test the CollectedObjects data structure directly - ->>> from django.db.models.query import CollectedObjects - ->>> g = CollectedObjects() ->>> g.add("key1", 1, "item1", None) -False ->>> g["key1"] -{1: 'item1'} ->>> g.add("key2", 1, "item1", "key1") -False ->>> g.add("key2", 2, "item2", "key1") -False ->>> g["key2"] -{1: 'item1', 2: 'item2'} ->>> g.add("key3", 1, "item1", "key1") -False ->>> g.add("key3", 1, "item1", "key2") -True ->>> g.ordered_keys() -['key3', 'key2', 'key1'] - ->>> g.add("key2", 1, "item1", "key3") -True ->>> g.ordered_keys() -Traceback (most recent call last): - ... -CyclicDependency: There is a cyclic dependency of items to be processed. - - -## Second, test the usage of CollectedObjects by Model.delete() - -# Due to the way that transactions work in the test harness, -# doing m.delete() here can work but fail in a real situation, -# since it may delete all objects, but not in the right order. -# So we manually check that the order of deletion is correct. - -# Also, it is possible that the order is correct 'accidentally', due -# solely to order of imports etc. To check this, we set the order -# that 'get_models()' will retrieve to a known 'nice' order, and -# then try again with a known 'tricky' order. Slightly naughty -# access to internals here :-) - -# If implementation changes, then the tests may need to be simplified: -# - remove the lines that set the .keyOrder and clear the related -# object caches -# - remove the second set of tests (with a2, b2 etc) - ->>> from django.db.models.loading import cache - ->>> def clear_rel_obj_caches(models): -... for m in models: -... if hasattr(m._meta, '_related_objects_cache'): -... del m._meta._related_objects_cache - -# Nice order ->>> cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd'] ->>> clear_rel_obj_caches([A, B, C, D]) - ->>> a1 = A() ->>> a1.save() ->>> b1 = B(a=a1) ->>> b1.save() ->>> c1 = C(b=b1) ->>> c1.save() ->>> d1 = D(c=c1, a=a1) ->>> d1.save() - ->>> o = CollectedObjects() ->>> a1._collect_sub_objects(o) ->>> o.keys() -[, , , ] ->>> a1.delete() - -# Same again with a known bad order ->>> cache.app_models['delete'].keyOrder = ['d', 'c', 'b', 'a'] ->>> clear_rel_obj_caches([A, B, C, D]) - ->>> a2 = A() ->>> a2.save() ->>> b2 = B(a=a2) ->>> b2.save() ->>> c2 = C(b=b2) ->>> c2.save() ->>> d2 = D(c=c2, a=a2) ->>> d2.save() - ->>> o = CollectedObjects() ->>> a2._collect_sub_objects(o) ->>> o.keys() -[, , , ] ->>> a2.delete() - -### Tests for models E,F - nullable related fields ### - -## First, test the CollectedObjects data structure directly - ->>> g = CollectedObjects() ->>> g.add("key1", 1, "item1", None) -False ->>> g.add("key2", 1, "item1", "key1", nullable=True) -False ->>> g.add("key1", 1, "item1", "key2") -True ->>> g.ordered_keys() -['key1', 'key2'] - -## Second, test the usage of CollectedObjects by Model.delete() - ->>> e1 = E() ->>> e1.save() ->>> f1 = F(e=e1) ->>> f1.save() ->>> e1.f = f1 ->>> e1.save() - -# Since E.f is nullable, we should delete F first (after nulling out -# the E.f field), then E. - ->>> o = CollectedObjects() ->>> e1._collect_sub_objects(o) ->>> o.keys() -[, ] - -# temporarily replace the UpdateQuery class to verify that E.f is actually nulled out first ->>> import django.db.models.sql ->>> class LoggingUpdateQuery(django.db.models.sql.UpdateQuery): -... def clear_related(self, related_field, pk_list, using): -... print "CLEARING FIELD",related_field.name -... return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list, using) ->>> original_class = django.db.models.sql.UpdateQuery ->>> django.db.models.sql.UpdateQuery = LoggingUpdateQuery ->>> e1.delete() -CLEARING FIELD f - ->>> e2 = E() ->>> e2.save() ->>> f2 = F(e=e2) ->>> f2.save() ->>> e2.f = f2 ->>> e2.save() - -# Same deal as before, though we are starting from the other object. - ->>> o = CollectedObjects() ->>> f2._collect_sub_objects(o) ->>> o.keys() -[, ] - ->>> f2.delete() -CLEARING FIELD f - -# Put this back to normal ->>> django.db.models.sql.UpdateQuery = original_class - -# Restore the app cache to previous condition so that all models are accounted for. ->>> cache.app_models['delete'].keyOrder = ['a', 'b', 'c', 'd', 'e', 'f'] ->>> clear_rel_obj_caches([A, B, C, D, E, F]) - -""" -} diff --git a/tests/modeltests/delete/tests.py b/tests/modeltests/delete/tests.py new file mode 100644 index 0000000000..7927cce1c1 --- /dev/null +++ b/tests/modeltests/delete/tests.py @@ -0,0 +1,135 @@ +from django.db.models import sql +from django.db.models.loading import cache +from django.db.models.query import CollectedObjects +from django.db.models.query_utils import CyclicDependency +from django.test import TestCase + +from models import A, B, C, D, E, F + + +class DeleteTests(TestCase): + def clear_rel_obj_caches(self, *models): + for m in models: + if hasattr(m._meta, '_related_objects_cache'): + del m._meta._related_objects_cache + + def order_models(self, *models): + cache.app_models["delete"].keyOrder = models + + def setUp(self): + self.order_models("a", "b", "c", "d", "e", "f") + self.clear_rel_obj_caches(A, B, C, D, E, F) + + def tearDown(self): + self.order_models("a", "b", "c", "d", "e", "f") + self.clear_rel_obj_caches(A, B, C, D, E, F) + + def test_collected_objects(self): + g = CollectedObjects() + self.assertFalse(g.add("key1", 1, "item1", None)) + self.assertEqual(g["key1"], {1: "item1"}) + + self.assertFalse(g.add("key2", 1, "item1", "key1")) + self.assertFalse(g.add("key2", 2, "item2", "key1")) + + self.assertEqual(g["key2"], {1: "item1", 2: "item2"}) + + self.assertFalse(g.add("key3", 1, "item1", "key1")) + self.assertTrue(g.add("key3", 1, "item1", "key2")) + self.assertEqual(g.ordered_keys(), ["key3", "key2", "key1"]) + + self.assertTrue(g.add("key2", 1, "item1", "key3")) + self.assertRaises(CyclicDependency, g.ordered_keys) + + def test_delete(self): + ## Second, test the usage of CollectedObjects by Model.delete() + + # Due to the way that transactions work in the test harness, doing + # m.delete() here can work but fail in a real situation, since it may + # delete all objects, but not in the right order. So we manually check + # that the order of deletion is correct. + + # Also, it is possible that the order is correct 'accidentally', due + # solely to order of imports etc. To check this, we set the order that + # 'get_models()' will retrieve to a known 'nice' order, and then try + # again with a known 'tricky' order. Slightly naughty access to + # internals here :-) + + # If implementation changes, then the tests may need to be simplified: + # - remove the lines that set the .keyOrder and clear the related + # object caches + # - remove the second set of tests (with a2, b2 etc) + + a1 = A.objects.create() + b1 = B.objects.create(a=a1) + c1 = C.objects.create(b=b1) + d1 = D.objects.create(c=c1, a=a1) + + o = CollectedObjects() + a1._collect_sub_objects(o) + self.assertEqual(o.keys(), [D, C, B, A]) + a1.delete() + + # Same again with a known bad order + self.order_models("d", "c", "b", "a") + self.clear_rel_obj_caches(A, B, C, D) + + a2 = A.objects.create() + b2 = B.objects.create(a=a2) + c2 = C.objects.create(b=b2) + d2 = D.objects.create(c=c2, a=a2) + + o = CollectedObjects() + a2._collect_sub_objects(o) + self.assertEqual(o.keys(), [D, C, B, A]) + a2.delete() + + def test_collected_objects_null(self): + g = CollectedObjects() + self.assertFalse(g.add("key1", 1, "item1", None)) + self.assertFalse(g.add("key2", 1, "item1", "key1", nullable=True)) + self.assertTrue(g.add("key1", 1, "item1", "key2")) + self.assertEqual(g.ordered_keys(), ["key1", "key2"]) + + def test_delete_nullable(self): + e1 = E.objects.create() + f1 = F.objects.create(e=e1) + e1.f = f1 + e1.save() + + # Since E.f is nullable, we should delete F first (after nulling out + # the E.f field), then E. + + o = CollectedObjects() + e1._collect_sub_objects(o) + self.assertEqual(o.keys(), [F, E]) + + # temporarily replace the UpdateQuery class to verify that E.f is + # actually nulled out first + + logged = [] + class LoggingUpdateQuery(sql.UpdateQuery): + def clear_related(self, related_field, pk_list, using): + logged.append(related_field.name) + return super(LoggingUpdateQuery, self).clear_related(related_field, pk_list, using) + original = sql.UpdateQuery + sql.UpdateQuery = LoggingUpdateQuery + + e1.delete() + self.assertEqual(logged, ["f"]) + logged = [] + + e2 = E.objects.create() + f2 = F.objects.create(e=e2) + e2.f = f2 + e2.save() + + # Same deal as before, though we are starting from the other object. + o = CollectedObjects() + f2._collect_sub_objects(o) + self.assertEqual(o.keys(), [F, E]) + f2.delete() + self.assertEqual(logged, ["f"]) + logged = [] + + sql.UpdateQuery = original