diff --git a/tests/regressiontests/model_inheritance_regress/models.py b/tests/regressiontests/model_inheritance_regress/models.py index 15330bd5ee..08a51bdf69 100644 --- a/tests/regressiontests/model_inheritance_regress/models.py +++ b/tests/regressiontests/model_inheritance_regress/models.py @@ -1,7 +1,3 @@ -""" -Regression tests for Model inheritance behaviour. -""" - import datetime from django.db import models @@ -136,255 +132,3 @@ class BachelorParty(AbstractEvent): class MessyBachelorParty(BachelorParty): pass - -__test__ = {'API_TESTS':""" -# Regression for #7350, #7202 -# Check that when you create a Parent object with a specific reference to an -# existent child instance, saving the Parent doesn't duplicate the child. This -# behaviour is only activated during a raw save - it is mostly relevant to -# deserialization, but any sort of CORBA style 'narrow()' API would require a -# similar approach. - -# Create a child-parent-grandparent chain ->>> place1 = Place(name="Guido's House of Pasta", address='944 W. Fullerton') ->>> place1.save_base(raw=True) ->>> restaurant = Restaurant(place_ptr=place1, serves_hot_dogs=True, serves_pizza=False) ->>> restaurant.save_base(raw=True) ->>> italian_restaurant = ItalianRestaurant(restaurant_ptr=restaurant, serves_gnocchi=True) ->>> italian_restaurant.save_base(raw=True) - -# Create a child-parent chain with an explicit parent link ->>> place2 = Place(name='Main St', address='111 Main St') ->>> place2.save_base(raw=True) ->>> park = ParkingLot(parent=place2, capacity=100) ->>> park.save_base(raw=True) - -# Check that no extra parent objects have been created. ->>> Place.objects.all() -[, ] - ->>> dicts = Restaurant.objects.values('name','serves_hot_dogs') ->>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's House of Pasta"), ('serves_hot_dogs', True)]] -True - ->>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi') ->>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's House of Pasta"), ('serves_gnocchi', True), ('serves_hot_dogs', True)]] -True - ->>> dicts = ParkingLot.objects.values('name','capacity') ->>> [sorted(d.items()) for d in dicts] -[[('capacity', 100), ('name', u'Main St')]] - -# You can also update objects when using a raw save. ->>> place1.name = "Guido's All New House of Pasta" ->>> place1.save_base(raw=True) - ->>> restaurant.serves_hot_dogs = False ->>> restaurant.save_base(raw=True) - ->>> italian_restaurant.serves_gnocchi = False ->>> italian_restaurant.save_base(raw=True) - ->>> place2.name='Derelict lot' ->>> place2.save_base(raw=True) - ->>> park.capacity = 50 ->>> park.save_base(raw=True) - -# No extra parent objects after an update, either. ->>> Place.objects.all() -[, ] - ->>> dicts = Restaurant.objects.values('name','serves_hot_dogs') ->>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's All New House of Pasta"), ('serves_hot_dogs', False)]] -True - ->>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi') ->>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]] -True - ->>> dicts = ParkingLot.objects.values('name','capacity') ->>> [sorted(d.items()) for d in dicts] -[[('capacity', 50), ('name', u'Derelict lot')]] - -# If you try to raw_save a parent attribute onto a child object, -# the attribute will be ignored. - ->>> italian_restaurant.name = "Lorenzo's Pasta Hut" ->>> italian_restaurant.save_base(raw=True) - -# Note that the name has not changed -# - name is an attribute of Place, not ItalianRestaurant ->>> dicts = ItalianRestaurant.objects.values('name','serves_hot_dogs','serves_gnocchi') ->>> [sorted(d.items()) for d in dicts] == [[('name', u"Guido's All New House of Pasta"), ('serves_gnocchi', False), ('serves_hot_dogs', False)]] -True - -# Regressions tests for #7105: dates() queries should be able to use fields -# from the parent model as easily as the child. ->>> obj = Child.objects.create(name='child', created=datetime.datetime(2008, 6, 26, 17, 0, 0)) ->>> Child.objects.dates('created', 'month') -[datetime.datetime(2008, 6, 1, 0, 0)] - -# Regression test for #7276: calling delete() on a model with multi-table -# inheritance should delete the associated rows from any ancestor tables, as -# well as any descendent objects. - ->>> ident = ItalianRestaurant.objects.all()[0].id ->>> Place.objects.get(pk=ident) - ->>> xx = Restaurant.objects.create(name='a', address='xx', serves_hot_dogs=True, serves_pizza=False) - -# This should delete both Restuarants, plus the related places, plus the ItalianRestaurant. ->>> Restaurant.objects.all().delete() - ->>> Place.objects.get(pk=ident) -Traceback (most recent call last): -... -DoesNotExist: Place matching query does not exist. - ->>> ItalianRestaurant.objects.get(pk=ident) -Traceback (most recent call last): -... -DoesNotExist: ItalianRestaurant matching query does not exist. - -# Regression test for #6755 ->>> r = Restaurant(serves_pizza=False) ->>> r.save() ->>> r.id == r.place_ptr_id -True ->>> orig_id = r.id ->>> r = Restaurant(place_ptr_id=orig_id, serves_pizza=True) ->>> r.save() ->>> r.id == orig_id -True ->>> r.id == r.place_ptr_id -True - -# Regression test for #7488. This looks a little crazy, but it's the equivalent -# of what the admin interface has to do for the edit-inline case. ->>> Supplier.objects.filter(restaurant=Restaurant(name='xx', address='yy')) -[] - -# Regression test for #11764. ->>> for w in Wholesaler.objects.all().select_related(): -... print w - -# Regression test for #7853 -# If the parent class has a self-referential link, make sure that any updates -# to that link via the child update the right table. - ->>> obj = SelfRefChild.objects.create(child_data=37, parent_data=42) ->>> obj.delete() - -# Regression tests for #8076 - get_(next/previous)_by_date should work. ->>> c1 = ArticleWithAuthor(headline='ArticleWithAuthor 1', author="Person 1", pub_date=datetime.datetime(2005, 8, 1, 3, 0)) ->>> c1.save() ->>> c2 = ArticleWithAuthor(headline='ArticleWithAuthor 2', author="Person 2", pub_date=datetime.datetime(2005, 8, 1, 10, 0)) ->>> c2.save() ->>> c3 = ArticleWithAuthor(headline='ArticleWithAuthor 3', author="Person 3", pub_date=datetime.datetime(2005, 8, 2)) ->>> c3.save() - ->>> c1.get_next_by_pub_date() - ->>> c2.get_next_by_pub_date() - ->>> c3.get_next_by_pub_date() -Traceback (most recent call last): - ... -DoesNotExist: ArticleWithAuthor matching query does not exist. ->>> c3.get_previous_by_pub_date() - ->>> c2.get_previous_by_pub_date() - ->>> c1.get_previous_by_pub_date() -Traceback (most recent call last): - ... -DoesNotExist: ArticleWithAuthor matching query does not exist. - -# Regression test for #8825: Make sure all inherited fields (esp. m2m fields, in -# this case) appear on the child class. ->>> M2MChild.objects.filter(articles__isnull=False) -[] - -# All fields from an ABC, including those inherited non-abstractly should be -# available on child classes (#7588). Creating this instance should work -# without error. ->>> _ = QualityControl.objects.create(headline="Problems in Django", pub_date=datetime.datetime.now(), quality=10, assignee="adrian") - -# Ordering should not include any database column more than once (this is most -# likely to ocurr naturally with model inheritance, so we check it here). -# Regression test for #9390. This necessarily pokes at the SQL string for the -# query, since the duplicate problems are only apparent at that late stage. ->>> qs = ArticleWithAuthor.objects.order_by('pub_date', 'pk') ->>> sql = qs.query.get_compiler(qs.db).as_sql()[0] ->>> fragment = sql[sql.find('ORDER BY'):] ->>> pos = fragment.find('pub_date') ->>> fragment.find('pub_date', pos + 1) == -1 -True - -# It is possible to call update() and only change a field in an ancestor model -# (regression test for #10362). ->>> article = ArticleWithAuthor.objects.create(author="fred", headline="Hey there!", pub_date = datetime.datetime(2009, 3, 1, 8, 0, 0)) ->>> ArticleWithAuthor.objects.filter(author="fred").update(headline="Oh, no!") -1 ->>> ArticleWithAuthor.objects.filter(pk=article.pk).update(headline="Oh, no!") -1 - ->>> DerivedM.objects.create(customPK=44, base_name="b1", derived_name="d1") - ->>> DerivedM.objects.all() -[] - -# Regression tests for #10406 - -# If there's a one-to-one link between a child model and the parent and no -# explicit pk declared, we can use the one-to-one link as the pk on the child. -# The ParkingLot2 model shows this behaviour. ->>> ParkingLot2._meta.pk.name -"parent" - -# However, the connector from child to parent need not be the pk on the child -# at all. ->>> ParkingLot3._meta.pk.name -"primary_key" ->>> ParkingLot3._meta.get_ancestor_link(Place).name # the child->parent link -"parent" - -# Check that many-to-many relations defined on an abstract base class -# are correctly inherited (and created) on the child class. ->>> p1 = Person.objects.create(name='Alice') ->>> p2 = Person.objects.create(name='Bob') ->>> p3 = Person.objects.create(name='Carol') ->>> p4 = Person.objects.create(name='Dave') - ->>> birthday = BirthdayParty.objects.create(name='Birthday party for Alice') ->>> birthday.attendees = [p1, p3] - ->>> bachelor = BachelorParty.objects.create(name='Bachelor party for Bob') ->>> bachelor.attendees = [p2, p4] - ->>> print p1.birthdayparty_set.all() -[] - ->>> print p1.bachelorparty_set.all() -[] - ->>> print p2.bachelorparty_set.all() -[] - -# Check that a subclass of a subclass of an abstract model -# doesn't get it's own accessor. ->>> p2.messybachelorparty_set.all() -Traceback (most recent call last): -... -AttributeError: 'Person' object has no attribute 'messybachelorparty_set' - -# ... but it does inherit the m2m from it's parent ->>> messy = MessyBachelorParty.objects.create(name='Bachelor party for Dave') ->>> messy.attendees = [p4] - ->>> p4.bachelorparty_set.all() -[, ] - -"""} - diff --git a/tests/regressiontests/model_inheritance_regress/tests.py b/tests/regressiontests/model_inheritance_regress/tests.py new file mode 100644 index 0000000000..012cd67325 --- /dev/null +++ b/tests/regressiontests/model_inheritance_regress/tests.py @@ -0,0 +1,355 @@ +""" +Regression tests for Model inheritance behaviour. +""" + +import datetime +from django.test import TestCase +from regressiontests.model_inheritance_regress.models import ( + Place, Restaurant, ItalianRestaurant, ParkingLot, ParkingLot2, + ParkingLot3, Supplier, Wholesaler, Child, SelfRefChild, ArticleWithAuthor, + M2MChild, QualityControl, DerivedM, Person, BirthdayParty, BachelorParty, + MessyBachelorParty) + +class ModelInheritanceTest(TestCase): + def test_model_inheritance(self): + # Regression for #7350, #7202 + # Check that when you create a Parent object with a specific reference + # to an existent child instance, saving the Parent doesn't duplicate + # the child. This behaviour is only activated during a raw save - it + # is mostly relevant to deserialization, but any sort of CORBA style + # 'narrow()' API would require a similar approach. + + # Create a child-parent-grandparent chain + place1 = Place( + name="Guido's House of Pasta", + address='944 W. Fullerton') + place1.save_base(raw=True) + restaurant = Restaurant( + place_ptr=place1, + serves_hot_dogs=True, + serves_pizza=False) + restaurant.save_base(raw=True) + italian_restaurant = ItalianRestaurant( + restaurant_ptr=restaurant, + serves_gnocchi=True) + italian_restaurant.save_base(raw=True) + + # Create a child-parent chain with an explicit parent link + place2 = Place(name='Main St', address='111 Main St') + place2.save_base(raw=True) + park = ParkingLot(parent=place2, capacity=100) + park.save_base(raw=True) + + # Check that no extra parent objects have been created. + places = list(Place.objects.all()) + self.assertEqual(places, [place1, place2]) + + dicts = list(Restaurant.objects.values('name','serves_hot_dogs')) + self.assertEqual(dicts, [{ + 'name': u"Guido's House of Pasta", + 'serves_hot_dogs': True + }]) + + dicts = list(ItalianRestaurant.objects.values( + 'name','serves_hot_dogs','serves_gnocchi')) + self.assertEqual(dicts, [{ + 'name': u"Guido's House of Pasta", + 'serves_gnocchi': True, + 'serves_hot_dogs': True, + }]) + + dicts = list(ParkingLot.objects.values('name','capacity')) + self.assertEqual(dicts, [{ + 'capacity': 100, + 'name': u'Main St', + }]) + + # You can also update objects when using a raw save. + place1.name = "Guido's All New House of Pasta" + place1.save_base(raw=True) + + restaurant.serves_hot_dogs = False + restaurant.save_base(raw=True) + + italian_restaurant.serves_gnocchi = False + italian_restaurant.save_base(raw=True) + + place2.name='Derelict lot' + place2.save_base(raw=True) + + park.capacity = 50 + park.save_base(raw=True) + + # No extra parent objects after an update, either. + places = list(Place.objects.all()) + self.assertEqual(places, [place2, place1]) + self.assertEqual(places[0].name, 'Derelict lot') + self.assertEqual(places[1].name, "Guido's All New House of Pasta") + + dicts = list(Restaurant.objects.values('name','serves_hot_dogs')) + self.assertEqual(dicts, [{ + 'name': u"Guido's All New House of Pasta", + 'serves_hot_dogs': False, + }]) + + dicts = list(ItalianRestaurant.objects.values( + 'name', 'serves_hot_dogs', 'serves_gnocchi')) + self.assertEqual(dicts, [{ + 'name': u"Guido's All New House of Pasta", + 'serves_gnocchi': False, + 'serves_hot_dogs': False, + }]) + + dicts = list(ParkingLot.objects.values('name','capacity')) + self.assertEqual(dicts, [{ + 'capacity': 50, + 'name': u'Derelict lot', + }]) + + # If you try to raw_save a parent attribute onto a child object, + # the attribute will be ignored. + + italian_restaurant.name = "Lorenzo's Pasta Hut" + italian_restaurant.save_base(raw=True) + + # Note that the name has not changed + # - name is an attribute of Place, not ItalianRestaurant + dicts = list(ItalianRestaurant.objects.values( + 'name','serves_hot_dogs','serves_gnocchi')) + self.assertEqual(dicts, [{ + 'name': u"Guido's All New House of Pasta", + 'serves_gnocchi': False, + 'serves_hot_dogs': False, + }]) + + def test_issue_7105(self): + # Regressions tests for #7105: dates() queries should be able to use + # fields from the parent model as easily as the child. + obj = Child.objects.create( + name='child', + created=datetime.datetime(2008, 6, 26, 17, 0, 0)) + dates = list(Child.objects.dates('created', 'month')) + self.assertEqual(dates, [datetime.datetime(2008, 6, 1, 0, 0)]) + + def test_issue_7276(self): + # Regression test for #7276: calling delete() on a model with + # multi-table inheritance should delete the associated rows from any + # ancestor tables, as well as any descendent objects. + place1 = Place( + name="Guido's House of Pasta", + address='944 W. Fullerton') + place1.save_base(raw=True) + restaurant = Restaurant( + place_ptr=place1, + serves_hot_dogs=True, + serves_pizza=False) + restaurant.save_base(raw=True) + italian_restaurant = ItalianRestaurant( + restaurant_ptr=restaurant, + serves_gnocchi=True) + italian_restaurant.save_base(raw=True) + + ident = ItalianRestaurant.objects.all()[0].id + self.assertEqual(Place.objects.get(pk=ident), place1) + xx = Restaurant.objects.create( + name='a', + address='xx', + serves_hot_dogs=True, + serves_pizza=False) + + # This should delete both Restuarants, plus the related places, plus + # the ItalianRestaurant. + Restaurant.objects.all().delete() + + self.assertRaises( + Place.DoesNotExist, + Place.objects.get, + pk=ident) + self.assertRaises( + ItalianRestaurant.DoesNotExist, + ItalianRestaurant.objects.get, + pk=ident) + + def test_issue_6755(self): + """ + Regression test for #6755 + """ + r = Restaurant(serves_pizza=False) + r.save() + self.assertEqual(r.id, r.place_ptr_id) + orig_id = r.id + r = Restaurant(place_ptr_id=orig_id, serves_pizza=True) + r.save() + self.assertEqual(r.id, orig_id) + self.assertEqual(r.id, r.place_ptr_id) + + def test_issue_7488(self): + # Regression test for #7488. This looks a little crazy, but it's the + # equivalent of what the admin interface has to do for the edit-inline + # case. + suppliers = Supplier.objects.filter( + restaurant=Restaurant(name='xx', address='yy')) + suppliers = list(suppliers) + self.assertEqual(suppliers, []) + + def test_issue_11764(self): + """ + Regression test for #11764 + """ + wholesalers = list(Wholesaler.objects.all().select_related()) + self.assertEqual(wholesalers, []) + + def test_issue_7853(self): + """ + Regression test for #7853 + If the parent class has a self-referential link, make sure that any + updates to that link via the child update the right table. + """ + obj = SelfRefChild.objects.create(child_data=37, parent_data=42) + obj.delete() + + def test_get_next_previous_by_date(self): + """ + Regression tests for #8076 + get_(next/previous)_by_date should work + """ + c1 = ArticleWithAuthor( + headline='ArticleWithAuthor 1', + author="Person 1", + pub_date=datetime.datetime(2005, 8, 1, 3, 0)) + c1.save() + c2 = ArticleWithAuthor( + headline='ArticleWithAuthor 2', + author="Person 2", + pub_date=datetime.datetime(2005, 8, 1, 10, 0)) + c2.save() + c3 = ArticleWithAuthor( + headline='ArticleWithAuthor 3', + author="Person 3", + pub_date=datetime.datetime(2005, 8, 2)) + c3.save() + + self.assertEqual(c1.get_next_by_pub_date(), c2) + self.assertEqual(c2.get_next_by_pub_date(), c3) + self.assertRaises( + ArticleWithAuthor.DoesNotExist, + c3.get_next_by_pub_date) + self.assertEqual(c3.get_previous_by_pub_date(), c2) + self.assertEqual(c2.get_previous_by_pub_date(), c1) + self.assertRaises( + ArticleWithAuthor.DoesNotExist, + c1.get_previous_by_pub_date) + + def test_inherited_fields(self): + """ + Regression test for #8825 and #9390 + Make sure all inherited fields (esp. m2m fields, in this case) appear + on the child class. + """ + m2mchildren = list(M2MChild.objects.filter(articles__isnull=False)) + self.assertEqual(m2mchildren, []) + + # Ordering should not include any database column more than once (this + # is most likely to ocurr naturally with model inheritance, so we + # check it here). Regression test for #9390. This necessarily pokes at + # the SQL string for the query, since the duplicate problems are only + # apparent at that late stage. + qs = ArticleWithAuthor.objects.order_by('pub_date', 'pk') + sql = qs.query.get_compiler(qs.db).as_sql()[0] + fragment = sql[sql.find('ORDER BY'):] + pos = fragment.find('pub_date') + self.assertEqual(fragment.find('pub_date', pos + 1), -1) + + def test_queryset_update_on_parent_model(self): + """ + Regression test for #10362 + It is possible to call update() and only change a field in + an ancestor model. + """ + article = ArticleWithAuthor.objects.create( + author="fred", + headline="Hey there!", + pub_date=datetime.datetime(2009, 3, 1, 8, 0, 0)) + update = ArticleWithAuthor.objects.filter( + author="fred").update(headline="Oh, no!") + self.assertEqual(update, 1) + update = ArticleWithAuthor.objects.filter( + pk=article.pk).update(headline="Oh, no!") + self.assertEqual(update, 1) + + derivedm1 = DerivedM.objects.create( + customPK=44, + base_name="b1", + derived_name="d1") + self.assertEqual(derivedm1.customPK, 44) + self.assertEqual(derivedm1.base_name, 'b1') + self.assertEqual(derivedm1.derived_name, 'd1') + derivedms = list(DerivedM.objects.all()) + self.assertEqual(derivedms, [derivedm1]) + + def test_use_explicit_o2o_to_parent_as_pk(self): + """ + Regression tests for #10406 + If there's a one-to-one link between a child model and the parent and + no explicit pk declared, we can use the one-to-one link as the pk on + the child. + """ + self.assertEqual(ParkingLot2._meta.pk.name, "parent") + + # However, the connector from child to parent need not be the pk on + # the child at all. + self.assertEqual(ParkingLot3._meta.pk.name, "primary_key") + # the child->parent link + self.assertEqual( + ParkingLot3._meta.get_ancestor_link(Place).name, + "parent") + + def test_all_fields_from_abstract_base_class(self): + """ + Regression tests for #7588 + """ + # All fields from an ABC, including those inherited non-abstractly + # should be available on child classes (#7588). Creating this instance + # should work without error. + QualityControl.objects.create( + headline="Problems in Django", + pub_date=datetime.datetime.now(), + quality=10, + assignee="adrian") + + def test_abstract_base_class_m2m_relation_inheritance(self): + # Check that many-to-many relations defined on an abstract base class + # are correctly inherited (and created) on the child class. + p1 = Person.objects.create(name='Alice') + p2 = Person.objects.create(name='Bob') + p3 = Person.objects.create(name='Carol') + p4 = Person.objects.create(name='Dave') + + birthday = BirthdayParty.objects.create( + name='Birthday party for Alice') + birthday.attendees = [p1, p3] + + bachelor = BachelorParty.objects.create(name='Bachelor party for Bob') + bachelor.attendees = [p2, p4] + + parties = list(p1.birthdayparty_set.all()) + self.assertEqual(parties, [birthday]) + + parties = list(p1.bachelorparty_set.all()) + self.assertEqual(parties, []) + + parties = list(p2.bachelorparty_set.all()) + self.assertEqual(parties, [bachelor]) + + # Check that a subclass of a subclass of an abstract model doesn't get + # it's own accessor. + self.assertFalse(hasattr(p2, 'messybachelorparty_set')) + + # ... but it does inherit the m2m from it's parent + messy = MessyBachelorParty.objects.create( + name='Bachelor party for Dave') + messy.attendees = [p4] + messy_parent = messy.bachelorparty_ptr + + parties = list(p4.bachelorparty_set.all()) + self.assertEqual(parties, [bachelor, messy_parent])