|
1 from django.test import TestCase |
|
2 from models import Article, Publication |
|
3 |
|
4 class ManyToManyTests(TestCase): |
|
5 |
|
6 def setUp(self): |
|
7 # Create a couple of Publications. |
|
8 self.p1 = Publication.objects.create(id=None, title='The Python Journal') |
|
9 self.p2 = Publication.objects.create(id=None, title='Science News') |
|
10 self.p3 = Publication.objects.create(id=None, title='Science Weekly') |
|
11 self.p4 = Publication.objects.create(title='Highlights for Children') |
|
12 |
|
13 self.a1 = Article.objects.create(id=None, headline='Django lets you build Web apps easily') |
|
14 self.a1.publications.add(self.p1) |
|
15 |
|
16 self.a2 = Article.objects.create(id=None, headline='NASA uses Python') |
|
17 self.a2.publications.add(self.p1, self.p2, self.p3, self.p4) |
|
18 |
|
19 self.a3 = Article.objects.create(headline='NASA finds intelligent life on Earth') |
|
20 self.a3.publications.add(self.p2) |
|
21 |
|
22 self.a4 = Article.objects.create(headline='Oxygen-free diet works wonders') |
|
23 self.a4.publications.add(self.p2) |
|
24 |
|
25 def test_add(self): |
|
26 # Create an Article. |
|
27 a5 = Article(id=None, headline='Django lets you reate Web apps easily') |
|
28 # You can't associate it with a Publication until it's been saved. |
|
29 self.assertRaises(ValueError, getattr, a5, 'publications') |
|
30 # Save it! |
|
31 a5.save() |
|
32 # Associate the Article with a Publication. |
|
33 a5.publications.add(self.p1) |
|
34 self.assertQuerysetEqual(a5.publications.all(), |
|
35 ['<Publication: The Python Journal>']) |
|
36 # Create another Article, and set it to appear in both Publications. |
|
37 a6 = Article(id=None, headline='ESA uses Python') |
|
38 a6.save() |
|
39 a6.publications.add(self.p1, self.p2) |
|
40 a6.publications.add(self.p3) |
|
41 # Adding a second time is OK |
|
42 a6.publications.add(self.p3) |
|
43 self.assertQuerysetEqual(a6.publications.all(), |
|
44 [ |
|
45 '<Publication: Science News>', |
|
46 '<Publication: Science Weekly>', |
|
47 '<Publication: The Python Journal>', |
|
48 ]) |
|
49 |
|
50 # Adding an object of the wrong type raises TypeError |
|
51 self.assertRaises(TypeError, a6.publications.add, a5) |
|
52 # Add a Publication directly via publications.add by using keyword arguments. |
|
53 p4 = a6.publications.create(title='Highlights for Adults') |
|
54 self.assertQuerysetEqual(a6.publications.all(), |
|
55 [ |
|
56 '<Publication: Highlights for Adults>', |
|
57 '<Publication: Science News>', |
|
58 '<Publication: Science Weekly>', |
|
59 '<Publication: The Python Journal>', |
|
60 ]) |
|
61 |
|
62 def test_reverse_add(self): |
|
63 # Adding via the 'other' end of an m2m |
|
64 a5 = Article(headline='NASA finds intelligent life on Mars') |
|
65 a5.save() |
|
66 self.p2.article_set.add(a5) |
|
67 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
68 [ |
|
69 '<Article: NASA finds intelligent life on Earth>', |
|
70 '<Article: NASA finds intelligent life on Mars>', |
|
71 '<Article: NASA uses Python>', |
|
72 '<Article: Oxygen-free diet works wonders>', |
|
73 ]) |
|
74 self.assertQuerysetEqual(a5.publications.all(), |
|
75 ['<Publication: Science News>']) |
|
76 |
|
77 # Adding via the other end using keywords |
|
78 new_article = self.p2.article_set.create(headline='Carbon-free diet works wonders') |
|
79 self.assertQuerysetEqual( |
|
80 self.p2.article_set.all(), |
|
81 [ |
|
82 '<Article: Carbon-free diet works wonders>', |
|
83 '<Article: NASA finds intelligent life on Earth>', |
|
84 '<Article: NASA finds intelligent life on Mars>', |
|
85 '<Article: NASA uses Python>', |
|
86 '<Article: Oxygen-free diet works wonders>', |
|
87 ]) |
|
88 a6 = self.p2.article_set.all()[3] |
|
89 self.assertQuerysetEqual(a6.publications.all(), |
|
90 [ |
|
91 '<Publication: Highlights for Children>', |
|
92 '<Publication: Science News>', |
|
93 '<Publication: Science Weekly>', |
|
94 '<Publication: The Python Journal>', |
|
95 ]) |
|
96 |
|
97 def test_related_sets(self): |
|
98 # Article objects have access to their related Publication objects. |
|
99 self.assertQuerysetEqual(self.a1.publications.all(), |
|
100 ['<Publication: The Python Journal>']) |
|
101 self.assertQuerysetEqual(self.a2.publications.all(), |
|
102 [ |
|
103 '<Publication: Highlights for Children>', |
|
104 '<Publication: Science News>', |
|
105 '<Publication: Science Weekly>', |
|
106 '<Publication: The Python Journal>', |
|
107 ]) |
|
108 # Publication objects have access to their related Article objects. |
|
109 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
110 [ |
|
111 '<Article: NASA finds intelligent life on Earth>', |
|
112 '<Article: NASA uses Python>', |
|
113 '<Article: Oxygen-free diet works wonders>', |
|
114 ]) |
|
115 self.assertQuerysetEqual(self.p1.article_set.all(), |
|
116 [ |
|
117 '<Article: Django lets you build Web apps easily>', |
|
118 '<Article: NASA uses Python>', |
|
119 ]) |
|
120 self.assertQuerysetEqual(Publication.objects.get(id=self.p4.id).article_set.all(), |
|
121 ['<Article: NASA uses Python>']) |
|
122 |
|
123 def test_selects(self): |
|
124 # We can perform kwarg queries across m2m relationships |
|
125 self.assertQuerysetEqual( |
|
126 Article.objects.filter(publications__id__exact=self.p1.id), |
|
127 [ |
|
128 '<Article: Django lets you build Web apps easily>', |
|
129 '<Article: NASA uses Python>', |
|
130 ]) |
|
131 self.assertQuerysetEqual( |
|
132 Article.objects.filter(publications__pk=self.p1.id), |
|
133 [ |
|
134 '<Article: Django lets you build Web apps easily>', |
|
135 '<Article: NASA uses Python>', |
|
136 ]) |
|
137 self.assertQuerysetEqual( |
|
138 Article.objects.filter(publications=self.p1.id), |
|
139 [ |
|
140 '<Article: Django lets you build Web apps easily>', |
|
141 '<Article: NASA uses Python>', |
|
142 ]) |
|
143 self.assertQuerysetEqual( |
|
144 Article.objects.filter(publications=self.p1), |
|
145 [ |
|
146 '<Article: Django lets you build Web apps easily>', |
|
147 '<Article: NASA uses Python>', |
|
148 ]) |
|
149 self.assertQuerysetEqual( |
|
150 Article.objects.filter(publications__title__startswith="Science"), |
|
151 [ |
|
152 '<Article: NASA finds intelligent life on Earth>', |
|
153 '<Article: NASA uses Python>', |
|
154 '<Article: NASA uses Python>', |
|
155 '<Article: Oxygen-free diet works wonders>', |
|
156 ]) |
|
157 self.assertQuerysetEqual( |
|
158 Article.objects.filter(publications__title__startswith="Science").distinct(), |
|
159 [ |
|
160 '<Article: NASA finds intelligent life on Earth>', |
|
161 '<Article: NASA uses Python>', |
|
162 '<Article: Oxygen-free diet works wonders>', |
|
163 ]) |
|
164 |
|
165 # The count() function respects distinct() as well. |
|
166 self.assertEqual(Article.objects.filter(publications__title__startswith="Science").count(), 4) |
|
167 self.assertEqual(Article.objects.filter(publications__title__startswith="Science").distinct().count(), 3) |
|
168 self.assertQuerysetEqual( |
|
169 Article.objects.filter(publications__in=[self.p1.id,self.p2.id]).distinct(), |
|
170 [ |
|
171 '<Article: Django lets you build Web apps easily>', |
|
172 '<Article: NASA finds intelligent life on Earth>', |
|
173 '<Article: NASA uses Python>', |
|
174 '<Article: Oxygen-free diet works wonders>', |
|
175 ]) |
|
176 self.assertQuerysetEqual( |
|
177 Article.objects.filter(publications__in=[self.p1.id,self.p2]).distinct(), |
|
178 [ |
|
179 '<Article: Django lets you build Web apps easily>', |
|
180 '<Article: NASA finds intelligent life on Earth>', |
|
181 '<Article: NASA uses Python>', |
|
182 '<Article: Oxygen-free diet works wonders>', |
|
183 ]) |
|
184 self.assertQuerysetEqual( |
|
185 Article.objects.filter(publications__in=[self.p1,self.p2]).distinct(), |
|
186 [ |
|
187 '<Article: Django lets you build Web apps easily>', |
|
188 '<Article: NASA finds intelligent life on Earth>', |
|
189 '<Article: NASA uses Python>', |
|
190 '<Article: Oxygen-free diet works wonders>', |
|
191 ]) |
|
192 |
|
193 # Excluding a related item works as you would expect, too (although the SQL |
|
194 # involved is a little complex). |
|
195 self.assertQuerysetEqual(Article.objects.exclude(publications=self.p2), |
|
196 ['<Article: Django lets you build Web apps easily>']) |
|
197 |
|
198 def test_reverse_selects(self): |
|
199 # Reverse m2m queries are supported (i.e., starting at the table that |
|
200 # doesn't have a ManyToManyField). |
|
201 self.assertQuerysetEqual(Publication.objects.filter(id__exact=self.p1.id), |
|
202 ['<Publication: The Python Journal>']) |
|
203 self.assertQuerysetEqual(Publication.objects.filter(pk=self.p1.id), |
|
204 ['<Publication: The Python Journal>']) |
|
205 self.assertQuerysetEqual( |
|
206 Publication.objects.filter(article__headline__startswith="NASA"), |
|
207 [ |
|
208 '<Publication: Highlights for Children>', |
|
209 '<Publication: Science News>', |
|
210 '<Publication: Science News>', |
|
211 '<Publication: Science Weekly>', |
|
212 '<Publication: The Python Journal>', |
|
213 ]) |
|
214 self.assertQuerysetEqual(Publication.objects.filter(article__id__exact=self.a1.id), |
|
215 ['<Publication: The Python Journal>']) |
|
216 self.assertQuerysetEqual(Publication.objects.filter(article__pk=self.a1.id), |
|
217 ['<Publication: The Python Journal>']) |
|
218 self.assertQuerysetEqual(Publication.objects.filter(article=self.a1.id), |
|
219 ['<Publication: The Python Journal>']) |
|
220 self.assertQuerysetEqual(Publication.objects.filter(article=self.a1), |
|
221 ['<Publication: The Python Journal>']) |
|
222 |
|
223 self.assertQuerysetEqual( |
|
224 Publication.objects.filter(article__in=[self.a1.id,self.a2.id]).distinct(), |
|
225 [ |
|
226 '<Publication: Highlights for Children>', |
|
227 '<Publication: Science News>', |
|
228 '<Publication: Science Weekly>', |
|
229 '<Publication: The Python Journal>', |
|
230 ]) |
|
231 self.assertQuerysetEqual( |
|
232 Publication.objects.filter(article__in=[self.a1.id,self.a2]).distinct(), |
|
233 [ |
|
234 '<Publication: Highlights for Children>', |
|
235 '<Publication: Science News>', |
|
236 '<Publication: Science Weekly>', |
|
237 '<Publication: The Python Journal>', |
|
238 ]) |
|
239 self.assertQuerysetEqual( |
|
240 Publication.objects.filter(article__in=[self.a1,self.a2]).distinct(), |
|
241 [ |
|
242 '<Publication: Highlights for Children>', |
|
243 '<Publication: Science News>', |
|
244 '<Publication: Science Weekly>', |
|
245 '<Publication: The Python Journal>', |
|
246 ]) |
|
247 |
|
248 def test_delete(self): |
|
249 # If we delete a Publication, its Articles won't be able to access it. |
|
250 self.p1.delete() |
|
251 self.assertQuerysetEqual(Publication.objects.all(), |
|
252 [ |
|
253 '<Publication: Highlights for Children>', |
|
254 '<Publication: Science News>', |
|
255 '<Publication: Science Weekly>', |
|
256 ]) |
|
257 self.assertQuerysetEqual(self.a1.publications.all(), []) |
|
258 # If we delete an Article, its Publications won't be able to access it. |
|
259 self.a2.delete() |
|
260 self.assertQuerysetEqual(Article.objects.all(), |
|
261 [ |
|
262 '<Article: Django lets you build Web apps easily>', |
|
263 '<Article: NASA finds intelligent life on Earth>', |
|
264 '<Article: Oxygen-free diet works wonders>', |
|
265 ]) |
|
266 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
267 [ |
|
268 '<Article: NASA finds intelligent life on Earth>', |
|
269 '<Article: Oxygen-free diet works wonders>', |
|
270 ]) |
|
271 |
|
272 def test_bulk_delete(self): |
|
273 # Bulk delete some Publications - references to deleted publications should go |
|
274 Publication.objects.filter(title__startswith='Science').delete() |
|
275 self.assertQuerysetEqual(Publication.objects.all(), |
|
276 [ |
|
277 '<Publication: Highlights for Children>', |
|
278 '<Publication: The Python Journal>', |
|
279 ]) |
|
280 self.assertQuerysetEqual(Article.objects.all(), |
|
281 [ |
|
282 '<Article: Django lets you build Web apps easily>', |
|
283 '<Article: NASA finds intelligent life on Earth>', |
|
284 '<Article: NASA uses Python>', |
|
285 '<Article: Oxygen-free diet works wonders>', |
|
286 ]) |
|
287 self.assertQuerysetEqual(self.a2.publications.all(), |
|
288 [ |
|
289 '<Publication: Highlights for Children>', |
|
290 '<Publication: The Python Journal>', |
|
291 ]) |
|
292 |
|
293 # Bulk delete some articles - references to deleted objects should go |
|
294 q = Article.objects.filter(headline__startswith='Django') |
|
295 self.assertQuerysetEqual(q, ['<Article: Django lets you build Web apps easily>']) |
|
296 q.delete() |
|
297 # After the delete, the QuerySet cache needs to be cleared, |
|
298 # and the referenced objects should be gone |
|
299 self.assertQuerysetEqual(q, []) |
|
300 self.assertQuerysetEqual(self.p1.article_set.all(), |
|
301 ['<Article: NASA uses Python>']) |
|
302 |
|
303 def test_remove(self): |
|
304 # Removing publication from an article: |
|
305 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
306 [ |
|
307 '<Article: NASA finds intelligent life on Earth>', |
|
308 '<Article: NASA uses Python>', |
|
309 '<Article: Oxygen-free diet works wonders>', |
|
310 ]) |
|
311 self.a4.publications.remove(self.p2) |
|
312 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
313 [ |
|
314 '<Article: NASA finds intelligent life on Earth>', |
|
315 '<Article: NASA uses Python>', |
|
316 ]) |
|
317 self.assertQuerysetEqual(self.a4.publications.all(), []) |
|
318 # And from the other end |
|
319 self.p2.article_set.remove(self.a3) |
|
320 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
321 [ |
|
322 '<Article: NASA uses Python>', |
|
323 ]) |
|
324 self.assertQuerysetEqual(self.a3.publications.all(), []) |
|
325 |
|
326 def test_assign(self): |
|
327 # Relation sets can be assigned. Assignment clears any existing set members |
|
328 self.p2.article_set = [self.a4, self.a3] |
|
329 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
330 [ |
|
331 '<Article: NASA finds intelligent life on Earth>', |
|
332 '<Article: Oxygen-free diet works wonders>', |
|
333 ]) |
|
334 self.assertQuerysetEqual(self.a4.publications.all(), |
|
335 ['<Publication: Science News>']) |
|
336 self.a4.publications = [self.p3.id] |
|
337 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
338 ['<Article: NASA finds intelligent life on Earth>']) |
|
339 self.assertQuerysetEqual(self.a4.publications.all(), |
|
340 ['<Publication: Science Weekly>']) |
|
341 |
|
342 # An alternate to calling clear() is to assign the empty set |
|
343 self.p2.article_set = [] |
|
344 self.assertQuerysetEqual(self.p2.article_set.all(), []) |
|
345 self.a4.publications = [] |
|
346 self.assertQuerysetEqual(self.a4.publications.all(), []) |
|
347 |
|
348 def test_assign_ids(self): |
|
349 # Relation sets can also be set using primary key values |
|
350 self.p2.article_set = [self.a4.id, self.a3.id] |
|
351 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
352 [ |
|
353 '<Article: NASA finds intelligent life on Earth>', |
|
354 '<Article: Oxygen-free diet works wonders>', |
|
355 ]) |
|
356 self.assertQuerysetEqual(self.a4.publications.all(), |
|
357 ['<Publication: Science News>']) |
|
358 self.a4.publications = [self.p3.id] |
|
359 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
360 ['<Article: NASA finds intelligent life on Earth>']) |
|
361 self.assertQuerysetEqual(self.a4.publications.all(), |
|
362 ['<Publication: Science Weekly>']) |
|
363 |
|
364 def test_clear(self): |
|
365 # Relation sets can be cleared: |
|
366 self.p2.article_set.clear() |
|
367 self.assertQuerysetEqual(self.p2.article_set.all(), []) |
|
368 self.assertQuerysetEqual(self.a4.publications.all(), []) |
|
369 |
|
370 # And you can clear from the other end |
|
371 self.p2.article_set.add(self.a3, self.a4) |
|
372 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
373 [ |
|
374 '<Article: NASA finds intelligent life on Earth>', |
|
375 '<Article: Oxygen-free diet works wonders>', |
|
376 ]) |
|
377 self.assertQuerysetEqual(self.a4.publications.all(), |
|
378 [ |
|
379 '<Publication: Science News>', |
|
380 ]) |
|
381 self.a4.publications.clear() |
|
382 self.assertQuerysetEqual(self.a4.publications.all(), []) |
|
383 self.assertQuerysetEqual(self.p2.article_set.all(), |
|
384 ['<Article: NASA finds intelligent life on Earth>']) |