Migrated delete doctests. Thanks to Alex Gaynor.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@13778 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
eb8b5dc2d4
commit
3fe5c6194c
|
@ -40,168 +40,3 @@ class E(DefaultRepr, models.Model):
|
||||||
class F(DefaultRepr, models.Model):
|
class F(DefaultRepr, models.Model):
|
||||||
e = models.ForeignKey(E, related_name='f_rel')
|
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()
|
|
||||||
[<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]
|
|
||||||
>>> 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()
|
|
||||||
[<class 'modeltests.delete.models.D'>, <class 'modeltests.delete.models.C'>, <class 'modeltests.delete.models.B'>, <class 'modeltests.delete.models.A'>]
|
|
||||||
>>> 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()
|
|
||||||
[<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]
|
|
||||||
|
|
||||||
# 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()
|
|
||||||
[<class 'modeltests.delete.models.F'>, <class 'modeltests.delete.models.E'>]
|
|
||||||
|
|
||||||
>>> 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])
|
|
||||||
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue