|
1 """ |
|
2 Regression tests for Model inheritance behaviour. |
|
3 """ |
|
4 |
|
5 import datetime |
|
6 from operator import attrgetter |
|
7 |
|
8 from django.test import TestCase |
|
9 |
|
10 from models import (Place, Restaurant, ItalianRestaurant, ParkingLot, |
|
11 ParkingLot2, ParkingLot3, Supplier, Wholesaler, Child, SelfRefParent, |
|
12 SelfRefChild, ArticleWithAuthor, M2MChild, QualityControl, DerivedM, |
|
13 Person, BirthdayParty, BachelorParty, MessyBachelorParty, |
|
14 InternalCertificationAudit) |
|
15 |
|
16 |
|
17 class ModelInheritanceTest(TestCase): |
|
18 def test_model_inheritance(self): |
|
19 # Regression for #7350, #7202 |
|
20 # Check that when you create a Parent object with a specific reference |
|
21 # to an existent child instance, saving the Parent doesn't duplicate |
|
22 # the child. This behaviour is only activated during a raw save - it |
|
23 # is mostly relevant to deserialization, but any sort of CORBA style |
|
24 # 'narrow()' API would require a similar approach. |
|
25 |
|
26 # Create a child-parent-grandparent chain |
|
27 place1 = Place( |
|
28 name="Guido's House of Pasta", |
|
29 address='944 W. Fullerton') |
|
30 place1.save_base(raw=True) |
|
31 restaurant = Restaurant( |
|
32 place_ptr=place1, |
|
33 serves_hot_dogs=True, |
|
34 serves_pizza=False) |
|
35 restaurant.save_base(raw=True) |
|
36 italian_restaurant = ItalianRestaurant( |
|
37 restaurant_ptr=restaurant, |
|
38 serves_gnocchi=True) |
|
39 italian_restaurant.save_base(raw=True) |
|
40 |
|
41 # Create a child-parent chain with an explicit parent link |
|
42 place2 = Place(name='Main St', address='111 Main St') |
|
43 place2.save_base(raw=True) |
|
44 park = ParkingLot(parent=place2, capacity=100) |
|
45 park.save_base(raw=True) |
|
46 |
|
47 # Check that no extra parent objects have been created. |
|
48 places = list(Place.objects.all()) |
|
49 self.assertEqual(places, [place1, place2]) |
|
50 |
|
51 dicts = list(Restaurant.objects.values('name','serves_hot_dogs')) |
|
52 self.assertEqual(dicts, [{ |
|
53 'name': u"Guido's House of Pasta", |
|
54 'serves_hot_dogs': True |
|
55 }]) |
|
56 |
|
57 dicts = list(ItalianRestaurant.objects.values( |
|
58 'name','serves_hot_dogs','serves_gnocchi')) |
|
59 self.assertEqual(dicts, [{ |
|
60 'name': u"Guido's House of Pasta", |
|
61 'serves_gnocchi': True, |
|
62 'serves_hot_dogs': True, |
|
63 }]) |
|
64 |
|
65 dicts = list(ParkingLot.objects.values('name','capacity')) |
|
66 self.assertEqual(dicts, [{ |
|
67 'capacity': 100, |
|
68 'name': u'Main St', |
|
69 }]) |
|
70 |
|
71 # You can also update objects when using a raw save. |
|
72 place1.name = "Guido's All New House of Pasta" |
|
73 place1.save_base(raw=True) |
|
74 |
|
75 restaurant.serves_hot_dogs = False |
|
76 restaurant.save_base(raw=True) |
|
77 |
|
78 italian_restaurant.serves_gnocchi = False |
|
79 italian_restaurant.save_base(raw=True) |
|
80 |
|
81 place2.name='Derelict lot' |
|
82 place2.save_base(raw=True) |
|
83 |
|
84 park.capacity = 50 |
|
85 park.save_base(raw=True) |
|
86 |
|
87 # No extra parent objects after an update, either. |
|
88 places = list(Place.objects.all()) |
|
89 self.assertEqual(places, [place2, place1]) |
|
90 self.assertEqual(places[0].name, 'Derelict lot') |
|
91 self.assertEqual(places[1].name, "Guido's All New House of Pasta") |
|
92 |
|
93 dicts = list(Restaurant.objects.values('name','serves_hot_dogs')) |
|
94 self.assertEqual(dicts, [{ |
|
95 'name': u"Guido's All New House of Pasta", |
|
96 'serves_hot_dogs': False, |
|
97 }]) |
|
98 |
|
99 dicts = list(ItalianRestaurant.objects.values( |
|
100 'name', 'serves_hot_dogs', 'serves_gnocchi')) |
|
101 self.assertEqual(dicts, [{ |
|
102 'name': u"Guido's All New House of Pasta", |
|
103 'serves_gnocchi': False, |
|
104 'serves_hot_dogs': False, |
|
105 }]) |
|
106 |
|
107 dicts = list(ParkingLot.objects.values('name','capacity')) |
|
108 self.assertEqual(dicts, [{ |
|
109 'capacity': 50, |
|
110 'name': u'Derelict lot', |
|
111 }]) |
|
112 |
|
113 # If you try to raw_save a parent attribute onto a child object, |
|
114 # the attribute will be ignored. |
|
115 |
|
116 italian_restaurant.name = "Lorenzo's Pasta Hut" |
|
117 italian_restaurant.save_base(raw=True) |
|
118 |
|
119 # Note that the name has not changed |
|
120 # - name is an attribute of Place, not ItalianRestaurant |
|
121 dicts = list(ItalianRestaurant.objects.values( |
|
122 'name','serves_hot_dogs','serves_gnocchi')) |
|
123 self.assertEqual(dicts, [{ |
|
124 'name': u"Guido's All New House of Pasta", |
|
125 'serves_gnocchi': False, |
|
126 'serves_hot_dogs': False, |
|
127 }]) |
|
128 |
|
129 def test_issue_7105(self): |
|
130 # Regressions tests for #7105: dates() queries should be able to use |
|
131 # fields from the parent model as easily as the child. |
|
132 obj = Child.objects.create( |
|
133 name='child', |
|
134 created=datetime.datetime(2008, 6, 26, 17, 0, 0)) |
|
135 dates = list(Child.objects.dates('created', 'month')) |
|
136 self.assertEqual(dates, [datetime.datetime(2008, 6, 1, 0, 0)]) |
|
137 |
|
138 def test_issue_7276(self): |
|
139 # Regression test for #7276: calling delete() on a model with |
|
140 # multi-table inheritance should delete the associated rows from any |
|
141 # ancestor tables, as well as any descendent objects. |
|
142 place1 = Place( |
|
143 name="Guido's House of Pasta", |
|
144 address='944 W. Fullerton') |
|
145 place1.save_base(raw=True) |
|
146 restaurant = Restaurant( |
|
147 place_ptr=place1, |
|
148 serves_hot_dogs=True, |
|
149 serves_pizza=False) |
|
150 restaurant.save_base(raw=True) |
|
151 italian_restaurant = ItalianRestaurant( |
|
152 restaurant_ptr=restaurant, |
|
153 serves_gnocchi=True) |
|
154 italian_restaurant.save_base(raw=True) |
|
155 |
|
156 ident = ItalianRestaurant.objects.all()[0].id |
|
157 self.assertEqual(Place.objects.get(pk=ident), place1) |
|
158 xx = Restaurant.objects.create( |
|
159 name='a', |
|
160 address='xx', |
|
161 serves_hot_dogs=True, |
|
162 serves_pizza=False) |
|
163 |
|
164 # This should delete both Restuarants, plus the related places, plus |
|
165 # the ItalianRestaurant. |
|
166 Restaurant.objects.all().delete() |
|
167 |
|
168 self.assertRaises( |
|
169 Place.DoesNotExist, |
|
170 Place.objects.get, |
|
171 pk=ident) |
|
172 self.assertRaises( |
|
173 ItalianRestaurant.DoesNotExist, |
|
174 ItalianRestaurant.objects.get, |
|
175 pk=ident) |
|
176 |
|
177 def test_issue_6755(self): |
|
178 """ |
|
179 Regression test for #6755 |
|
180 """ |
|
181 r = Restaurant(serves_pizza=False) |
|
182 r.save() |
|
183 self.assertEqual(r.id, r.place_ptr_id) |
|
184 orig_id = r.id |
|
185 r = Restaurant(place_ptr_id=orig_id, serves_pizza=True) |
|
186 r.save() |
|
187 self.assertEqual(r.id, orig_id) |
|
188 self.assertEqual(r.id, r.place_ptr_id) |
|
189 |
|
190 def test_issue_7488(self): |
|
191 # Regression test for #7488. This looks a little crazy, but it's the |
|
192 # equivalent of what the admin interface has to do for the edit-inline |
|
193 # case. |
|
194 suppliers = Supplier.objects.filter( |
|
195 restaurant=Restaurant(name='xx', address='yy')) |
|
196 suppliers = list(suppliers) |
|
197 self.assertEqual(suppliers, []) |
|
198 |
|
199 def test_issue_11764(self): |
|
200 """ |
|
201 Regression test for #11764 |
|
202 """ |
|
203 wholesalers = list(Wholesaler.objects.all().select_related()) |
|
204 self.assertEqual(wholesalers, []) |
|
205 |
|
206 def test_issue_7853(self): |
|
207 """ |
|
208 Regression test for #7853 |
|
209 If the parent class has a self-referential link, make sure that any |
|
210 updates to that link via the child update the right table. |
|
211 """ |
|
212 obj = SelfRefChild.objects.create(child_data=37, parent_data=42) |
|
213 obj.delete() |
|
214 |
|
215 def test_get_next_previous_by_date(self): |
|
216 """ |
|
217 Regression tests for #8076 |
|
218 get_(next/previous)_by_date should work |
|
219 """ |
|
220 c1 = ArticleWithAuthor( |
|
221 headline='ArticleWithAuthor 1', |
|
222 author="Person 1", |
|
223 pub_date=datetime.datetime(2005, 8, 1, 3, 0)) |
|
224 c1.save() |
|
225 c2 = ArticleWithAuthor( |
|
226 headline='ArticleWithAuthor 2', |
|
227 author="Person 2", |
|
228 pub_date=datetime.datetime(2005, 8, 1, 10, 0)) |
|
229 c2.save() |
|
230 c3 = ArticleWithAuthor( |
|
231 headline='ArticleWithAuthor 3', |
|
232 author="Person 3", |
|
233 pub_date=datetime.datetime(2005, 8, 2)) |
|
234 c3.save() |
|
235 |
|
236 self.assertEqual(c1.get_next_by_pub_date(), c2) |
|
237 self.assertEqual(c2.get_next_by_pub_date(), c3) |
|
238 self.assertRaises( |
|
239 ArticleWithAuthor.DoesNotExist, |
|
240 c3.get_next_by_pub_date) |
|
241 self.assertEqual(c3.get_previous_by_pub_date(), c2) |
|
242 self.assertEqual(c2.get_previous_by_pub_date(), c1) |
|
243 self.assertRaises( |
|
244 ArticleWithAuthor.DoesNotExist, |
|
245 c1.get_previous_by_pub_date) |
|
246 |
|
247 def test_inherited_fields(self): |
|
248 """ |
|
249 Regression test for #8825 and #9390 |
|
250 Make sure all inherited fields (esp. m2m fields, in this case) appear |
|
251 on the child class. |
|
252 """ |
|
253 m2mchildren = list(M2MChild.objects.filter(articles__isnull=False)) |
|
254 self.assertEqual(m2mchildren, []) |
|
255 |
|
256 # Ordering should not include any database column more than once (this |
|
257 # is most likely to ocurr naturally with model inheritance, so we |
|
258 # check it here). Regression test for #9390. This necessarily pokes at |
|
259 # the SQL string for the query, since the duplicate problems are only |
|
260 # apparent at that late stage. |
|
261 qs = ArticleWithAuthor.objects.order_by('pub_date', 'pk') |
|
262 sql = qs.query.get_compiler(qs.db).as_sql()[0] |
|
263 fragment = sql[sql.find('ORDER BY'):] |
|
264 pos = fragment.find('pub_date') |
|
265 self.assertEqual(fragment.find('pub_date', pos + 1), -1) |
|
266 |
|
267 def test_queryset_update_on_parent_model(self): |
|
268 """ |
|
269 Regression test for #10362 |
|
270 It is possible to call update() and only change a field in |
|
271 an ancestor model. |
|
272 """ |
|
273 article = ArticleWithAuthor.objects.create( |
|
274 author="fred", |
|
275 headline="Hey there!", |
|
276 pub_date=datetime.datetime(2009, 3, 1, 8, 0, 0)) |
|
277 update = ArticleWithAuthor.objects.filter( |
|
278 author="fred").update(headline="Oh, no!") |
|
279 self.assertEqual(update, 1) |
|
280 update = ArticleWithAuthor.objects.filter( |
|
281 pk=article.pk).update(headline="Oh, no!") |
|
282 self.assertEqual(update, 1) |
|
283 |
|
284 derivedm1 = DerivedM.objects.create( |
|
285 customPK=44, |
|
286 base_name="b1", |
|
287 derived_name="d1") |
|
288 self.assertEqual(derivedm1.customPK, 44) |
|
289 self.assertEqual(derivedm1.base_name, 'b1') |
|
290 self.assertEqual(derivedm1.derived_name, 'd1') |
|
291 derivedms = list(DerivedM.objects.all()) |
|
292 self.assertEqual(derivedms, [derivedm1]) |
|
293 |
|
294 def test_use_explicit_o2o_to_parent_as_pk(self): |
|
295 """ |
|
296 Regression tests for #10406 |
|
297 If there's a one-to-one link between a child model and the parent and |
|
298 no explicit pk declared, we can use the one-to-one link as the pk on |
|
299 the child. |
|
300 """ |
|
301 self.assertEqual(ParkingLot2._meta.pk.name, "parent") |
|
302 |
|
303 # However, the connector from child to parent need not be the pk on |
|
304 # the child at all. |
|
305 self.assertEqual(ParkingLot3._meta.pk.name, "primary_key") |
|
306 # the child->parent link |
|
307 self.assertEqual( |
|
308 ParkingLot3._meta.get_ancestor_link(Place).name, |
|
309 "parent") |
|
310 |
|
311 def test_all_fields_from_abstract_base_class(self): |
|
312 """ |
|
313 Regression tests for #7588 |
|
314 """ |
|
315 # All fields from an ABC, including those inherited non-abstractly |
|
316 # should be available on child classes (#7588). Creating this instance |
|
317 # should work without error. |
|
318 QualityControl.objects.create( |
|
319 headline="Problems in Django", |
|
320 pub_date=datetime.datetime.now(), |
|
321 quality=10, |
|
322 assignee="adrian") |
|
323 |
|
324 def test_abstract_base_class_m2m_relation_inheritance(self): |
|
325 # Check that many-to-many relations defined on an abstract base class |
|
326 # are correctly inherited (and created) on the child class. |
|
327 p1 = Person.objects.create(name='Alice') |
|
328 p2 = Person.objects.create(name='Bob') |
|
329 p3 = Person.objects.create(name='Carol') |
|
330 p4 = Person.objects.create(name='Dave') |
|
331 |
|
332 birthday = BirthdayParty.objects.create( |
|
333 name='Birthday party for Alice') |
|
334 birthday.attendees = [p1, p3] |
|
335 |
|
336 bachelor = BachelorParty.objects.create(name='Bachelor party for Bob') |
|
337 bachelor.attendees = [p2, p4] |
|
338 |
|
339 parties = list(p1.birthdayparty_set.all()) |
|
340 self.assertEqual(parties, [birthday]) |
|
341 |
|
342 parties = list(p1.bachelorparty_set.all()) |
|
343 self.assertEqual(parties, []) |
|
344 |
|
345 parties = list(p2.bachelorparty_set.all()) |
|
346 self.assertEqual(parties, [bachelor]) |
|
347 |
|
348 # Check that a subclass of a subclass of an abstract model doesn't get |
|
349 # it's own accessor. |
|
350 self.assertFalse(hasattr(p2, 'messybachelorparty_set')) |
|
351 |
|
352 # ... but it does inherit the m2m from it's parent |
|
353 messy = MessyBachelorParty.objects.create( |
|
354 name='Bachelor party for Dave') |
|
355 messy.attendees = [p4] |
|
356 messy_parent = messy.bachelorparty_ptr |
|
357 |
|
358 parties = list(p4.bachelorparty_set.all()) |
|
359 self.assertEqual(parties, [bachelor, messy_parent]) |
|
360 |
|
361 def test_11369(self): |
|
362 """ |
|
363 verbose_name_plural correctly inherited from ABC if inheritance chain |
|
364 includes an abstract model. |
|
365 """ |
|
366 # Regression test for #11369: verbose_name_plural should be inherited |
|
367 # from an ABC even when there are one or more intermediate |
|
368 # abstract models in the inheritance chain, for consistency with |
|
369 # verbose_name. |
|
370 self.assertEquals( |
|
371 InternalCertificationAudit._meta.verbose_name_plural, |
|
372 u'Audits' |
|
373 ) |
|
374 |
|
375 def test_inherited_nullable_exclude(self): |
|
376 obj = SelfRefChild.objects.create(child_data=37, parent_data=42) |
|
377 self.assertQuerysetEqual( |
|
378 SelfRefParent.objects.exclude(self_data=72), [ |
|
379 obj.pk |
|
380 ], |
|
381 attrgetter("pk") |
|
382 ) |
|
383 self.assertQuerysetEqual( |
|
384 SelfRefChild.objects.exclude(self_data=72), [ |
|
385 obj.pk |
|
386 ], |
|
387 attrgetter("pk") |
|
388 ) |