summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/modeltests/delete/models.py20
-rw-r--r--tests/modeltests/delete/tests.py101
-rw-r--r--tests/regressiontests/admin_util/models.py3
-rw-r--r--tests/regressiontests/admin_util/tests.py13
-rw-r--r--tests/regressiontests/delete_regress/tests.py11
-rw-r--r--tests/regressiontests/dispatch/tests/test_dispatcher.py12
6 files changed, 148 insertions, 12 deletions
diff --git a/tests/modeltests/delete/models.py b/tests/modeltests/delete/models.py
index e0cec426ea..65d4e6f725 100644
--- a/tests/modeltests/delete/models.py
+++ b/tests/modeltests/delete/models.py
@@ -95,7 +95,7 @@ class MRNull(models.Model):
class Avatar(models.Model):
- pass
+ desc = models.TextField(null=True)
class User(models.Model):
@@ -108,3 +108,21 @@ class HiddenUser(models.Model):
class HiddenUserProfile(models.Model):
user = models.ForeignKey(HiddenUser)
+
+class M2MTo(models.Model):
+ pass
+
+class M2MFrom(models.Model):
+ m2m = models.ManyToManyField(M2MTo)
+
+class Parent(models.Model):
+ pass
+
+class Child(Parent):
+ pass
+
+class Base(models.Model):
+ pass
+
+class RelToBase(models.Model):
+ base = models.ForeignKey(Base, on_delete=models.DO_NOTHING)
diff --git a/tests/modeltests/delete/tests.py b/tests/modeltests/delete/tests.py
index 26f2fd52c1..2610cb4b39 100644
--- a/tests/modeltests/delete/tests.py
+++ b/tests/modeltests/delete/tests.py
@@ -1,11 +1,12 @@
from __future__ import absolute_import
-from django.db import models, IntegrityError
+from django.db import models, IntegrityError, connection
from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature
from django.utils.six.moves import xrange
from .models import (R, RChild, S, T, U, A, M, MR, MRNull,
- create_a, get_default_r, User, Avatar, HiddenUser, HiddenUserProfile)
+ create_a, get_default_r, User, Avatar, HiddenUser, HiddenUserProfile,
+ M2MTo, M2MFrom, Parent, Child, Base)
class OnDeleteTests(TestCase):
@@ -74,6 +75,16 @@ class OnDeleteTests(TestCase):
self.assertEqual(replacement_r, a.donothing)
models.signals.pre_delete.disconnect(check_do_nothing)
+ def test_do_nothing_qscount(self):
+ """
+ Test that a models.DO_NOTHING relation doesn't trigger a query.
+ """
+ b = Base.objects.create()
+ with self.assertNumQueries(1):
+ # RelToBase should not be queried.
+ b.delete()
+ self.assertEqual(Base.objects.count(), 0)
+
def test_inheritance_cascade_up(self):
child = RChild.objects.create()
child.delete()
@@ -229,16 +240,34 @@ class DeletionTests(TestCase):
# 1 query to delete the avatar
# The important thing is that when we can defer constraint checks there
# is no need to do an UPDATE on User.avatar to null it out.
+
+ # Attach a signal to make sure we will not do fast_deletes.
+ calls = []
+ def noop(*args, **kwargs):
+ calls.append('')
+ models.signals.post_delete.connect(noop, sender=User)
+
self.assertNumQueries(3, a.delete)
self.assertFalse(User.objects.exists())
self.assertFalse(Avatar.objects.exists())
+ self.assertEquals(len(calls), 1)
+ models.signals.post_delete.disconnect(noop, sender=User)
@skipIfDBFeature("can_defer_constraint_checks")
def test_cannot_defer_constraint_checks(self):
u = User.objects.create(
avatar=Avatar.objects.create()
)
+ # Attach a signal to make sure we will not do fast_deletes.
+ calls = []
+ def noop(*args, **kwargs):
+ calls.append('')
+ models.signals.post_delete.connect(noop, sender=User)
+
a = Avatar.objects.get(pk=u.avatar_id)
+ # The below doesn't make sense... Why do we need to null out
+ # user.avatar if we are going to delete the user immediately after it,
+ # and there are no more cascades.
# 1 query to find the users for the avatar.
# 1 query to delete the user
# 1 query to null out user.avatar, because we can't defer the constraint
@@ -246,6 +275,8 @@ class DeletionTests(TestCase):
self.assertNumQueries(4, a.delete)
self.assertFalse(User.objects.exists())
self.assertFalse(Avatar.objects.exists())
+ self.assertEquals(len(calls), 1)
+ models.signals.post_delete.disconnect(noop, sender=User)
def test_hidden_related(self):
r = R.objects.create()
@@ -254,3 +285,69 @@ class DeletionTests(TestCase):
r.delete()
self.assertEqual(HiddenUserProfile.objects.count(), 0)
+
+class FastDeleteTests(TestCase):
+
+ def test_fast_delete_fk(self):
+ u = User.objects.create(
+ avatar=Avatar.objects.create()
+ )
+ a = Avatar.objects.get(pk=u.avatar_id)
+ # 1 query to fast-delete the user
+ # 1 query to delete the avatar
+ self.assertNumQueries(2, a.delete)
+ self.assertFalse(User.objects.exists())
+ self.assertFalse(Avatar.objects.exists())
+
+ def test_fast_delete_m2m(self):
+ t = M2MTo.objects.create()
+ f = M2MFrom.objects.create()
+ f.m2m.add(t)
+ # 1 to delete f, 1 to fast-delete m2m for f
+ self.assertNumQueries(2, f.delete)
+
+ def test_fast_delete_revm2m(self):
+ t = M2MTo.objects.create()
+ f = M2MFrom.objects.create()
+ f.m2m.add(t)
+ # 1 to delete t, 1 to fast-delete t's m_set
+ self.assertNumQueries(2, f.delete)
+
+ def test_fast_delete_qs(self):
+ u1 = User.objects.create()
+ u2 = User.objects.create()
+ self.assertNumQueries(1, User.objects.filter(pk=u1.pk).delete)
+ self.assertEquals(User.objects.count(), 1)
+ self.assertTrue(User.objects.filter(pk=u2.pk).exists())
+
+ def test_fast_delete_joined_qs(self):
+ a = Avatar.objects.create(desc='a')
+ User.objects.create(avatar=a)
+ u2 = User.objects.create()
+ expected_queries = 1 if connection.features.update_can_self_select else 2
+ self.assertNumQueries(expected_queries,
+ User.objects.filter(avatar__desc='a').delete)
+ self.assertEquals(User.objects.count(), 1)
+ self.assertTrue(User.objects.filter(pk=u2.pk).exists())
+
+ def test_fast_delete_inheritance(self):
+ c = Child.objects.create()
+ p = Parent.objects.create()
+ # 1 for self, 1 for parent
+ # However, this doesn't work as child.parent access creates a query,
+ # and this means we will be generating extra queries (a lot for large
+ # querysets). This is not a fast-delete problem.
+ # self.assertNumQueries(2, c.delete)
+ c.delete()
+ self.assertFalse(Child.objects.exists())
+ self.assertEquals(Parent.objects.count(), 1)
+ self.assertEquals(Parent.objects.filter(pk=p.pk).count(), 1)
+ # 1 for self delete, 1 for fast delete of empty "child" qs.
+ self.assertNumQueries(2, p.delete)
+ self.assertFalse(Parent.objects.exists())
+ # 1 for self delete, 1 for fast delete of empty "child" qs.
+ c = Child.objects.create()
+ p = c.parent_ptr
+ self.assertNumQueries(2, p.delete)
+ self.assertFalse(Parent.objects.exists())
+ self.assertFalse(Child.objects.exists())
diff --git a/tests/regressiontests/admin_util/models.py b/tests/regressiontests/admin_util/models.py
index b3504a1fa4..32a6cd6291 100644
--- a/tests/regressiontests/admin_util/models.py
+++ b/tests/regressiontests/admin_util/models.py
@@ -39,3 +39,6 @@ class Guest(models.Model):
class Meta:
verbose_name = "awesome guest"
+
+class EventGuide(models.Model):
+ event = models.ForeignKey(Event, on_delete=models.DO_NOTHING)
diff --git a/tests/regressiontests/admin_util/tests.py b/tests/regressiontests/admin_util/tests.py
index d04740ce95..ef8a91d1db 100644
--- a/tests/regressiontests/admin_util/tests.py
+++ b/tests/regressiontests/admin_util/tests.py
@@ -17,7 +17,7 @@ from django.utils.formats import localize
from django.utils.safestring import mark_safe
from django.utils import six
-from .models import Article, Count, Event, Location
+from .models import Article, Count, Event, Location, EventGuide
class NestedObjectsTests(TestCase):
@@ -71,6 +71,17 @@ class NestedObjectsTests(TestCase):
# Should not require additional queries to populate the nested graph.
self.assertNumQueries(2, self._collect, 0)
+ def test_on_delete_do_nothing(self):
+ """
+ Check that the nested collector doesn't query for DO_NOTHING objects.
+ """
+ n = NestedObjects(using=DEFAULT_DB_ALIAS)
+ objs = [Event.objects.create()]
+ EventGuide.objects.create(event=objs[0])
+ with self.assertNumQueries(2):
+ # One for Location, one for Guest, and no query for EventGuide
+ n.collect(objs)
+
class UtilTests(unittest.TestCase):
def test_values_from_lookup_field(self):
"""
diff --git a/tests/regressiontests/delete_regress/tests.py b/tests/regressiontests/delete_regress/tests.py
index 32feae2ded..ebe59bffd7 100644
--- a/tests/regressiontests/delete_regress/tests.py
+++ b/tests/regressiontests/delete_regress/tests.py
@@ -3,7 +3,7 @@ from __future__ import absolute_import
import datetime
from django.conf import settings
-from django.db import backend, transaction, DEFAULT_DB_ALIAS
+from django.db import backend, transaction, DEFAULT_DB_ALIAS, models
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
from .models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith,
@@ -139,17 +139,24 @@ class DeleteCascadeTransactionTests(TransactionTestCase):
eaten = Eaten.objects.create(food=apple, meal="lunch")
apple.delete()
+ self.assertFalse(Food.objects.exists())
+ self.assertFalse(Eaten.objects.exists())
+
class LargeDeleteTests(TestCase):
def test_large_deletes(self):
"Regression for #13309 -- if the number of objects > chunk size, deletion still occurs"
for x in range(300):
track = Book.objects.create(pagecount=x+100)
+ # attach a signal to make sure we will not fast-delete
+ def noop(*args, **kwargs):
+ pass
+ models.signals.post_delete.connect(noop, sender=Book)
Book.objects.all().delete()
+ models.signals.post_delete.disconnect(noop, sender=Book)
self.assertEqual(Book.objects.count(), 0)
-
class ProxyDeleteTest(TestCase):
"""
Tests on_delete behavior for proxy models.
diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/regressiontests/dispatch/tests/test_dispatcher.py
index 4e4669d34c..5f8f92acaf 100644
--- a/tests/regressiontests/dispatch/tests/test_dispatcher.py
+++ b/tests/regressiontests/dispatch/tests/test_dispatcher.py
@@ -127,15 +127,15 @@ class DispatcherTests(unittest.TestCase):
self._testIsClean(a_signal)
def test_has_listeners(self):
- self.assertIs(a_signal.has_listeners(), False)
- self.assertIs(a_signal.has_listeners(sender=object()), False)
+ self.assertFalse(a_signal.has_listeners())
+ self.assertFalse(a_signal.has_listeners(sender=object()))
receiver_1 = Callable()
a_signal.connect(receiver_1)
- self.assertIs(a_signal.has_listeners(), True)
- self.assertIs(a_signal.has_listeners(sender=object()), True)
+ self.assertTrue(a_signal.has_listeners())
+ self.assertTrue(a_signal.has_listeners(sender=object()))
a_signal.disconnect(receiver_1)
- self.assertIs(a_signal.has_listeners(), False)
- self.assertIs(a_signal.has_listeners(sender=object()), False)
+ self.assertFalse(a_signal.has_listeners())
+ self.assertFalse(a_signal.has_listeners(sender=object()))
class ReceiverTestCase(unittest.TestCase):