|
1 from datetime import datetime |
|
2 from django.test import TestCase |
|
3 from django.core.exceptions import FieldError |
|
4 from models import Article, Reporter |
|
5 |
|
6 class ManyToOneTests(TestCase): |
|
7 |
|
8 def setUp(self): |
|
9 # Create a few Reporters. |
|
10 self.r = Reporter(first_name='John', last_name='Smith', email='john@example.com') |
|
11 self.r.save() |
|
12 self.r2 = Reporter(first_name='Paul', last_name='Jones', email='paul@example.com') |
|
13 self.r2.save() |
|
14 # Create an Article. |
|
15 self.a = Article(id=None, headline="This is a test", |
|
16 pub_date=datetime(2005, 7, 27), reporter=self.r) |
|
17 self.a.save() |
|
18 |
|
19 def test_get(self): |
|
20 # Article objects have access to their related Reporter objects. |
|
21 r = self.a.reporter |
|
22 self.assertEqual(r.id, self.r.id) |
|
23 # These are strings instead of unicode strings because that's what was used in |
|
24 # the creation of this reporter (and we haven't refreshed the data from the |
|
25 # database, which always returns unicode strings). |
|
26 self.assertEqual((r.first_name, self.r.last_name), ('John', 'Smith')) |
|
27 |
|
28 def test_create(self): |
|
29 # You can also instantiate an Article by passing the Reporter's ID |
|
30 # instead of a Reporter object. |
|
31 a3 = Article(id=None, headline="Third article", |
|
32 pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) |
|
33 a3.save() |
|
34 self.assertEqual(a3.reporter.id, self.r.id) |
|
35 |
|
36 # Similarly, the reporter ID can be a string. |
|
37 a4 = Article(id=None, headline="Fourth article", |
|
38 pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) |
|
39 a4.save() |
|
40 self.assertEqual(repr(a4.reporter), "<Reporter: John Smith>") |
|
41 |
|
42 def test_add(self): |
|
43 # Create an Article via the Reporter object. |
|
44 new_article = self.r.article_set.create(headline="John's second story", |
|
45 pub_date=datetime(2005, 7, 29)) |
|
46 self.assertEqual(repr(new_article), "<Article: John's second story>") |
|
47 self.assertEqual(new_article.reporter.id, self.r.id) |
|
48 |
|
49 # Create a new article, and add it to the article set. |
|
50 new_article2 = Article(headline="Paul's story", pub_date=datetime(2006, 1, 17)) |
|
51 self.r.article_set.add(new_article2) |
|
52 self.assertEqual(new_article2.reporter.id, self.r.id) |
|
53 self.assertQuerysetEqual(self.r.article_set.all(), |
|
54 [ |
|
55 "<Article: John's second story>", |
|
56 "<Article: Paul's story>", |
|
57 "<Article: This is a test>", |
|
58 ]) |
|
59 |
|
60 # Add the same article to a different article set - check that it moves. |
|
61 self.r2.article_set.add(new_article2) |
|
62 self.assertEqual(new_article2.reporter.id, self.r2.id) |
|
63 self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"]) |
|
64 |
|
65 # Adding an object of the wrong type raises TypeError. |
|
66 self.assertRaises(TypeError, self.r.article_set.add, self.r2) |
|
67 self.assertQuerysetEqual(self.r.article_set.all(), |
|
68 [ |
|
69 "<Article: John's second story>", |
|
70 "<Article: This is a test>", |
|
71 ]) |
|
72 |
|
73 def test_assign(self): |
|
74 new_article = self.r.article_set.create(headline="John's second story", |
|
75 pub_date=datetime(2005, 7, 29)) |
|
76 new_article2 = self.r2.article_set.create(headline="Paul's story", |
|
77 pub_date=datetime(2006, 1, 17)) |
|
78 # Assign the article to the reporter directly using the descriptor. |
|
79 new_article2.reporter = self.r |
|
80 new_article2.save() |
|
81 self.assertEqual(repr(new_article2.reporter), "<Reporter: John Smith>") |
|
82 self.assertEqual(new_article2.reporter.id, self.r.id) |
|
83 self.assertQuerysetEqual(self.r.article_set.all(), [ |
|
84 "<Article: John's second story>", |
|
85 "<Article: Paul's story>", |
|
86 "<Article: This is a test>", |
|
87 ]) |
|
88 self.assertQuerysetEqual(self.r2.article_set.all(), []) |
|
89 # Set the article back again using set descriptor. |
|
90 self.r2.article_set = [new_article, new_article2] |
|
91 self.assertQuerysetEqual(self.r.article_set.all(), ["<Article: This is a test>"]) |
|
92 self.assertQuerysetEqual(self.r2.article_set.all(), |
|
93 [ |
|
94 "<Article: John's second story>", |
|
95 "<Article: Paul's story>", |
|
96 ]) |
|
97 |
|
98 # Funny case - assignment notation can only go so far; because the |
|
99 # ForeignKey cannot be null, existing members of the set must remain. |
|
100 self.r.article_set = [new_article] |
|
101 self.assertQuerysetEqual(self.r.article_set.all(), |
|
102 [ |
|
103 "<Article: John's second story>", |
|
104 "<Article: This is a test>", |
|
105 ]) |
|
106 self.assertQuerysetEqual(self.r2.article_set.all(), ["<Article: Paul's story>"]) |
|
107 # Reporter cannot be null - there should not be a clear or remove method |
|
108 self.assertFalse(hasattr(self.r2.article_set, 'remove')) |
|
109 self.assertFalse(hasattr(self.r2.article_set, 'clear')) |
|
110 |
|
111 def test_selects(self): |
|
112 new_article = self.r.article_set.create(headline="John's second story", |
|
113 pub_date=datetime(2005, 7, 29)) |
|
114 new_article2 = self.r2.article_set.create(headline="Paul's story", |
|
115 pub_date=datetime(2006, 1, 17)) |
|
116 # Reporter objects have access to their related Article objects. |
|
117 self.assertQuerysetEqual(self.r.article_set.all(), [ |
|
118 "<Article: John's second story>", |
|
119 "<Article: This is a test>", |
|
120 ]) |
|
121 self.assertQuerysetEqual(self.r.article_set.filter(headline__startswith='This'), |
|
122 ["<Article: This is a test>"]) |
|
123 self.assertEqual(self.r.article_set.count(), 2) |
|
124 self.assertEqual(self.r2.article_set.count(), 1) |
|
125 # Get articles by id |
|
126 self.assertQuerysetEqual(Article.objects.filter(id__exact=self.a.id), |
|
127 ["<Article: This is a test>"]) |
|
128 self.assertQuerysetEqual(Article.objects.filter(pk=self.a.id), |
|
129 ["<Article: This is a test>"]) |
|
130 # Query on an article property |
|
131 self.assertQuerysetEqual(Article.objects.filter(headline__startswith='This'), |
|
132 ["<Article: This is a test>"]) |
|
133 # The API automatically follows relationships as far as you need. |
|
134 # Use double underscores to separate relationships. |
|
135 # This works as many levels deep as you want. There's no limit. |
|
136 # Find all Articles for any Reporter whose first name is "John". |
|
137 self.assertQuerysetEqual(Article.objects.filter(reporter__first_name__exact='John'), |
|
138 [ |
|
139 "<Article: John's second story>", |
|
140 "<Article: This is a test>", |
|
141 ]) |
|
142 # Check that implied __exact also works |
|
143 self.assertQuerysetEqual(Article.objects.filter(reporter__first_name='John'), |
|
144 [ |
|
145 "<Article: John's second story>", |
|
146 "<Article: This is a test>", |
|
147 ]) |
|
148 # Query twice over the related field. |
|
149 self.assertQuerysetEqual( |
|
150 Article.objects.filter(reporter__first_name__exact='John', |
|
151 reporter__last_name__exact='Smith'), |
|
152 [ |
|
153 "<Article: John's second story>", |
|
154 "<Article: This is a test>", |
|
155 ]) |
|
156 # The underlying query only makes one join when a related table is referenced twice. |
|
157 queryset = Article.objects.filter(reporter__first_name__exact='John', |
|
158 reporter__last_name__exact='Smith') |
|
159 self.assertEqual(queryset.query.get_compiler(queryset.db).as_sql()[0].count('INNER JOIN'), 1) |
|
160 |
|
161 # The automatically joined table has a predictable name. |
|
162 self.assertQuerysetEqual( |
|
163 Article.objects.filter(reporter__first_name__exact='John').extra( |
|
164 where=["many_to_one_reporter.last_name='Smith'"]), |
|
165 [ |
|
166 "<Article: John's second story>", |
|
167 "<Article: This is a test>", |
|
168 ]) |
|
169 # ... and should work fine with the unicode that comes out of forms.Form.cleaned_data |
|
170 self.assertQuerysetEqual( |
|
171 Article.objects.filter(reporter__first_name__exact='John' |
|
172 ).extra(where=["many_to_one_reporter.last_name='%s'" % u'Smith']), |
|
173 [ |
|
174 "<Article: John's second story>", |
|
175 "<Article: This is a test>", |
|
176 ]) |
|
177 # Find all Articles for a Reporter. |
|
178 # Use direct ID check, pk check, and object comparison |
|
179 self.assertQuerysetEqual( |
|
180 Article.objects.filter(reporter__id__exact=self.r.id), |
|
181 [ |
|
182 "<Article: John's second story>", |
|
183 "<Article: This is a test>", |
|
184 ]) |
|
185 self.assertQuerysetEqual( |
|
186 Article.objects.filter(reporter__pk=self.r.id), |
|
187 [ |
|
188 "<Article: John's second story>", |
|
189 "<Article: This is a test>", |
|
190 ]) |
|
191 self.assertQuerysetEqual( |
|
192 Article.objects.filter(reporter=self.r.id), |
|
193 [ |
|
194 "<Article: John's second story>", |
|
195 "<Article: This is a test>", |
|
196 ]) |
|
197 self.assertQuerysetEqual( |
|
198 Article.objects.filter(reporter=self.r), |
|
199 [ |
|
200 "<Article: John's second story>", |
|
201 "<Article: This is a test>", |
|
202 ]) |
|
203 self.assertQuerysetEqual( |
|
204 Article.objects.filter(reporter__in=[self.r.id,self.r2.id]).distinct(), |
|
205 [ |
|
206 "<Article: John's second story>", |
|
207 "<Article: Paul's story>", |
|
208 "<Article: This is a test>", |
|
209 ]) |
|
210 self.assertQuerysetEqual( |
|
211 Article.objects.filter(reporter__in=[self.r,self.r2]).distinct(), |
|
212 [ |
|
213 "<Article: John's second story>", |
|
214 "<Article: Paul's story>", |
|
215 "<Article: This is a test>", |
|
216 ]) |
|
217 # You can also use a queryset instead of a literal list of instances. |
|
218 # The queryset must be reduced to a list of values using values(), |
|
219 # then converted into a query |
|
220 self.assertQuerysetEqual( |
|
221 Article.objects.filter( |
|
222 reporter__in=Reporter.objects.filter(first_name='John').values('pk').query |
|
223 ).distinct(), |
|
224 [ |
|
225 "<Article: John's second story>", |
|
226 "<Article: This is a test>", |
|
227 ]) |
|
228 # You need two underscores between "reporter" and "id" -- not one. |
|
229 self.assertRaises(FieldError, Article.objects.filter, reporter_id__exact=self.r.id) |
|
230 # You need to specify a comparison clause |
|
231 self.assertRaises(FieldError, Article.objects.filter, reporter_id=self.r.id) |
|
232 |
|
233 def test_reverse_selects(self): |
|
234 a3 = Article.objects.create(id=None, headline="Third article", |
|
235 pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) |
|
236 a4 = Article.objects.create(id=None, headline="Fourth article", |
|
237 pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) |
|
238 # Reporters can be queried |
|
239 self.assertQuerysetEqual(Reporter.objects.filter(id__exact=self.r.id), |
|
240 ["<Reporter: John Smith>"]) |
|
241 self.assertQuerysetEqual(Reporter.objects.filter(pk=self.r.id), |
|
242 ["<Reporter: John Smith>"]) |
|
243 self.assertQuerysetEqual(Reporter.objects.filter(first_name__startswith='John'), |
|
244 ["<Reporter: John Smith>"]) |
|
245 # Reporters can query in opposite direction of ForeignKey definition |
|
246 self.assertQuerysetEqual(Reporter.objects.filter(article__id__exact=self.a.id), |
|
247 ["<Reporter: John Smith>"]) |
|
248 self.assertQuerysetEqual(Reporter.objects.filter(article__pk=self.a.id), |
|
249 ["<Reporter: John Smith>"]) |
|
250 self.assertQuerysetEqual(Reporter.objects.filter(article=self.a.id), |
|
251 ["<Reporter: John Smith>"]) |
|
252 self.assertQuerysetEqual(Reporter.objects.filter(article=self.a), |
|
253 ["<Reporter: John Smith>"]) |
|
254 self.assertQuerysetEqual( |
|
255 Reporter.objects.filter(article__in=[self.a.id,a3.id]).distinct(), |
|
256 ["<Reporter: John Smith>"]) |
|
257 self.assertQuerysetEqual( |
|
258 Reporter.objects.filter(article__in=[self.a.id,a3]).distinct(), |
|
259 ["<Reporter: John Smith>"]) |
|
260 self.assertQuerysetEqual( |
|
261 Reporter.objects.filter(article__in=[self.a,a3]).distinct(), |
|
262 ["<Reporter: John Smith>"]) |
|
263 self.assertQuerysetEqual( |
|
264 Reporter.objects.filter(article__headline__startswith='T'), |
|
265 ["<Reporter: John Smith>", "<Reporter: John Smith>"]) |
|
266 self.assertQuerysetEqual( |
|
267 Reporter.objects.filter(article__headline__startswith='T').distinct(), |
|
268 ["<Reporter: John Smith>"]) |
|
269 |
|
270 # Counting in the opposite direction works in conjunction with distinct() |
|
271 self.assertEqual( |
|
272 Reporter.objects.filter(article__headline__startswith='T').count(), 2) |
|
273 self.assertEqual( |
|
274 Reporter.objects.filter(article__headline__startswith='T').distinct().count(), 1) |
|
275 |
|
276 # Queries can go round in circles. |
|
277 self.assertQuerysetEqual( |
|
278 Reporter.objects.filter(article__reporter__first_name__startswith='John'), |
|
279 [ |
|
280 "<Reporter: John Smith>", |
|
281 "<Reporter: John Smith>", |
|
282 "<Reporter: John Smith>", |
|
283 ]) |
|
284 self.assertQuerysetEqual( |
|
285 Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct(), |
|
286 ["<Reporter: John Smith>"]) |
|
287 self.assertQuerysetEqual( |
|
288 Reporter.objects.filter(article__reporter__exact=self.r).distinct(), |
|
289 ["<Reporter: John Smith>"]) |
|
290 |
|
291 # Check that implied __exact also works. |
|
292 self.assertQuerysetEqual( |
|
293 Reporter.objects.filter(article__reporter=self.r).distinct(), |
|
294 ["<Reporter: John Smith>"]) |
|
295 |
|
296 # It's possible to use values() calls across many-to-one relations. |
|
297 # (Note, too, that we clear the ordering here so as not to drag the |
|
298 # 'headline' field into the columns being used to determine uniqueness) |
|
299 d = {'reporter__first_name': u'John', 'reporter__last_name': u'Smith'} |
|
300 self.assertEqual([d], |
|
301 list(Article.objects.filter(reporter=self.r).distinct().order_by() |
|
302 .values('reporter__first_name', 'reporter__last_name'))) |
|
303 |
|
304 def test_select_related(self): |
|
305 # Check that Article.objects.select_related().dates() works properly when |
|
306 # there are multiple Articles with the same date but different foreign-key |
|
307 # objects (Reporters). |
|
308 r1 = Reporter.objects.create(first_name='Mike', last_name='Royko', email='royko@suntimes.com') |
|
309 r2 = Reporter.objects.create(first_name='John', last_name='Kass', email='jkass@tribune.com') |
|
310 a1 = Article.objects.create(headline='First', pub_date=datetime(1980, 4, 23), reporter=r1) |
|
311 a2 = Article.objects.create(headline='Second', pub_date=datetime(1980, 4, 23), reporter=r2) |
|
312 self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'day')), |
|
313 [ |
|
314 datetime(1980, 4, 23, 0, 0), |
|
315 datetime(2005, 7, 27, 0, 0), |
|
316 ]) |
|
317 self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'month')), |
|
318 [ |
|
319 datetime(1980, 4, 1, 0, 0), |
|
320 datetime(2005, 7, 1, 0, 0), |
|
321 ]) |
|
322 self.assertEqual(list(Article.objects.select_related().dates('pub_date', 'year')), |
|
323 [ |
|
324 datetime(1980, 1, 1, 0, 0), |
|
325 datetime(2005, 1, 1, 0, 0), |
|
326 ]) |
|
327 |
|
328 def test_delete(self): |
|
329 new_article = self.r.article_set.create(headline="John's second story", |
|
330 pub_date=datetime(2005, 7, 29)) |
|
331 new_article2 = self.r2.article_set.create(headline="Paul's story", |
|
332 pub_date=datetime(2006, 1, 17)) |
|
333 a3 = Article.objects.create(id=None, headline="Third article", |
|
334 pub_date=datetime(2005, 7, 27), reporter_id=self.r.id) |
|
335 a4 = Article.objects.create(id=None, headline="Fourth article", |
|
336 pub_date=datetime(2005, 7, 27), reporter_id=str(self.r.id)) |
|
337 # If you delete a reporter, his articles will be deleted. |
|
338 self.assertQuerysetEqual(Article.objects.all(), |
|
339 [ |
|
340 "<Article: Fourth article>", |
|
341 "<Article: John's second story>", |
|
342 "<Article: Paul's story>", |
|
343 "<Article: Third article>", |
|
344 "<Article: This is a test>", |
|
345 ]) |
|
346 self.assertQuerysetEqual(Reporter.objects.order_by('first_name'), |
|
347 [ |
|
348 "<Reporter: John Smith>", |
|
349 "<Reporter: Paul Jones>", |
|
350 ]) |
|
351 self.r2.delete() |
|
352 self.assertQuerysetEqual(Article.objects.all(), |
|
353 [ |
|
354 "<Article: Fourth article>", |
|
355 "<Article: John's second story>", |
|
356 "<Article: Third article>", |
|
357 "<Article: This is a test>", |
|
358 ]) |
|
359 self.assertQuerysetEqual(Reporter.objects.order_by('first_name'), |
|
360 ["<Reporter: John Smith>"]) |
|
361 # You can delete using a JOIN in the query. |
|
362 Reporter.objects.filter(article__headline__startswith='This').delete() |
|
363 self.assertQuerysetEqual(Reporter.objects.all(), []) |
|
364 self.assertQuerysetEqual(Article.objects.all(), []) |
|
365 |
|
366 def test_regression_12876(self): |
|
367 # Regression for #12876 -- Model methods that include queries that |
|
368 # recursive don't cause recursion depth problems under deepcopy. |
|
369 self.r.cached_query = Article.objects.filter(reporter=self.r) |
|
370 from copy import deepcopy |
|
371 self.assertEqual(repr(deepcopy(self.r)), "<Reporter: John Smith>") |