tests.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. from __future__ import unicode_literals
  2. from django.db import models
  3. from django.db.models import signals
  4. from django.dispatch import receiver
  5. from django.test import TestCase
  6. from django.utils import six
  7. from .models import Author, Book, Car, Person
  8. class BaseSignalTest(TestCase):
  9. def setUp(self):
  10. # Save up the number of connected signals so that we can check at the
  11. # end that all the signals we register get properly unregistered (#9989)
  12. self.pre_signals = (
  13. len(signals.pre_save.receivers),
  14. len(signals.post_save.receivers),
  15. len(signals.pre_delete.receivers),
  16. len(signals.post_delete.receivers),
  17. )
  18. def tearDown(self):
  19. # Check that all our signals got disconnected properly.
  20. post_signals = (
  21. len(signals.pre_save.receivers),
  22. len(signals.post_save.receivers),
  23. len(signals.pre_delete.receivers),
  24. len(signals.post_delete.receivers),
  25. )
  26. self.assertEqual(self.pre_signals, post_signals)
  27. class SignalTests(BaseSignalTest):
  28. def test_save_signals(self):
  29. data = []
  30. def pre_save_handler(signal, sender, instance, **kwargs):
  31. data.append(
  32. (instance, kwargs.get("raw", False))
  33. )
  34. def post_save_handler(signal, sender, instance, **kwargs):
  35. data.append(
  36. (instance, kwargs.get("created"), kwargs.get("raw", False))
  37. )
  38. signals.pre_save.connect(pre_save_handler, weak=False)
  39. signals.post_save.connect(post_save_handler, weak=False)
  40. try:
  41. p1 = Person.objects.create(first_name="John", last_name="Smith")
  42. self.assertEqual(data, [
  43. (p1, False),
  44. (p1, True, False),
  45. ])
  46. data[:] = []
  47. p1.first_name = "Tom"
  48. p1.save()
  49. self.assertEqual(data, [
  50. (p1, False),
  51. (p1, False, False),
  52. ])
  53. data[:] = []
  54. # Calling an internal method purely so that we can trigger a "raw" save.
  55. p1.save_base(raw=True)
  56. self.assertEqual(data, [
  57. (p1, True),
  58. (p1, False, True),
  59. ])
  60. data[:] = []
  61. p2 = Person(first_name="James", last_name="Jones")
  62. p2.id = 99999
  63. p2.save()
  64. self.assertEqual(data, [
  65. (p2, False),
  66. (p2, True, False),
  67. ])
  68. data[:] = []
  69. p2.id = 99998
  70. p2.save()
  71. self.assertEqual(data, [
  72. (p2, False),
  73. (p2, True, False),
  74. ])
  75. finally:
  76. signals.pre_save.disconnect(pre_save_handler)
  77. signals.post_save.disconnect(post_save_handler)
  78. def test_delete_signals(self):
  79. data = []
  80. def pre_delete_handler(signal, sender, instance, **kwargs):
  81. data.append(
  82. (instance, instance.id is None)
  83. )
  84. # #8285: signals can be any callable
  85. class PostDeleteHandler(object):
  86. def __init__(self, data):
  87. self.data = data
  88. def __call__(self, signal, sender, instance, **kwargs):
  89. self.data.append(
  90. (instance, instance.id is None)
  91. )
  92. post_delete_handler = PostDeleteHandler(data)
  93. signals.pre_delete.connect(pre_delete_handler, weak=False)
  94. signals.post_delete.connect(post_delete_handler, weak=False)
  95. try:
  96. p1 = Person.objects.create(first_name="John", last_name="Smith")
  97. p1.delete()
  98. self.assertEqual(data, [
  99. (p1, False),
  100. (p1, False),
  101. ])
  102. data[:] = []
  103. p2 = Person(first_name="James", last_name="Jones")
  104. p2.id = 99999
  105. p2.save()
  106. p2.id = 99998
  107. p2.save()
  108. p2.delete()
  109. self.assertEqual(data, [
  110. (p2, False),
  111. (p2, False)
  112. ])
  113. data[:] = []
  114. self.assertQuerysetEqual(
  115. Person.objects.all(), [
  116. "James Jones",
  117. ],
  118. six.text_type
  119. )
  120. finally:
  121. signals.pre_delete.disconnect(pre_delete_handler)
  122. signals.post_delete.disconnect(post_delete_handler)
  123. def test_decorators(self):
  124. data = []
  125. @receiver(signals.pre_save, weak=False)
  126. def decorated_handler(signal, sender, instance, **kwargs):
  127. data.append(instance)
  128. @receiver(signals.pre_save, sender=Car, weak=False)
  129. def decorated_handler_with_sender_arg(signal, sender, instance, **kwargs):
  130. data.append(instance)
  131. try:
  132. c1 = Car.objects.create(make="Volkswagon", model="Passat")
  133. self.assertEqual(data, [c1, c1])
  134. finally:
  135. signals.pre_save.disconnect(decorated_handler)
  136. signals.pre_save.disconnect(decorated_handler_with_sender_arg, sender=Car)
  137. def test_save_and_delete_signals_with_m2m(self):
  138. data = []
  139. def pre_save_handler(signal, sender, instance, **kwargs):
  140. data.append('pre_save signal, %s' % instance)
  141. if kwargs.get('raw'):
  142. data.append('Is raw')
  143. def post_save_handler(signal, sender, instance, **kwargs):
  144. data.append('post_save signal, %s' % instance)
  145. if 'created' in kwargs:
  146. if kwargs['created']:
  147. data.append('Is created')
  148. else:
  149. data.append('Is updated')
  150. if kwargs.get('raw'):
  151. data.append('Is raw')
  152. def pre_delete_handler(signal, sender, instance, **kwargs):
  153. data.append('pre_delete signal, %s' % instance)
  154. data.append('instance.id is not None: %s' % (instance.id is not None))
  155. def post_delete_handler(signal, sender, instance, **kwargs):
  156. data.append('post_delete signal, %s' % instance)
  157. data.append('instance.id is not None: %s' % (instance.id is not None))
  158. signals.pre_save.connect(pre_save_handler, weak=False)
  159. signals.post_save.connect(post_save_handler, weak=False)
  160. signals.pre_delete.connect(pre_delete_handler, weak=False)
  161. signals.post_delete.connect(post_delete_handler, weak=False)
  162. try:
  163. a1 = Author.objects.create(name='Neal Stephenson')
  164. self.assertEqual(data, [
  165. "pre_save signal, Neal Stephenson",
  166. "post_save signal, Neal Stephenson",
  167. "Is created"
  168. ])
  169. data[:] = []
  170. b1 = Book.objects.create(name='Snow Crash')
  171. self.assertEqual(data, [
  172. "pre_save signal, Snow Crash",
  173. "post_save signal, Snow Crash",
  174. "Is created"
  175. ])
  176. data[:] = []
  177. # Assigning and removing to/from m2m shouldn't generate an m2m signal.
  178. b1.authors = [a1]
  179. self.assertEqual(data, [])
  180. b1.authors = []
  181. self.assertEqual(data, [])
  182. finally:
  183. signals.pre_save.disconnect(pre_save_handler)
  184. signals.post_save.disconnect(post_save_handler)
  185. signals.pre_delete.disconnect(pre_delete_handler)
  186. signals.post_delete.disconnect(post_delete_handler)
  187. def test_disconnect_in_dispatch(self):
  188. """
  189. Test that signals that disconnect when being called don't mess future
  190. dispatching.
  191. """
  192. class Handler(object):
  193. def __init__(self, param):
  194. self.param = param
  195. self._run = False
  196. def __call__(self, signal, sender, **kwargs):
  197. self._run = True
  198. signal.disconnect(receiver=self, sender=sender)
  199. a, b = Handler(1), Handler(2)
  200. signals.post_save.connect(a, sender=Person, weak=False)
  201. signals.post_save.connect(b, sender=Person, weak=False)
  202. Person.objects.create(first_name='John', last_name='Smith')
  203. self.assertTrue(a._run)
  204. self.assertTrue(b._run)
  205. self.assertEqual(signals.post_save.receivers, [])
  206. class LazyModelRefTest(BaseSignalTest):
  207. def setUp(self):
  208. super(LazyModelRefTest, self).setUp()
  209. self.received = []
  210. def receiver(self, **kwargs):
  211. self.received.append(kwargs)
  212. def test_invalid_sender_model_name(self):
  213. with self.assertRaisesMessage(ValueError,
  214. "Specified sender must either be a model or a "
  215. "model name of the 'app_label.ModelName' form."):
  216. signals.post_init.connect(self.receiver, sender='invalid')
  217. def test_already_loaded_model(self):
  218. signals.post_init.connect(
  219. self.receiver, sender='signals.Book', weak=False
  220. )
  221. try:
  222. instance = Book()
  223. self.assertEqual(self.received, [{
  224. 'signal': signals.post_init,
  225. 'sender': Book,
  226. 'instance': instance
  227. }])
  228. finally:
  229. signals.post_init.disconnect(self.receiver, sender=Book)
  230. def test_not_loaded_model(self):
  231. signals.post_init.connect(
  232. self.receiver, sender='signals.Created', weak=False
  233. )
  234. try:
  235. class Created(models.Model):
  236. pass
  237. instance = Created()
  238. self.assertEqual(self.received, [{
  239. 'signal': signals.post_init, 'sender': Created, 'instance': instance
  240. }])
  241. finally:
  242. signals.post_init.disconnect(self.receiver, sender=Created)