123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676 |
- """
- 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")
|