PageRenderTime 113ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/tests/modeltests/m2m_signals/tests.py

https://code.google.com/p/mango-py/
Python | 427 lines | 373 code | 31 blank | 23 comment | 1 complexity | 24156599874c55bcb76db4df564aab18 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """
  2. Testing signals emitted on changing m2m relations.
  3. """
  4. from django.db import models
  5. from django.test import TestCase
  6. from models import Part, Car, SportsCar, Person
  7. class ManyToManySignalsTest(TestCase):
  8. def m2m_changed_signal_receiver(self, signal, sender, **kwargs):
  9. message = {
  10. 'instance': kwargs['instance'],
  11. 'action': kwargs['action'],
  12. 'reverse': kwargs['reverse'],
  13. 'model': kwargs['model'],
  14. }
  15. if kwargs['pk_set']:
  16. message['objects'] = list(
  17. kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
  18. )
  19. self.m2m_changed_messages.append(message)
  20. def setUp(self):
  21. self.m2m_changed_messages = []
  22. self.vw = Car.objects.create(name='VW')
  23. self.bmw = Car.objects.create(name='BMW')
  24. self.toyota = Car.objects.create(name='Toyota')
  25. self.wheelset = Part.objects.create(name='Wheelset')
  26. self.doors = Part.objects.create(name='Doors')
  27. self.engine = Part.objects.create(name='Engine')
  28. self.airbag = Part.objects.create(name='Airbag')
  29. self.sunroof = Part.objects.create(name='Sunroof')
  30. self.alice = Person.objects.create(name='Alice')
  31. self.bob = Person.objects.create(name='Bob')
  32. self.chuck = Person.objects.create(name='Chuck')
  33. self.daisy = Person.objects.create(name='Daisy')
  34. def tearDown(self):
  35. # disconnect all signal handlers
  36. models.signals.m2m_changed.disconnect(
  37. self.m2m_changed_signal_receiver, Car.default_parts.through
  38. )
  39. models.signals.m2m_changed.disconnect(
  40. self.m2m_changed_signal_receiver, Car.optional_parts.through
  41. )
  42. models.signals.m2m_changed.disconnect(
  43. self.m2m_changed_signal_receiver, Person.fans.through
  44. )
  45. models.signals.m2m_changed.disconnect(
  46. self.m2m_changed_signal_receiver, Person.friends.through
  47. )
  48. def test_m2m_relations_add_remove_clear(self):
  49. expected_messages = []
  50. # Install a listener on one of the two m2m relations.
  51. models.signals.m2m_changed.connect(
  52. self.m2m_changed_signal_receiver, Car.optional_parts.through
  53. )
  54. # Test the add, remove and clear methods on both sides of the
  55. # many-to-many relation
  56. # adding a default part to our car - no signal listener installed
  57. self.vw.default_parts.add(self.sunroof)
  58. # Now install a listener
  59. models.signals.m2m_changed.connect(
  60. self.m2m_changed_signal_receiver, Car.default_parts.through
  61. )
  62. self.vw.default_parts.add(self.wheelset, self.doors, self.engine)
  63. expected_messages.append({
  64. 'instance': self.vw,
  65. 'action': 'pre_add',
  66. 'reverse': False,
  67. 'model': Part,
  68. 'objects': [self.doors, self.engine, self.wheelset],
  69. })
  70. expected_messages.append({
  71. 'instance': self.vw,
  72. 'action': 'post_add',
  73. 'reverse': False,
  74. 'model': Part,
  75. 'objects': [self.doors, self.engine, self.wheelset],
  76. })
  77. self.assertEqual(self.m2m_changed_messages, expected_messages)
  78. # give the BMW and Toyata some doors as well
  79. self.doors.car_set.add(self.bmw, self.toyota)
  80. expected_messages.append({
  81. 'instance': self.doors,
  82. 'action': 'pre_add',
  83. 'reverse': True,
  84. 'model': Car,
  85. 'objects': [self.bmw, self.toyota],
  86. })
  87. expected_messages.append({
  88. 'instance': self.doors,
  89. 'action': 'post_add',
  90. 'reverse': True,
  91. 'model': Car,
  92. 'objects': [self.bmw, self.toyota],
  93. })
  94. self.assertEqual(self.m2m_changed_messages, expected_messages)
  95. # remove the engine from the self.vw and the airbag (which is not set
  96. # but is returned)
  97. self.vw.default_parts.remove(self.engine, self.airbag)
  98. expected_messages.append({
  99. 'instance': self.vw,
  100. 'action': 'pre_remove',
  101. 'reverse': False,
  102. 'model': Part,
  103. 'objects': [self.airbag, self.engine],
  104. })
  105. expected_messages.append({
  106. 'instance': self.vw,
  107. 'action': 'post_remove',
  108. 'reverse': False,
  109. 'model': Part,
  110. 'objects': [self.airbag, self.engine],
  111. })
  112. self.assertEqual(self.m2m_changed_messages, expected_messages)
  113. # give the self.vw some optional parts (second relation to same model)
  114. self.vw.optional_parts.add(self.airbag, self.sunroof)
  115. expected_messages.append({
  116. 'instance': self.vw,
  117. 'action': 'pre_add',
  118. 'reverse': False,
  119. 'model': Part,
  120. 'objects': [self.airbag, self.sunroof],
  121. })
  122. expected_messages.append({
  123. 'instance': self.vw,
  124. 'action': 'post_add',
  125. 'reverse': False,
  126. 'model': Part,
  127. 'objects': [self.airbag, self.sunroof],
  128. })
  129. self.assertEqual(self.m2m_changed_messages, expected_messages)
  130. # add airbag to all the cars (even though the self.vw already has one)
  131. self.airbag.cars_optional.add(self.vw, self.bmw, self.toyota)
  132. expected_messages.append({
  133. 'instance': self.airbag,
  134. 'action': 'pre_add',
  135. 'reverse': True,
  136. 'model': Car,
  137. 'objects': [self.bmw, self.toyota],
  138. })
  139. expected_messages.append({
  140. 'instance': self.airbag,
  141. 'action': 'post_add',
  142. 'reverse': True,
  143. 'model': Car,
  144. 'objects': [self.bmw, self.toyota],
  145. })
  146. self.assertEqual(self.m2m_changed_messages, expected_messages)
  147. # remove airbag from the self.vw (reverse relation with custom
  148. # related_name)
  149. self.airbag.cars_optional.remove(self.vw)
  150. expected_messages.append({
  151. 'instance': self.airbag,
  152. 'action': 'pre_remove',
  153. 'reverse': True,
  154. 'model': Car,
  155. 'objects': [self.vw],
  156. })
  157. expected_messages.append({
  158. 'instance': self.airbag,
  159. 'action': 'post_remove',
  160. 'reverse': True,
  161. 'model': Car,
  162. 'objects': [self.vw],
  163. })
  164. self.assertEqual(self.m2m_changed_messages, expected_messages)
  165. # clear all parts of the self.vw
  166. self.vw.default_parts.clear()
  167. expected_messages.append({
  168. 'instance': self.vw,
  169. 'action': 'pre_clear',
  170. 'reverse': False,
  171. 'model': Part,
  172. })
  173. expected_messages.append({
  174. 'instance': self.vw,
  175. 'action': 'post_clear',
  176. 'reverse': False,
  177. 'model': Part,
  178. })
  179. self.assertEqual(self.m2m_changed_messages, expected_messages)
  180. # take all the doors off of cars
  181. self.doors.car_set.clear()
  182. expected_messages.append({
  183. 'instance': self.doors,
  184. 'action': 'pre_clear',
  185. 'reverse': True,
  186. 'model': Car,
  187. })
  188. expected_messages.append({
  189. 'instance': self.doors,
  190. 'action': 'post_clear',
  191. 'reverse': True,
  192. 'model': Car,
  193. })
  194. self.assertEqual(self.m2m_changed_messages, expected_messages)
  195. # take all the airbags off of cars (clear reverse relation with custom
  196. # related_name)
  197. self.airbag.cars_optional.clear()
  198. expected_messages.append({
  199. 'instance': self.airbag,
  200. 'action': 'pre_clear',
  201. 'reverse': True,
  202. 'model': Car,
  203. })
  204. expected_messages.append({
  205. 'instance': self.airbag,
  206. 'action': 'post_clear',
  207. 'reverse': True,
  208. 'model': Car,
  209. })
  210. self.assertEqual(self.m2m_changed_messages, expected_messages)
  211. # alternative ways of setting relation:
  212. self.vw.default_parts.create(name='Windows')
  213. p6 = Part.objects.get(name='Windows')
  214. expected_messages.append({
  215. 'instance': self.vw,
  216. 'action': 'pre_add',
  217. 'reverse': False,
  218. 'model': Part,
  219. 'objects': [p6],
  220. })
  221. expected_messages.append({
  222. 'instance': self.vw,
  223. 'action': 'post_add',
  224. 'reverse': False,
  225. 'model': Part,
  226. 'objects': [p6],
  227. })
  228. self.assertEqual(self.m2m_changed_messages, expected_messages)
  229. # direct assignment clears the set first, then adds
  230. self.vw.default_parts = [self.wheelset,self.doors,self.engine]
  231. expected_messages.append({
  232. 'instance': self.vw,
  233. 'action': 'pre_clear',
  234. 'reverse': False,
  235. 'model': Part,
  236. })
  237. expected_messages.append({
  238. 'instance': self.vw,
  239. 'action': 'post_clear',
  240. 'reverse': False,
  241. 'model': Part,
  242. })
  243. expected_messages.append({
  244. 'instance': self.vw,
  245. 'action': 'pre_add',
  246. 'reverse': False,
  247. 'model': Part,
  248. 'objects': [self.doors, self.engine, self.wheelset],
  249. })
  250. expected_messages.append({
  251. 'instance': self.vw,
  252. 'action': 'post_add',
  253. 'reverse': False,
  254. 'model': Part,
  255. 'objects': [self.doors, self.engine, self.wheelset],
  256. })
  257. self.assertEqual(self.m2m_changed_messages, expected_messages)
  258. # Check that signals still work when model inheritance is involved
  259. c4 = SportsCar.objects.create(name='Bugatti', price='1000000')
  260. c4b = Car.objects.get(name='Bugatti')
  261. c4.default_parts = [self.doors]
  262. expected_messages.append({
  263. 'instance': c4,
  264. 'action': 'pre_clear',
  265. 'reverse': False,
  266. 'model': Part,
  267. })
  268. expected_messages.append({
  269. 'instance': c4,
  270. 'action': 'post_clear',
  271. 'reverse': False,
  272. 'model': Part,
  273. })
  274. expected_messages.append({
  275. 'instance': c4,
  276. 'action': 'pre_add',
  277. 'reverse': False,
  278. 'model': Part,
  279. 'objects': [self.doors],
  280. })
  281. expected_messages.append({
  282. 'instance': c4,
  283. 'action': 'post_add',
  284. 'reverse': False,
  285. 'model': Part,
  286. 'objects': [self.doors],
  287. })
  288. self.assertEqual(self.m2m_changed_messages, expected_messages)
  289. self.engine.car_set.add(c4)
  290. expected_messages.append({
  291. 'instance': self.engine,
  292. 'action': 'pre_add',
  293. 'reverse': True,
  294. 'model': Car,
  295. 'objects': [c4b],
  296. })
  297. expected_messages.append({
  298. 'instance': self.engine,
  299. 'action': 'post_add',
  300. 'reverse': True,
  301. 'model': Car,
  302. 'objects': [c4b],
  303. })
  304. self.assertEqual(self.m2m_changed_messages, expected_messages)
  305. def test_m2m_relations_with_self(self):
  306. expected_messages = []
  307. models.signals.m2m_changed.connect(
  308. self.m2m_changed_signal_receiver, Person.fans.through
  309. )
  310. models.signals.m2m_changed.connect(
  311. self.m2m_changed_signal_receiver, Person.friends.through
  312. )
  313. self.alice.friends = [self.bob, self.chuck]
  314. expected_messages.append({
  315. 'instance': self.alice,
  316. 'action': 'pre_clear',
  317. 'reverse': False,
  318. 'model': Person,
  319. })
  320. expected_messages.append({
  321. 'instance': self.alice,
  322. 'action': 'post_clear',
  323. 'reverse': False,
  324. 'model': Person,
  325. })
  326. expected_messages.append({
  327. 'instance': self.alice,
  328. 'action': 'pre_add',
  329. 'reverse': False,
  330. 'model': Person,
  331. 'objects': [self.bob, self.chuck],
  332. })
  333. expected_messages.append({
  334. 'instance': self.alice,
  335. 'action': 'post_add',
  336. 'reverse': False,
  337. 'model': Person,
  338. 'objects': [self.bob, self.chuck],
  339. })
  340. self.assertEqual(self.m2m_changed_messages, expected_messages)
  341. self.alice.fans = [self.daisy]
  342. expected_messages.append({
  343. 'instance': self.alice,
  344. 'action': 'pre_clear',
  345. 'reverse': False,
  346. 'model': Person,
  347. })
  348. expected_messages.append({
  349. 'instance': self.alice,
  350. 'action': 'post_clear',
  351. 'reverse': False,
  352. 'model': Person,
  353. })
  354. expected_messages.append({
  355. 'instance': self.alice,
  356. 'action': 'pre_add',
  357. 'reverse': False,
  358. 'model': Person,
  359. 'objects': [self.daisy],
  360. })
  361. expected_messages.append({
  362. 'instance': self.alice,
  363. 'action': 'post_add',
  364. 'reverse': False,
  365. 'model': Person,
  366. 'objects': [self.daisy],
  367. })
  368. self.assertEqual(self.m2m_changed_messages, expected_messages)
  369. self.chuck.idols = [self.alice,self.bob]
  370. expected_messages.append({
  371. 'instance': self.chuck,
  372. 'action': 'pre_clear',
  373. 'reverse': True,
  374. 'model': Person,
  375. })
  376. expected_messages.append({
  377. 'instance': self.chuck,
  378. 'action': 'post_clear',
  379. 'reverse': True,
  380. 'model': Person,
  381. })
  382. expected_messages.append({
  383. 'instance': self.chuck,
  384. 'action': 'pre_add',
  385. 'reverse': True,
  386. 'model': Person,
  387. 'objects': [self.alice, self.bob],
  388. })
  389. expected_messages.append({
  390. 'instance': self.chuck,
  391. 'action': 'post_add',
  392. 'reverse': True,
  393. 'model': Person,
  394. 'objects': [self.alice, self.bob],
  395. })
  396. self.assertEqual(self.m2m_changed_messages, expected_messages)