diff --git a/tests/signals/models.py b/tests/signals/models.py index 765230a44e..2f76343ecf 100644 --- a/tests/signals/models.py +++ b/tests/signals/models.py @@ -23,3 +23,20 @@ class Car(models.Model): def __str__(self): return "%s %s" % (self.make, self.model) + + +@python_2_unicode_compatible +class Author(models.Model): + name = models.CharField(max_length=20) + + def __str__(self): + return self.name + + +@python_2_unicode_compatible +class Book(models.Model): + name = models.CharField(max_length=20) + authors = models.ManyToManyField(Author) + + def __str__(self): + return self.name diff --git a/tests/signals/tests.py b/tests/signals/tests.py index 1eb2ffb4a3..bb23cdbee4 100644 --- a/tests/signals/tests.py +++ b/tests/signals/tests.py @@ -5,158 +5,22 @@ from django.dispatch import receiver from django.test import TestCase from django.utils import six -from .models import Person, Car - - -# #8285: signals can be any callable -class PostDeleteHandler(object): - def __init__(self, data): - self.data = data - - def __call__(self, signal, sender, instance, **kwargs): - self.data.append( - (instance, instance.id is None) - ) - - -class MyReceiver(object): - def __init__(self, param): - self.param = param - self._run = False - - def __call__(self, signal, sender, **kwargs): - self._run = True - signal.disconnect(receiver=self, sender=sender) +from .models import Author, Book, Car, Person class SignalTests(TestCase): - def test_basic(self): + + def setUp(self): # Save up the number of connected signals so that we can check at the # end that all the signals we register get properly unregistered (#9989) - pre_signals = ( + self.pre_signals = ( len(signals.pre_save.receivers), len(signals.post_save.receivers), len(signals.pre_delete.receivers), len(signals.post_delete.receivers), ) - data = [] - - def pre_save_test(signal, sender, instance, **kwargs): - data.append( - (instance, kwargs.get("raw", False)) - ) - signals.pre_save.connect(pre_save_test) - - def post_save_test(signal, sender, instance, **kwargs): - data.append( - (instance, kwargs.get("created"), kwargs.get("raw", False)) - ) - signals.post_save.connect(post_save_test) - - def pre_delete_test(signal, sender, instance, **kwargs): - data.append( - (instance, instance.id is None) - ) - signals.pre_delete.connect(pre_delete_test) - - post_delete_test = PostDeleteHandler(data) - signals.post_delete.connect(post_delete_test) - - # throw a decorator syntax receiver into the mix - @receiver(signals.pre_save) - def pre_save_decorator_test(signal, sender, instance, **kwargs): - data.append(instance) - - @receiver(signals.pre_save, sender=Car) - def pre_save_decorator_sender_test(signal, sender, instance, **kwargs): - data.append(instance) - - p1 = Person(first_name="John", last_name="Smith") - self.assertEqual(data, []) - p1.save() - self.assertEqual(data, [ - (p1, False), - p1, - (p1, True, False), - ]) - data[:] = [] - - p1.first_name = "Tom" - p1.save() - self.assertEqual(data, [ - (p1, False), - p1, - (p1, False, False), - ]) - data[:] = [] - - # Car signal (sender defined) - c1 = Car(make="Volkswagon", model="Passat") - c1.save() - self.assertEqual(data, [ - (c1, False), - c1, - c1, - (c1, True, False), - ]) - data[:] = [] - - # Calling an internal method purely so that we can trigger a "raw" save. - p1.save_base(raw=True) - self.assertEqual(data, [ - (p1, True), - p1, - (p1, False, True), - ]) - data[:] = [] - - p1.delete() - self.assertEqual(data, [ - (p1, False), - (p1, False), - ]) - data[:] = [] - - p2 = Person(first_name="James", last_name="Jones") - p2.id = 99999 - p2.save() - self.assertEqual(data, [ - (p2, False), - p2, - (p2, True, False), - ]) - data[:] = [] - - p2.id = 99998 - p2.save() - self.assertEqual(data, [ - (p2, False), - p2, - (p2, True, False), - ]) - data[:] = [] - - p2.delete() - self.assertEqual(data, [ - (p2, False), - (p2, False) - ]) - - self.assertQuerysetEqual( - Person.objects.all(), [ - "James Jones", - ], - six.text_type - ) - - signals.post_delete.disconnect(post_delete_test) - signals.pre_delete.disconnect(pre_delete_test) - signals.post_save.disconnect(post_save_test) - signals.pre_save.disconnect(pre_save_test) - signals.pre_save.disconnect(pre_save_decorator_test) - signals.pre_save.disconnect(pre_save_decorator_sender_test, sender=Car) - + def tearDown(self): # Check that all our signals got disconnected properly. post_signals = ( len(signals.pre_save.receivers), @@ -164,14 +28,210 @@ class SignalTests(TestCase): len(signals.pre_delete.receivers), len(signals.post_delete.receivers), ) - self.assertEqual(pre_signals, post_signals) + self.assertEqual(self.pre_signals, post_signals) + + def test_save_signals(self): + data = [] + + def pre_save_handler(signal, sender, instance, **kwargs): + data.append( + (instance, kwargs.get("raw", False)) + ) + + def post_save_handler(signal, sender, instance, **kwargs): + data.append( + (instance, kwargs.get("created"), kwargs.get("raw", False)) + ) + + signals.pre_save.connect(pre_save_handler) + signals.post_save.connect(post_save_handler) + try: + p1 = Person.objects.create(first_name="John", last_name="Smith") + + self.assertEqual(data, [ + (p1, False), + (p1, True, False), + ]) + data[:] = [] + + p1.first_name = "Tom" + p1.save() + self.assertEqual(data, [ + (p1, False), + (p1, False, False), + ]) + data[:] = [] + + # Calling an internal method purely so that we can trigger a "raw" save. + p1.save_base(raw=True) + self.assertEqual(data, [ + (p1, True), + (p1, False, True), + ]) + data[:] = [] + + p2 = Person(first_name="James", last_name="Jones") + p2.id = 99999 + p2.save() + self.assertEqual(data, [ + (p2, False), + (p2, True, False), + ]) + data[:] = [] + p2.id = 99998 + p2.save() + self.assertEqual(data, [ + (p2, False), + (p2, True, False), + ]) + finally: + signals.post_delete.disconnect(pre_save_handler) + signals.pre_delete.disconnect(post_save_handler) + + def test_delete_signals(self): + data = [] + + def pre_delete_handler(signal, sender, instance, **kwargs): + data.append( + (instance, instance.id is None) + ) + + # #8285: signals can be any callable + class PostDeleteHandler(object): + def __init__(self, data): + self.data = data + + def __call__(self, signal, sender, instance, **kwargs): + self.data.append( + (instance, instance.id is None) + ) + post_delete_handler = PostDeleteHandler(data) + + signals.pre_delete.connect(pre_delete_handler) + signals.post_delete.connect(post_delete_handler) + try: + p1 = Person.objects.create(first_name="John", last_name="Smith") + p1.delete() + self.assertEqual(data, [ + (p1, False), + (p1, False), + ]) + data[:] = [] + + p2 = Person(first_name="James", last_name="Jones") + p2.id = 99999 + p2.save() + p2.id = 99998 + p2.save() + p2.delete() + self.assertEqual(data, [ + (p2, False), + (p2, False) + ]) + data[:] = [] + + self.assertQuerysetEqual( + Person.objects.all(), [ + "James Jones", + ], + six.text_type + ) + finally: + signals.post_delete.disconnect(pre_delete_handler) + signals.pre_delete.disconnect(post_delete_handler) + + def test_decorators(self): + data = [] + + @receiver(signals.pre_save) + def decorated_handler(signal, sender, instance, **kwargs): + data.append(instance) + + @receiver(signals.pre_save, sender=Car) + def decorated_handler_with_sender_arg(signal, sender, instance, **kwargs): + data.append(instance) + + try: + c1 = Car.objects.create(make="Volkswagon", model="Passat") + self.assertEqual(data, [c1, c1]) + finally: + signals.post_delete.disconnect(decorated_handler) + signals.pre_delete.disconnect(decorated_handler_with_sender_arg, sender=Car) + + def test_save_and_delete_signals_with_m2m(self): + data = [] + + def pre_save_handler(signal, sender, instance, **kwargs): + data.append('pre_save signal, %s' % instance) + if kwargs.get('raw'): + data.append('Is raw') + + def post_save_handler(signal, sender, instance, **kwargs): + data.append('post_save signal, %s' % instance) + if 'created' in kwargs: + if kwargs['created']: + data.append('Is created') + else: + data.append('Is updated') + if kwargs.get('raw'): + data.append('Is raw') + + def pre_delete_handler(signal, sender, instance, **kwargs): + data.append('pre_save signal, %s' % instance) + data.append('instance.id is not None: %s' % (instance.id is not None)) + + def post_delete_handler(signal, sender, instance, **kwargs): + data.append('post_delete signal, %s' % instance) + data.append('instance.id is not None: %s' % (instance.id is not None)) + + signals.pre_save.connect(pre_save_handler) + signals.post_save.connect(post_save_handler) + signals.pre_delete.connect(pre_delete_handler) + signals.post_delete.connect(post_delete_handler) + try: + a1 = Author.objects.create(name='Neal Stephenson') + self.assertEqual(data, [ + "pre_save signal, Neal Stephenson", + "post_save signal, Neal Stephenson", + "Is created" + ]) + data[:] = [] + + b1 = Book.objects.create(name='Snow Crash') + self.assertEqual(data, [ + "pre_save signal, Snow Crash", + "post_save signal, Snow Crash", + "Is created" + ]) + data[:] = [] + + # Assigning and removing to/from m2m shouldn't generate an m2m signal. + b1.authors = [a1] + self.assertEqual(data, []) + b1.authors = [] + self.assertEqual(data, []) + finally: + signals.post_delete.disconnect(pre_save_handler) + signals.pre_delete.disconnect(post_save_handler) + signals.post_save.disconnect(pre_delete_handler) + signals.pre_save.disconnect(post_delete_handler) def test_disconnect_in_dispatch(self): """ Test that signals that disconnect when being called don't mess future dispatching. """ - a, b = MyReceiver(1), MyReceiver(2) + + class Handler(object): + def __init__(self, param): + self.param = param + self._run = False + + def __call__(self, signal, sender, **kwargs): + self._run = True + signal.disconnect(receiver=self, sender=sender) + + a, b = Handler(1), Handler(2) signals.post_save.connect(sender=Person, receiver=a) signals.post_save.connect(sender=Person, receiver=b) Person.objects.create(first_name='John', last_name='Smith') diff --git a/tests/signals_regress/__init__.py b/tests/signals_regress/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/signals_regress/models.py b/tests/signals_regress/models.py deleted file mode 100644 index 5c28b76669..0000000000 --- a/tests/signals_regress/models.py +++ /dev/null @@ -1,19 +0,0 @@ -from django.db import models -from django.utils.encoding import python_2_unicode_compatible - - -@python_2_unicode_compatible -class Author(models.Model): - name = models.CharField(max_length=20) - - def __str__(self): - return self.name - - -@python_2_unicode_compatible -class Book(models.Model): - name = models.CharField(max_length=20) - authors = models.ManyToManyField(Author) - - def __str__(self): - return self.name diff --git a/tests/signals_regress/tests.py b/tests/signals_regress/tests.py deleted file mode 100644 index 6d15553c26..0000000000 --- a/tests/signals_regress/tests.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import unicode_literals - -from django.db import models -from django.test import TestCase - -from .models import Author, Book - - -class SignalsRegressTests(TestCase): - """ - Testing signals before/after saving and deleting. - """ - - def get_signal_output(self, fn, *args, **kwargs): - # Flush any existing signal output - self.signal_output = [] - fn(*args, **kwargs) - return self.signal_output - - def pre_save_test(self, signal, sender, instance, **kwargs): - self.signal_output.append('pre_save signal, %s' % instance) - if kwargs.get('raw'): - self.signal_output.append('Is raw') - - def post_save_test(self, signal, sender, instance, **kwargs): - self.signal_output.append('post_save signal, %s' % instance) - if 'created' in kwargs: - if kwargs['created']: - self.signal_output.append('Is created') - else: - self.signal_output.append('Is updated') - if kwargs.get('raw'): - self.signal_output.append('Is raw') - - def pre_delete_test(self, signal, sender, instance, **kwargs): - self.signal_output.append('pre_save signal, %s' % instance) - self.signal_output.append('instance.id is not None: %s' % (instance.id is not None)) - - def post_delete_test(self, signal, sender, instance, **kwargs): - self.signal_output.append('post_delete signal, %s' % instance) - self.signal_output.append('instance.id is not None: %s' % (instance.id is not None)) - - def setUp(self): - self.signal_output = [] - # Save up the number of connected signals so that we can check at the end - # that all the signals we register get properly unregistered (#9989) - self.pre_signals = (len(models.signals.pre_save.receivers), - len(models.signals.post_save.receivers), - len(models.signals.pre_delete.receivers), - len(models.signals.post_delete.receivers)) - - models.signals.pre_save.connect(self.pre_save_test) - models.signals.post_save.connect(self.post_save_test) - models.signals.pre_delete.connect(self.pre_delete_test) - models.signals.post_delete.connect(self.post_delete_test) - - def tearDown(self): - models.signals.post_delete.disconnect(self.post_delete_test) - models.signals.pre_delete.disconnect(self.pre_delete_test) - models.signals.post_save.disconnect(self.post_save_test) - models.signals.pre_save.disconnect(self.pre_save_test) - - # Check that all our signals got disconnected properly. - post_signals = (len(models.signals.pre_save.receivers), - len(models.signals.post_save.receivers), - len(models.signals.pre_delete.receivers), - len(models.signals.post_delete.receivers)) - - self.assertEqual(self.pre_signals, post_signals) - - def test_model_signals(self): - """ Model saves should throw some signals. """ - a1 = Author(name='Neal Stephenson') - self.assertEqual(self.get_signal_output(a1.save), [ - "pre_save signal, Neal Stephenson", - "post_save signal, Neal Stephenson", - "Is created" - ]) - - b1 = Book(name='Snow Crash') - self.assertEqual(self.get_signal_output(b1.save), [ - "pre_save signal, Snow Crash", - "post_save signal, Snow Crash", - "Is created" - ]) - - def test_m2m_signals(self): - """ Assigning and removing to/from m2m shouldn't generate an m2m signal """ - - b1 = Book(name='Snow Crash') - self.get_signal_output(b1.save) - a1 = Author(name='Neal Stephenson') - self.get_signal_output(a1.save) - self.assertEqual(self.get_signal_output(setattr, b1, 'authors', [a1]), []) - self.assertEqual(self.get_signal_output(setattr, b1, 'authors', []), [])