From 2cd19f3738b9c4a6861c007bf22f72a386cba07f Mon Sep 17 00:00:00 2001 From: Alexander Shchapov Date: Wed, 3 Dec 2014 23:24:42 +0200 Subject: [PATCH] Refs #18586 -- Split up model_inheritance.ModelInheritanceTest --- tests/model_inheritance/tests.py | 292 ++++++++++++++++--------------- 1 file changed, 147 insertions(+), 145 deletions(-) diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py index bf76286391..10721a6b3f 100644 --- a/tests/model_inheritance/tests.py +++ b/tests/model_inheritance/tests.py @@ -49,9 +49,16 @@ class ModelInheritanceTests(TestCase): # doesn't exist as a model). self.assertRaises(AttributeError, lambda: CommonInfo.objects.all()) - def test_multiple_table(self): - post = Post.objects.create(title="Lorem Ipsum") + def test_reverse_relation_for_different_hierarchy_tree(self): + # Even though p.supplier for a Place 'p' (a parent of a Supplier), a + # Restaurant object cannot access that reverse relation, since it's not + # part of the Place-Supplier Hierarchy. + self.assertQuerysetEqual(Place.objects.filter(supplier__name="foo"), []) + self.assertRaises(FieldError, Restaurant.objects.filter, supplier__name="foo") + + def test_model_with_distinct_accessors(self): # The Post model has distinct accessors for the Comment and Link models. + post = Post.objects.create(title="Lorem Ipsum") post.attached_comment_set.create(content="Save $ on V1agr@", is_spam=True) post.attached_link_set.create( content="The Web framework for perfections with deadlines.", @@ -64,47 +71,7 @@ class ModelInheritanceTests(TestCase): AttributeError, getattr, post, "attached_%(class)s_set" ) - # The Place/Restaurant/ItalianRestaurant models all exist as - # independent models. However, the subclasses also have transparent - # access to the fields of their ancestors. - # Create a couple of Places. - Place.objects.create(name="Master Shakes", address="666 W. Jersey") - Place.objects.create(name="Ace Hardware", address="1013 N. Ashland") - - # Test constructor for Restaurant. - r = Restaurant.objects.create( - name="Demon Dogs", - address="944 W. Fullerton", - serves_hot_dogs=True, - serves_pizza=False, - rating=2 - ) - # Test the constructor for ItalianRestaurant. - c = Chef.objects.create(name="Albert") - ir = ItalianRestaurant.objects.create( - name="Ristorante Miron", - address="1234 W. Ash", - serves_hot_dogs=False, - serves_pizza=False, - serves_gnocchi=True, - rating=4, - chef=c - ) - self.assertQuerysetEqual( - ItalianRestaurant.objects.filter(address="1234 W. Ash"), [ - "Ristorante Miron", - ], - attrgetter("name") - ) - ir.address = "1234 W. Elm" - ir.save() - self.assertQuerysetEqual( - ItalianRestaurant.objects.filter(address="1234 W. Elm"), [ - "Ristorante Miron", - ], - attrgetter("name") - ) - + def test_meta_fields_and_ordering(self): # Make sure Restaurant and ItalianRestaurant have the right fields in # the right order. self.assertEqual( @@ -119,14 +86,91 @@ class ModelInheritanceTests(TestCase): ) self.assertEqual(Restaurant._meta.ordering, ["-rating"]) - # Even though p.supplier for a Place 'p' (a parent of a Supplier), a - # Restaurant object cannot access that reverse relation, since it's not - # part of the Place-Supplier Hierarchy. - self.assertQuerysetEqual(Place.objects.filter(supplier__name="foo"), []) - self.assertRaises( - FieldError, Restaurant.objects.filter, supplier__name="foo" + def test_custompk_m2m(self): + b = Base.objects.create() + b.titles.add(Title.objects.create(title="foof")) + s = SubBase.objects.create(sub_id=b.id) + b = Base.objects.get(pk=s.id) + self.assertNotEqual(b.pk, s.pk) + # Low-level test for related_val + self.assertEqual(s.titles.related_val, (s.id,)) + # Higher level test for correct query values (title foof not + # accidentally found). + self.assertQuerysetEqual(s.titles.all(), []) + + def test_update_parent_filtering(self): + """ + Test that updating a field of a model subclass doesn't issue an UPDATE + query constrained by an inner query. + Refs #10399 + """ + supplier = Supplier.objects.create( + name='Central market', + address='610 some street', + ) + # Capture the expected query in a database agnostic way + with CaptureQueriesContext(connection) as captured_queries: + Place.objects.filter(pk=supplier.pk).update(name=supplier.name) + expected_sql = captured_queries[0]['sql'] + # Capture the queries executed when a subclassed model instance is saved. + with CaptureQueriesContext(connection) as captured_queries: + supplier.save(update_fields=('name',)) + for query in captured_queries: + sql = query['sql'] + if 'UPDATE' in sql: + self.assertEqual(expected_sql, sql) + + def test_eq(self): + # Equality doesn't transfer in multitable inheritance. + self.assertNotEqual(Place(id=1), Restaurant(id=1)) + self.assertNotEqual(Restaurant(id=1), Place(id=1)) + + def test_mixin_init(self): + m = MixinModel() + self.assertEqual(m.other_attr, 1) + + +class ModelInheritanceDataTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.restaurant = Restaurant.objects.create( + name="Demon Dogs", + address="944 W. Fullerton", + serves_hot_dogs=True, + serves_pizza=False, + rating=2, ) + chef = Chef.objects.create(name="Albert") + cls.italian_restaurant = ItalianRestaurant.objects.create( + name="Ristorante Miron", + address="1234 W. Ash", + serves_hot_dogs=False, + serves_pizza=False, + serves_gnocchi=True, + rating=4, + chef=chef, + ) + + def test_filter_inherited_model(self): + self.assertQuerysetEqual( + ItalianRestaurant.objects.filter(address="1234 W. Ash"), [ + "Ristorante Miron", + ], + attrgetter("name") + ) + + def test_update_inherited_model(self): + self.italian_restaurant.address = "1234 W. Elm" + self.italian_restaurant.save() + self.assertQuerysetEqual( + ItalianRestaurant.objects.filter(address="1234 W. Elm"), [ + "Ristorante Miron", + ], + attrgetter("name") + ) + + def test_parent_fields_available_for_filtering_in_child_model(self): # Parent fields can be used directly in filters on the child model. self.assertQuerysetEqual( Restaurant.objects.filter(name="Demon Dogs"), [ @@ -135,21 +179,24 @@ class ModelInheritanceTests(TestCase): attrgetter("name") ) self.assertQuerysetEqual( - ItalianRestaurant.objects.filter(address="1234 W. Elm"), [ + ItalianRestaurant.objects.filter(address="1234 W. Ash"), [ "Ristorante Miron", ], attrgetter("name") ) + def test_filter_on_parent_returns_object_of_parent_type(self): # Filters against the parent model return objects of the parent's type. p = Place.objects.get(name="Demon Dogs") self.assertIs(type(p), Place) + def test_parent_child_one_to_one_link(self): # Since the parent and child are linked by an automatically created # OneToOneField, you can get from the parent to the child by using the # child's name. self.assertEqual( - p.restaurant, Restaurant.objects.get(name="Demon Dogs") + Place.objects.get(name="Demon Dogs").restaurant, + Restaurant.objects.get(name="Demon Dogs") ) self.assertEqual( Place.objects.get(name="Ristorante Miron").restaurant.italianrestaurant, @@ -160,29 +207,35 @@ class ModelInheritanceTests(TestCase): ItalianRestaurant.objects.get(name="Ristorante Miron") ) + def test_parent_child_one_to_one_link_on_nonrelated_objects(self): # This won't work because the Demon Dogs restaurant is not an Italian # restaurant. self.assertRaises( ItalianRestaurant.DoesNotExist, - lambda: p.restaurant.italianrestaurant + lambda: Place.objects.get(name="Demon Dogs").restaurant.italianrestaurant ) + + def test_inherited_does_not_exist_exception(self): # An ItalianRestaurant which does not exist is also a Place which does # not exist. self.assertRaises( Place.DoesNotExist, ItalianRestaurant.objects.get, name="The Noodle Void" ) + + def test_inherited_multiple_objects_returned_exception(self): # MultipleObjectsReturned is also inherited. self.assertRaises( Place.MultipleObjectsReturned, Restaurant.objects.get, id__lt=12321 ) + def test_related_objects_for_inherited_models(self): # Related objects work just as they normally do. s1 = Supplier.objects.create(name="Joe's Chickens", address="123 Sesame St") - s1.customers = [r, ir] + s1.customers = [self.restaurant, self.italian_restaurant] s2 = Supplier.objects.create(name="Luigi's Pasta", address="456 Sesame St") - s2.customers = [ir] + s2.customers = [self.italian_restaurant] # This won't work because the Place we select is not a Restaurant (it's # a Supplier). @@ -193,7 +246,7 @@ class ModelInheritanceTests(TestCase): self.assertEqual(p.supplier, s1) self.assertQuerysetEqual( - ir.provider.order_by("-name"), [ + self.italian_restaurant.provider.order_by("-name"), [ "Luigi's Pasta", "Joe's Chickens" ], @@ -217,7 +270,7 @@ class ModelInheritanceTests(TestCase): name="Main St", address="111 Main St", main_site=s1 ) ParkingLot.objects.create( - name="Well Lit", address="124 Sesame St", main_site=ir + name="Well Lit", address="124 Sesame St", main_site=self.italian_restaurant ) self.assertEqual( @@ -225,6 +278,7 @@ class ModelInheritanceTests(TestCase): "Ristorante Miron" ) + def test_update_works_on_parent_and_child_models_at_once(self): # The update() command can update fields in parent and child classes at # once (although it executed multiple SQL queries to do so). rows = Restaurant.objects.filter( @@ -234,18 +288,20 @@ class ModelInheritanceTests(TestCase): ) self.assertEqual(rows, 1) - r1 = Restaurant.objects.get(pk=r.pk) + r1 = Restaurant.objects.get(pk=self.restaurant.pk) self.assertFalse(r1.serves_hot_dogs) self.assertEqual(r1.name, "Demon Puppies") + def test_values_works_on_parent_model_fields(self): # The values() command also works on fields from parent models. self.assertQuerysetEqual( ItalianRestaurant.objects.values("name", "rating"), [ - {"rating": 4, "name": "Ristorante Miron"} + {"rating": 4, "name": "Ristorante Miron"}, ], lambda o: o ) + def test_select_related_works_on_parent_model_fields(self): # select_related works with fields from the parent object as if they # were a normal part of the model. self.assertNumQueries( @@ -260,22 +316,6 @@ class ModelInheritanceTests(TestCase): #23370 - Should be able to defer child fields when using select_related() from parent to child. """ - Restaurant.objects.create( - name="Demon Dogs", - address="944 W. Fullerton", - serves_hot_dogs=True, - serves_pizza=False, - rating=2, - ) - ItalianRestaurant.objects.create( - name="Ristorante Miron", - address="1234 W. Ash", - serves_hot_dogs=False, - serves_pizza=False, - serves_gnocchi=True, - rating=4, - ) - qs = (Restaurant.objects .select_related("italianrestaurant") .defer("italianrestaurant__serves_gnocchi") @@ -292,91 +332,53 @@ class ModelInheritanceTests(TestCase): self.assertEqual(qs[1].italianrestaurant.name, 'Ristorante Miron') self.assertEqual(qs[1].italianrestaurant.rating, 4) - def test_mixin_init(self): - m = MixinModel() - self.assertEqual(m.other_attr, 1) - def test_update_query_counts(self): """ Test that update queries do not generate non-necessary queries. Refs #18304. """ - c = Chef.objects.create(name="Albert") - ir = ItalianRestaurant.objects.create( - name="Ristorante Miron", - address="1234 W. Ash", - serves_hot_dogs=False, - serves_pizza=False, - serves_gnocchi=True, - rating=4, - chef=c - ) with self.assertNumQueries(3): - ir.save() + self.italian_restaurant.save() - def test_update_parent_filtering(self): - """ - Test that updating a field of a model subclass doesn't issue an UPDATE - query constrained by an inner query. - Refs #10399 - """ - supplier = Supplier.objects.create( - name='Central market', - address='610 some street' - ) - # Capture the expected query in a database agnostic way - with CaptureQueriesContext(connection) as captured_queries: - Place.objects.filter(pk=supplier.pk).update(name=supplier.name) - expected_sql = captured_queries[0]['sql'] - # Capture the queries executed when a subclassed model instance is saved. - with CaptureQueriesContext(connection) as captured_queries: - supplier.save(update_fields=('name',)) - for query in captured_queries: - sql = query['sql'] - if 'UPDATE' in sql: - self.assertEqual(expected_sql, sql) - - def test_eq(self): - # Equality doesn't transfer in multitable inheritance. - self.assertNotEqual(Place(id=1), Restaurant(id=1)) - self.assertNotEqual(Restaurant(id=1), Place(id=1)) - - def test_ticket_12567(self): - r = Restaurant.objects.create(name='n1', address='a1') - s = Supplier.objects.create(name='s1', address='a2') - self.assertQuerysetEqual( - Place.objects.filter(supplier__isnull=False), - [Place.objects.get(pk=s.pk)], - lambda x: x + def test_filter_inherited_on_null(self): + # Refs #12567 + Supplier.objects.create( + name="Central market", + address="610 some street", ) self.assertQuerysetEqual( - Place.objects.filter(supplier__isnull=True), - [Place.objects.get(pk=r.pk)], - lambda x: x + Place.objects.filter(supplier__isnull=False), [ + "Central market", + ], + attrgetter("name") ) self.assertQuerysetEqual( - Place.objects.exclude(supplier__isnull=False), - [Place.objects.get(pk=r.pk)], - lambda x: x - ) - self.assertQuerysetEqual( - Place.objects.exclude(supplier__isnull=True), - [Place.objects.get(pk=s.pk)], - lambda x: x + Place.objects.filter(supplier__isnull=True).order_by("name"), [ + "Demon Dogs", + "Ristorante Miron", + ], + attrgetter("name") ) - def test_custompk_m2m(self): - b = Base.objects.create() - b.titles.add(Title.objects.create(title="foof")) - s = SubBase.objects.create(sub_id=b.id) - b = Base.objects.get(pk=s.id) - self.assertNotEqual(b.pk, s.pk) - # Low-level test for related_val - self.assertEqual(s.titles.related_val, (s.id,)) - # Higher level test for correct query values (title foof not - # accidentally found). + def test_exclude_inherited_on_null(self): + # Refs #12567 + Supplier.objects.create( + name="Central market", + address="610 some street", + ) self.assertQuerysetEqual( - s.titles.all(), []) + Place.objects.exclude(supplier__isnull=False).order_by("name"), [ + "Demon Dogs", + "Ristorante Miron", + ], + attrgetter("name") + ) + self.assertQuerysetEqual( + Place.objects.exclude(supplier__isnull=True), [ + "Central market", + ], + attrgetter("name") + ) class InheritanceSameModelNameTests(TransactionTestCase):