summaryrefslogtreecommitdiff
path: root/tests/model_inheritance_regress
diff options
context:
space:
mode:
authorFlorian Apolloner <florian@apolloner.eu>2013-02-26 09:53:47 +0100
committerFlorian Apolloner <florian@apolloner.eu>2013-02-26 14:36:57 +0100
commit89f40e36246100df6a11316c31a76712ebc6c501 (patch)
tree6e65639683ddaf2027908d1ecb1739e0e2ff853b /tests/model_inheritance_regress
parentb3d2ccb5bfbaf6e7fe1f98843baaa48c35a70950 (diff)
downloaddjango-89f40e36246100df6a11316c31a76712ebc6c501.tar.gz
Merged regressiontests and modeltests into the test root.
Diffstat (limited to 'tests/model_inheritance_regress')
-rw-r--r--tests/model_inheritance_regress/__init__.py0
-rw-r--r--tests/model_inheritance_regress/models.py184
-rw-r--r--tests/model_inheritance_regress/tests.py424
3 files changed, 608 insertions, 0 deletions
diff --git a/tests/model_inheritance_regress/__init__.py b/tests/model_inheritance_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/model_inheritance_regress/__init__.py
diff --git a/tests/model_inheritance_regress/models.py b/tests/model_inheritance_regress/models.py
new file mode 100644
index 0000000000..811c8175bb
--- /dev/null
+++ b/tests/model_inheritance_regress/models.py
@@ -0,0 +1,184 @@
+from __future__ import unicode_literals
+
+import datetime
+
+from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
+
+@python_2_unicode_compatible
+class Place(models.Model):
+ name = models.CharField(max_length=50)
+ address = models.CharField(max_length=80)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __str__(self):
+ return "%s the place" % self.name
+
+@python_2_unicode_compatible
+class Restaurant(Place):
+ serves_hot_dogs = models.BooleanField()
+ serves_pizza = models.BooleanField()
+
+ def __str__(self):
+ return "%s the restaurant" % self.name
+
+@python_2_unicode_compatible
+class ItalianRestaurant(Restaurant):
+ serves_gnocchi = models.BooleanField()
+
+ def __str__(self):
+ return "%s the italian restaurant" % self.name
+
+@python_2_unicode_compatible
+class ParkingLot(Place):
+ # An explicit link to the parent (we can control the attribute name).
+ parent = models.OneToOneField(Place, primary_key=True, parent_link=True)
+ capacity = models.IntegerField()
+
+ def __str__(self):
+ return "%s the parking lot" % self.name
+
+class ParkingLot2(Place):
+ # In lieu of any other connector, an existing OneToOneField will be
+ # promoted to the primary key.
+ parent = models.OneToOneField(Place)
+
+class ParkingLot3(Place):
+ # The parent_link connector need not be the pk on the model.
+ primary_key = models.AutoField(primary_key=True)
+ parent = models.OneToOneField(Place, parent_link=True)
+
+class Supplier(models.Model):
+ restaurant = models.ForeignKey(Restaurant)
+
+class Wholesaler(Supplier):
+ retailer = models.ForeignKey(Supplier,related_name='wholesale_supplier')
+
+class Parent(models.Model):
+ created = models.DateTimeField(default=datetime.datetime.now)
+
+class Child(Parent):
+ name = models.CharField(max_length=10)
+
+class SelfRefParent(models.Model):
+ parent_data = models.IntegerField()
+ self_data = models.ForeignKey('self', null=True)
+
+class SelfRefChild(SelfRefParent):
+ child_data = models.IntegerField()
+
+@python_2_unicode_compatible
+class Article(models.Model):
+ headline = models.CharField(max_length=100)
+ pub_date = models.DateTimeField()
+ class Meta:
+ ordering = ('-pub_date', 'headline')
+
+ def __str__(self):
+ return self.headline
+
+class ArticleWithAuthor(Article):
+ author = models.CharField(max_length=100)
+
+class M2MBase(models.Model):
+ articles = models.ManyToManyField(Article)
+
+class M2MChild(M2MBase):
+ name = models.CharField(max_length=50)
+
+class Evaluation(Article):
+ quality = models.IntegerField()
+
+ class Meta:
+ abstract = True
+
+class QualityControl(Evaluation):
+ assignee = models.CharField(max_length=50)
+
+@python_2_unicode_compatible
+class BaseM(models.Model):
+ base_name = models.CharField(max_length=100)
+
+ def __str__(self):
+ return self.base_name
+
+@python_2_unicode_compatible
+class DerivedM(BaseM):
+ customPK = models.IntegerField(primary_key=True)
+ derived_name = models.CharField(max_length=100)
+
+ def __str__(self):
+ return "PK = %d, base_name = %s, derived_name = %s" \
+ % (self.customPK, self.base_name, self.derived_name)
+
+class AuditBase(models.Model):
+ planned_date = models.DateField()
+
+ class Meta:
+ abstract = True
+ verbose_name_plural = 'Audits'
+
+class CertificationAudit(AuditBase):
+ class Meta(AuditBase.Meta):
+ abstract = True
+
+class InternalCertificationAudit(CertificationAudit):
+ auditing_dept = models.CharField(max_length=20)
+
+# Check that abstract classes don't get m2m tables autocreated.
+@python_2_unicode_compatible
+class Person(models.Model):
+ name = models.CharField(max_length=100)
+
+ class Meta:
+ ordering = ('name',)
+
+ def __str__(self):
+ return self.name
+
+@python_2_unicode_compatible
+class AbstractEvent(models.Model):
+ name = models.CharField(max_length=100)
+ attendees = models.ManyToManyField(Person, related_name="%(class)s_set")
+
+ class Meta:
+ abstract = True
+ ordering = ('name',)
+
+ def __str__(self):
+ return self.name
+
+class BirthdayParty(AbstractEvent):
+ pass
+
+class BachelorParty(AbstractEvent):
+ pass
+
+class MessyBachelorParty(BachelorParty):
+ pass
+
+# Check concrete -> abstract -> concrete inheritance
+class SearchableLocation(models.Model):
+ keywords = models.CharField(max_length=256)
+
+class Station(SearchableLocation):
+ name = models.CharField(max_length=128)
+
+ class Meta:
+ abstract = True
+
+class BusStation(Station):
+ bus_routes = models.CommaSeparatedIntegerField(max_length=128)
+ inbound = models.BooleanField()
+
+class TrainStation(Station):
+ zone = models.IntegerField()
+
+class User(models.Model):
+ username = models.CharField(max_length=30, unique=True)
+
+class Profile(User):
+ profile_id = models.AutoField(primary_key=True)
+ extra = models.CharField(max_length=30, blank=True)
diff --git a/tests/model_inheritance_regress/tests.py b/tests/model_inheritance_regress/tests.py
new file mode 100644
index 0000000000..8f741bbb7f
--- /dev/null
+++ b/tests/model_inheritance_regress/tests.py
@@ -0,0 +1,424 @@
+"""
+Regression tests for Model inheritance behavior.
+"""
+from __future__ import absolute_import, unicode_literals
+
+import datetime
+from operator import attrgetter
+from django import forms
+
+from django.test import TestCase
+
+from .models import (Place, Restaurant, ItalianRestaurant, ParkingLot,
+ ParkingLot2, ParkingLot3, Supplier, Wholesaler, Child, SelfRefParent,
+ SelfRefChild, ArticleWithAuthor, M2MChild, QualityControl, DerivedM,
+ Person, BirthdayParty, BachelorParty, MessyBachelorParty,
+ InternalCertificationAudit, BusStation, TrainStation, User, Profile)
+
+
+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 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)
+
+ # 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': "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.
+ obj = 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)
+ 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])
+
+ 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):
+ """
+ Test that a model which has different primary key for the parent model
+ passes unique field checking correctly. Refs #17615.
+ """
+ class ProfileForm(forms.ModelForm):
+ class Meta:
+ model = Profile
+ 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())