/tests/modeltests/m2m_through/tests.py

https://code.google.com/p/mango-py/ · Python · 343 lines · 247 code · 34 blank · 62 comment · 3 complexity · d9e9f0894adb29f803449e803d8c09d9 MD5 · raw file

  1. from datetime import datetime
  2. from operator import attrgetter
  3. from django.test import TestCase
  4. from models import Person, Group, Membership, CustomMembership, \
  5. TestNoDefaultsOrNulls, PersonSelfRefM2M, Friendship
  6. class M2mThroughTests(TestCase):
  7. def setUp(self):
  8. self.bob = Person.objects.create(name='Bob')
  9. self.jim = Person.objects.create(name='Jim')
  10. self.jane = Person.objects.create(name='Jane')
  11. self.rock = Group.objects.create(name='Rock')
  12. self.roll = Group.objects.create(name='Roll')
  13. def test_m2m_through(self):
  14. # We start out by making sure that the Group 'rock' has no members.
  15. self.assertQuerysetEqual(
  16. self.rock.members.all(),
  17. []
  18. )
  19. # To make Jim a member of Group Rock, simply create a Membership object.
  20. m1 = Membership.objects.create(person=self.jim, group=self.rock)
  21. # We can do the same for Jane and Rock.
  22. m2 = Membership.objects.create(person=self.jane, group=self.rock)
  23. # Let's check to make sure that it worked. Jane and Jim should be members of Rock.
  24. self.assertQuerysetEqual(
  25. self.rock.members.all(), [
  26. 'Jane',
  27. 'Jim'
  28. ],
  29. attrgetter("name")
  30. )
  31. # Now we can add a bunch more Membership objects to test with.
  32. m3 = Membership.objects.create(person=self.bob, group=self.roll)
  33. m4 = Membership.objects.create(person=self.jim, group=self.roll)
  34. m5 = Membership.objects.create(person=self.jane, group=self.roll)
  35. # We can get Jim's Group membership as with any ForeignKey.
  36. self.assertQuerysetEqual(
  37. self.jim.group_set.all(), [
  38. 'Rock',
  39. 'Roll'
  40. ],
  41. attrgetter("name")
  42. )
  43. # Querying the intermediary model works like normal.
  44. self.assertEqual(
  45. repr(Membership.objects.get(person=self.jane, group=self.rock)),
  46. '<Membership: Jane is a member of Rock>'
  47. )
  48. # It's not only get that works. Filter works like normal as well.
  49. self.assertQuerysetEqual(
  50. Membership.objects.filter(person=self.jim), [
  51. '<Membership: Jim is a member of Rock>',
  52. '<Membership: Jim is a member of Roll>'
  53. ]
  54. )
  55. self.rock.members.clear()
  56. # Now there will be no members of Rock.
  57. self.assertQuerysetEqual(
  58. self.rock.members.all(),
  59. []
  60. )
  61. def test_forward_descriptors(self):
  62. # Due to complications with adding via an intermediary model,
  63. # the add method is not provided.
  64. self.assertRaises(AttributeError, lambda: self.rock.members.add(self.bob))
  65. # Create is also disabled as it suffers from the same problems as add.
  66. self.assertRaises(AttributeError, lambda: self.rock.members.create(name='Anne'))
  67. # Remove has similar complications, and is not provided either.
  68. self.assertRaises(AttributeError, lambda: self.rock.members.remove(self.jim))
  69. m1 = Membership.objects.create(person=self.jim, group=self.rock)
  70. m2 = Membership.objects.create(person=self.jane, group=self.rock)
  71. # Here we back up the list of all members of Rock.
  72. backup = list(self.rock.members.all())
  73. # ...and we verify that it has worked.
  74. self.assertEqual(
  75. [p.name for p in backup],
  76. ['Jane', 'Jim']
  77. )
  78. # The clear function should still work.
  79. self.rock.members.clear()
  80. # Now there will be no members of Rock.
  81. self.assertQuerysetEqual(
  82. self.rock.members.all(),
  83. []
  84. )
  85. # Assignment should not work with models specifying a through model for many of
  86. # the same reasons as adding.
  87. self.assertRaises(AttributeError, setattr, self.rock, "members", backup)
  88. # Let's re-save those instances that we've cleared.
  89. m1.save()
  90. m2.save()
  91. # Verifying that those instances were re-saved successfully.
  92. self.assertQuerysetEqual(
  93. self.rock.members.all(),[
  94. 'Jane',
  95. 'Jim'
  96. ],
  97. attrgetter("name")
  98. )
  99. def test_reverse_descriptors(self):
  100. # Due to complications with adding via an intermediary model,
  101. # the add method is not provided.
  102. self.assertRaises(AttributeError, lambda: self.bob.group_set.add(self.rock))
  103. # Create is also disabled as it suffers from the same problems as add.
  104. self.assertRaises(AttributeError, lambda: self.bob.group_set.create(name="funk"))
  105. # Remove has similar complications, and is not provided either.
  106. self.assertRaises(AttributeError, lambda: self.jim.group_set.remove(self.rock))
  107. m1 = Membership.objects.create(person=self.jim, group=self.rock)
  108. m2 = Membership.objects.create(person=self.jim, group=self.roll)
  109. # Here we back up the list of all of Jim's groups.
  110. backup = list(self.jim.group_set.all())
  111. self.assertEqual(
  112. [g.name for g in backup],
  113. ['Rock', 'Roll']
  114. )
  115. # The clear function should still work.
  116. self.jim.group_set.clear()
  117. # Now Jim will be in no groups.
  118. self.assertQuerysetEqual(
  119. self.jim.group_set.all(),
  120. []
  121. )
  122. # Assignment should not work with models specifying a through model for many of
  123. # the same reasons as adding.
  124. self.assertRaises(AttributeError, setattr, self.jim, "group_set", backup)
  125. # Let's re-save those instances that we've cleared.
  126. m1.save()
  127. m2.save()
  128. # Verifying that those instances were re-saved successfully.
  129. self.assertQuerysetEqual(
  130. self.jim.group_set.all(),[
  131. 'Rock',
  132. 'Roll'
  133. ],
  134. attrgetter("name")
  135. )
  136. def test_custom_tests(self):
  137. # Let's see if we can query through our second relationship.
  138. self.assertQuerysetEqual(
  139. self.rock.custom_members.all(),
  140. []
  141. )
  142. # We can query in the opposite direction as well.
  143. self.assertQuerysetEqual(
  144. self.bob.custom.all(),
  145. []
  146. )
  147. cm1 = CustomMembership.objects.create(person=self.bob, group=self.rock)
  148. cm2 = CustomMembership.objects.create(person=self.jim, group=self.rock)
  149. # If we get the number of people in Rock, it should be both Bob and Jim.
  150. self.assertQuerysetEqual(
  151. self.rock.custom_members.all(),[
  152. 'Bob',
  153. 'Jim'
  154. ],
  155. attrgetter("name")
  156. )
  157. # Bob should only be in one custom group.
  158. self.assertQuerysetEqual(
  159. self.bob.custom.all(),[
  160. 'Rock'
  161. ],
  162. attrgetter("name")
  163. )
  164. # Let's make sure our new descriptors don't conflict with the FK related_name.
  165. self.assertQuerysetEqual(
  166. self.bob.custom_person_related_name.all(),[
  167. '<CustomMembership: Bob is a member of Rock>'
  168. ]
  169. )
  170. def test_self_referential_tests(self):
  171. # Let's first create a person who has no friends.
  172. tony = PersonSelfRefM2M.objects.create(name="Tony")
  173. self.assertQuerysetEqual(
  174. tony.friends.all(),
  175. []
  176. )
  177. chris = PersonSelfRefM2M.objects.create(name="Chris")
  178. f = Friendship.objects.create(first=tony, second=chris, date_friended=datetime.now())
  179. # Tony should now show that Chris is his friend.
  180. self.assertQuerysetEqual(
  181. tony.friends.all(),[
  182. 'Chris'
  183. ],
  184. attrgetter("name")
  185. )
  186. # But we haven't established that Chris is Tony's Friend.
  187. self.assertQuerysetEqual(
  188. chris.friends.all(),
  189. []
  190. )
  191. f2 = Friendship.objects.create(first=chris, second=tony, date_friended=datetime.now())
  192. # Having added Chris as a friend, let's make sure that his friend set reflects
  193. # that addition.
  194. self.assertQuerysetEqual(
  195. chris.friends.all(),[
  196. 'Tony'
  197. ],
  198. attrgetter("name")
  199. )
  200. # Chris gets mad and wants to get rid of all of his friends.
  201. chris.friends.clear()
  202. # Now he should not have any more friends.
  203. self.assertQuerysetEqual(
  204. chris.friends.all(),
  205. []
  206. )
  207. # Since this isn't a symmetrical relation, Tony's friend link still exists.
  208. self.assertQuerysetEqual(
  209. tony.friends.all(),[
  210. 'Chris'
  211. ],
  212. attrgetter("name")
  213. )
  214. def test_query_tests(self):
  215. m1 = Membership.objects.create(person=self.jim, group=self.rock)
  216. m2 = Membership.objects.create(person=self.jane, group=self.rock)
  217. m3 = Membership.objects.create(person=self.bob, group=self.roll)
  218. m4 = Membership.objects.create(person=self.jim, group=self.roll)
  219. m5 = Membership.objects.create(person=self.jane, group=self.roll)
  220. m2.invite_reason = "She was just awesome."
  221. m2.date_joined = datetime(2006, 1, 1)
  222. m2.save()
  223. m3.date_joined = datetime(2004, 1, 1)
  224. m3.save()
  225. m5.date_joined = datetime(2004, 1, 1)
  226. m5.save()
  227. # We can query for the related model by using its attribute name (members, in
  228. # this case).
  229. self.assertQuerysetEqual(
  230. Group.objects.filter(members__name='Bob'),[
  231. 'Roll'
  232. ],
  233. attrgetter("name")
  234. )
  235. # To query through the intermediary model, we specify its model name.
  236. # In this case, membership.
  237. self.assertQuerysetEqual(
  238. Group.objects.filter(membership__invite_reason="She was just awesome."),[
  239. 'Rock'
  240. ],
  241. attrgetter("name")
  242. )
  243. # If we want to query in the reverse direction by the related model, use its
  244. # model name (group, in this case).
  245. self.assertQuerysetEqual(
  246. Person.objects.filter(group__name="Rock"),[
  247. 'Jane',
  248. 'Jim'
  249. ],
  250. attrgetter("name")
  251. )
  252. cm1 = CustomMembership.objects.create(person=self.bob, group=self.rock)
  253. cm2 = CustomMembership.objects.create(person=self.jim, group=self.rock)
  254. # If the m2m field has specified a related_name, using that will work.
  255. self.assertQuerysetEqual(
  256. Person.objects.filter(custom__name="Rock"),[
  257. 'Bob',
  258. 'Jim'
  259. ],
  260. attrgetter("name")
  261. )
  262. # To query through the intermediary model in the reverse direction, we again
  263. # specify its model name (membership, in this case).
  264. self.assertQuerysetEqual(
  265. Person.objects.filter(membership__invite_reason="She was just awesome."),[
  266. 'Jane'
  267. ],
  268. attrgetter("name")
  269. )
  270. # Let's see all of the groups that Jane joined after 1 Jan 2005:
  271. self.assertQuerysetEqual(
  272. Group.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__person=self.jane),[
  273. 'Rock'
  274. ],
  275. attrgetter("name")
  276. )
  277. # Queries also work in the reverse direction: Now let's see all of the people
  278. # that have joined Rock since 1 Jan 2005:
  279. self.assertQuerysetEqual(
  280. Person.objects.filter(membership__date_joined__gt=datetime(2005, 1, 1), membership__group=self.rock),[
  281. 'Jane',
  282. 'Jim'
  283. ],
  284. attrgetter("name")
  285. )
  286. # Conceivably, queries through membership could return correct, but non-unique
  287. # querysets. To demonstrate this, we query for all people who have joined a
  288. # group after 2004:
  289. self.assertQuerysetEqual(
  290. Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)),[
  291. 'Jane',
  292. 'Jim',
  293. 'Jim'
  294. ],
  295. attrgetter("name")
  296. )
  297. # Jim showed up twice, because he joined two groups ('Rock', and 'Roll'):
  298. self.assertEqual(
  299. [(m.person.name, m.group.name) for m in Membership.objects.filter(date_joined__gt=datetime(2004, 1, 1))],
  300. [(u'Jane', u'Rock'), (u'Jim', u'Rock'), (u'Jim', u'Roll')]
  301. )
  302. # QuerySet's distinct() method can correct this problem.
  303. self.assertQuerysetEqual(
  304. Person.objects.filter(membership__date_joined__gt=datetime(2004, 1, 1)).distinct(),[
  305. 'Jane',
  306. 'Jim'
  307. ],
  308. attrgetter("name")
  309. )