""" Regression tests for Model inheritance behavior. """ import datetime from operator import attrgetter from unittest import expectedFailure from django import forms from django.test import TestCase from .models import ( ArticleWithAuthor, BachelorParty, BirthdayParty, BusStation, Child, Congressman, DerivedM, InternalCertificationAudit, ItalianRestaurant, M2MChild, MessyBachelorParty, ParkingLot, ParkingLot3, ParkingLot4A, ParkingLot4B, Person, Place, Politician, Profile, QualityControl, Restaurant, SelfRefChild, SelfRefParent, Senator, Supplier, TrainStation, User, Wholesaler, ) class ModelInheritanceTest(TestCase): def test_model_inheritance(self): # Regression for #7350, #7202 # When you create a Parent object with a specific reference to an # existent child instance, saving the Parent doesn't duplicate the # child. This behavior 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) # 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": "Guido's House of Pasta", "serves_hot_dogs": True}] ) dicts = list( ItalianRestaurant.objects.values( "name", "serves_hot_dogs", "serves_gnocchi" ) ) self.assertEqual( dicts, [ { "name": "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": "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": "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": "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": "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": "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. Child.objects.create( name="child", created=datetime.datetime(2008, 6, 26, 17, 0, 0) ) datetimes = list(Child.objects.datetimes("created", "month")) self.assertEqual(datetimes, [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) Restaurant.objects.create( name="a", address="xx", serves_hot_dogs=True, serves_pizza=False, ) # This should delete both Restaurants, plus the related places, plus # the ItalianRestaurant. Restaurant.objects.all().delete() with self.assertRaises(Place.DoesNotExist): Place.objects.get(pk=ident) with self.assertRaises(ItalianRestaurant.DoesNotExist): ItalianRestaurant.objects.get(pk=ident) def test_issue_6755(self): """ Regression test for #6755 """ r = Restaurant(serves_pizza=False, serves_hot_dogs=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, serves_hot_dogs=False) r.save() self.assertEqual(r.id, orig_id) self.assertEqual(r.id, r.place_ptr_id) def test_issue_11764(self): """ Regression test for #11764 """ wholesalers = list(Wholesaler.objects.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) with 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) with 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 occur 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): """ The connector from child to parent need not be the pk on the child. """ self.assertEqual(ParkingLot3._meta.pk.name, "primary_key") # the child->parent link self.assertEqual(ParkingLot3._meta.get_ancestor_link(Place).name, "parent") def test_use_explicit_o2o_to_parent_from_abstract_model(self): self.assertEqual(ParkingLot4A._meta.pk.name, "parent") ParkingLot4A.objects.create( name="Parking4A", address="21 Jump Street", ) self.assertEqual(ParkingLot4B._meta.pk.name, "parent") ParkingLot4A.objects.create( name="Parking4B", address="21 Jump Street", ) 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): # 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.set([p1, p3]) bachelor = BachelorParty.objects.create(name="Bachelor party for Bob") bachelor.attendees.set([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]) # A subclass of a subclass of an abstract model doesn't get its own # accessor. self.assertFalse(hasattr(p2, "messybachelorparty_set")) # ... but it does inherit the m2m from its parent messy = MessyBachelorParty.objects.create(name="Bachelor party for Dave") messy.attendees.set([p4]) messy_parent = messy.bachelorparty_ptr parties = list(p4.bachelorparty_set.all()) self.assertEqual(parties, [bachelor, messy_parent]) def test_abstract_verbose_name_plural_inheritance(self): """ verbose_name_plural correctly inherited from ABC if inheritance chain includes an abstract model. """ # Regression test for #11369: verbose_name_plural should be inherited # from an ABC even when there are one or more intermediate # abstract models in the inheritance chain, for consistency with # verbose_name. self.assertEqual(InternalCertificationAudit._meta.verbose_name_plural, "Audits") def test_inherited_nullable_exclude(self): obj = SelfRefChild.objects.create(child_data=37, parent_data=42) self.assertQuerySetEqual( SelfRefParent.objects.exclude(self_data=72), [obj.pk], attrgetter("pk") ) self.assertQuerySetEqual( SelfRefChild.objects.exclude(self_data=72), [obj.pk], attrgetter("pk") ) def test_concrete_abstract_concrete_pk(self): """ Primary key set correctly with concrete->abstract->concrete inheritance. """ # Regression test for #13987: Primary key is incorrectly determined # when more than one model has a concrete->abstract->concrete # inheritance hierarchy. self.assertEqual( len( [field for field in BusStation._meta.local_fields if field.primary_key] ), 1, ) self.assertEqual( len( [ field for field in TrainStation._meta.local_fields if field.primary_key ] ), 1, ) self.assertIs(BusStation._meta.pk.model, BusStation) self.assertIs(TrainStation._meta.pk.model, TrainStation) def test_inherited_unique_field_with_form(self): """ A model which has different primary key for the parent model passes unique field checking correctly (#17615). """ class ProfileForm(forms.ModelForm): class Meta: model = Profile fields = "__all__" User.objects.create(username="user_only") p = Profile.objects.create(username="user_with_profile") form = ProfileForm( {"username": "user_with_profile", "extra": "hello"}, instance=p ) self.assertTrue(form.is_valid()) def test_inheritance_joins(self): # Test for #17502 - check that filtering through two levels of # inheritance chain doesn't generate extra joins. qs = ItalianRestaurant.objects.all() self.assertEqual(str(qs.query).count("JOIN"), 2) qs = ItalianRestaurant.objects.filter(name="foo") self.assertEqual(str(qs.query).count("JOIN"), 2) @expectedFailure def test_inheritance_values_joins(self): # It would be nice (but not too important) to skip the middle join in # this case. Skipping is possible as nothing from the middle model is # used in the qs and top contains direct pointer to the bottom model. qs = ItalianRestaurant.objects.values_list("serves_gnocchi").filter(name="foo") self.assertEqual(str(qs.query).count("JOIN"), 1) def test_issue_21554(self): senator = Senator.objects.create(name="John Doe", title="X", state="Y") senator = Senator.objects.get(pk=senator.pk) self.assertEqual(senator.name, "John Doe") self.assertEqual(senator.title, "X") self.assertEqual(senator.state, "Y") def test_inheritance_resolve_columns(self): Restaurant.objects.create( name="Bobs Cafe", address="Somewhere", serves_pizza=True, serves_hot_dogs=True, ) p = Place.objects.select_related("restaurant")[0] self.assertIsInstance(p.restaurant.serves_pizza, bool) def test_inheritance_select_related(self): # Regression test for #7246 r1 = Restaurant.objects.create( name="Nobu", serves_hot_dogs=True, serves_pizza=False ) r2 = Restaurant.objects.create( name="Craft", serves_hot_dogs=False, serves_pizza=True ) Supplier.objects.create(name="John", restaurant=r1) Supplier.objects.create(name="Jane", restaurant=r2) self.assertQuerySetEqual( Supplier.objects.order_by("name").select_related(), [ "Jane", "John", ], attrgetter("name"), ) jane = Supplier.objects.order_by("name").select_related("restaurant")[0] self.assertEqual(jane.restaurant.name, "Craft") def test_filter_with_parent_fk(self): r = Restaurant.objects.create() s = Supplier.objects.create(restaurant=r) # The mismatch between Restaurant and Place is intentional (#28175). self.assertSequenceEqual( Supplier.objects.filter(restaurant__in=Place.objects.all()), [s] ) def test_ptr_accessor_assigns_state(self): r = Restaurant.objects.create() self.assertIs(r.place_ptr._state.adding, False) self.assertEqual(r.place_ptr._state.db, "default") def test_related_filtering_query_efficiency_ticket_15844(self): r = Restaurant.objects.create( name="Guido's House of Pasta", address="944 W. Fullerton", serves_hot_dogs=True, serves_pizza=False, ) s = Supplier.objects.create(restaurant=r) with self.assertNumQueries(1): self.assertSequenceEqual(Supplier.objects.filter(restaurant=r), [s]) with self.assertNumQueries(1): self.assertSequenceEqual(r.supplier_set.all(), [s]) def test_queries_on_parent_access(self): italian_restaurant = ItalianRestaurant.objects.create( name="Guido's House of Pasta", address="944 W. Fullerton", serves_hot_dogs=True, serves_pizza=False, serves_gnocchi=True, ) # No queries are made when accessing the parent objects. italian_restaurant = ItalianRestaurant.objects.get(pk=italian_restaurant.pk) with self.assertNumQueries(0): restaurant = italian_restaurant.restaurant_ptr self.assertEqual(restaurant.place_ptr.restaurant, restaurant) self.assertEqual(restaurant.italianrestaurant, italian_restaurant) # One query is made when accessing the parent objects when the instance # is deferred. italian_restaurant = ItalianRestaurant.objects.only("serves_gnocchi").get( pk=italian_restaurant.pk ) with self.assertNumQueries(1): restaurant = italian_restaurant.restaurant_ptr self.assertEqual(restaurant.place_ptr.restaurant, restaurant) self.assertEqual(restaurant.italianrestaurant, italian_restaurant) # No queries are made when accessing the parent objects when the # instance has deferred a field not present in the parent table. italian_restaurant = ItalianRestaurant.objects.defer("serves_gnocchi").get( pk=italian_restaurant.pk ) with self.assertNumQueries(0): restaurant = italian_restaurant.restaurant_ptr self.assertEqual(restaurant.place_ptr.restaurant, restaurant) self.assertEqual(restaurant.italianrestaurant, italian_restaurant) def test_id_field_update_on_ancestor_change(self): place1 = Place.objects.create(name="House of Pasta", address="944 Fullerton") place2 = Place.objects.create(name="House of Pizza", address="954 Fullerton") place3 = Place.objects.create(name="Burger house", address="964 Fullerton") restaurant1 = Restaurant.objects.create( place_ptr=place1, serves_hot_dogs=True, serves_pizza=False, ) restaurant2 = Restaurant.objects.create( place_ptr=place2, serves_hot_dogs=True, serves_pizza=False, ) italian_restaurant = ItalianRestaurant.objects.create( restaurant_ptr=restaurant1, serves_gnocchi=True, ) # Changing the parent of a restaurant changes the restaurant's ID & PK. restaurant1.place_ptr = place3 self.assertEqual(restaurant1.pk, place3.pk) self.assertEqual(restaurant1.id, place3.id) self.assertEqual(restaurant1.pk, restaurant1.id) restaurant1.place_ptr = None self.assertIsNone(restaurant1.pk) self.assertIsNone(restaurant1.id) # Changing the parent of an italian restaurant changes the restaurant's # ID & PK. italian_restaurant.restaurant_ptr = restaurant2 self.assertEqual(italian_restaurant.pk, restaurant2.pk) self.assertEqual(italian_restaurant.id, restaurant2.id) self.assertEqual(italian_restaurant.pk, italian_restaurant.id) italian_restaurant.restaurant_ptr = None self.assertIsNone(italian_restaurant.pk) self.assertIsNone(italian_restaurant.id) def test_create_new_instance_with_pk_equals_none(self): p1 = Profile.objects.create(username="john") p2 = User.objects.get(pk=p1.user_ptr_id).profile # Create a new profile by setting pk = None. p2.pk = None p2.user_ptr_id = None p2.username = "bill" p2.save() self.assertEqual(Profile.objects.count(), 2) self.assertEqual(User.objects.get(pk=p1.user_ptr_id).username, "john") def test_create_new_instance_with_pk_equals_none_multi_inheritance(self): c1 = Congressman.objects.create(state="PA", name="John", title="senator 1") c2 = Person.objects.get(pk=c1.pk).congressman # Create a new congressman by setting pk = None. c2.pk = None c2.id = None c2.politician_ptr_id = None c2.name = "Bill" c2.title = "senator 2" c2.save() self.assertEqual(Congressman.objects.count(), 2) self.assertEqual(Person.objects.get(pk=c1.pk).name, "John") self.assertEqual( Politician.objects.get(pk=c1.politician_ptr_id).title, "senator 1", ) def test_mti_update_parent_through_child(self): Politician.objects.create() Congressman.objects.create() Congressman.objects.update(title="senator 1") self.assertEqual(Congressman.objects.get().title, "senator 1") def test_mti_update_grand_parent_through_child(self): Politician.objects.create() Senator.objects.create() Senator.objects.update(title="senator 1") self.assertEqual(Senator.objects.get().title, "senator 1")