From 21ac4982ea44977928d1ff6db836f4b322f409f4 Mon Sep 17 00:00:00 2001 From: Loic Bistuer Date: Mon, 12 Aug 2013 18:30:38 +0700 Subject: [PATCH] Fixed #20883 -- Made multi-table inheritance look for explicit parent_link in abstract parents. --- django/db/models/base.py | 21 ++++++++++++++++----- docs/releases/1.7.txt | 3 +++ tests/model_inheritance_regress/models.py | 13 +++++++++++++ tests/model_inheritance_regress/tests.py | 16 +++++++++++++++- 4 files changed, 47 insertions(+), 6 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 01ba559e0874..cd3768bccb09 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -184,10 +184,21 @@ def __new__(cls, name, bases, attrs): else: new_class._meta.concrete_model = new_class - # Do the appropriate setup for any model parents. - o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields - if isinstance(f, OneToOneField)]) + # Collect the parent links for multi-table inheritance. + parent_links = {} + for base in reversed([new_class] + parents): + # Conceptually equivalent to `if base is Model`. + if not hasattr(base, '_meta'): + continue + # Skip concrete parent classes. + if base != new_class and not base._meta.abstract: + continue + # Locate OneToOneField instances. + for field in base._meta.local_fields: + if isinstance(field, OneToOneField): + parent_links[field.rel.to] = field + # Do the appropriate setup for any model parents. for base in parents: original_base = base if not hasattr(base, '_meta'): @@ -208,8 +219,8 @@ def __new__(cls, name, bases, attrs): if not base._meta.abstract: # Concrete classes... base = base._meta.concrete_model - if base in o2o_map: - field = o2o_map[base] + if base in parent_links: + field = parent_links[base] elif not is_proxy: attr_name = '%s_ptr' % base._meta.model_name field = OneToOneField(base, name=attr_name, diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 6fda83ebc704..6ea957d959d5 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -142,6 +142,9 @@ Minor features the file system permissions of directories created during file upload, like :setting:`FILE_UPLOAD_PERMISSIONS` does for the files themselves. +* Explicit :class:`~django.db.models.OneToOneField` for + :ref:`multi-table-inheritance` are now discovered in abstract classes. + Backwards incompatible changes in 1.7 ===================================== diff --git a/tests/model_inheritance_regress/models.py b/tests/model_inheritance_regress/models.py index 811c8175bb71..0f45a2cb3f43 100644 --- a/tests/model_inheritance_regress/models.py +++ b/tests/model_inheritance_regress/models.py @@ -50,6 +50,19 @@ class ParkingLot3(Place): primary_key = models.AutoField(primary_key=True) parent = models.OneToOneField(Place, parent_link=True) +class ParkingLot4(models.Model): + # Test parent_link connector can be discovered in abstract classes. + parent = models.OneToOneField(Place, parent_link=True) + + class Meta: + abstract = True + +class ParkingLot4A(ParkingLot4, Place): + pass + +class ParkingLot4B(Place, ParkingLot4): + pass + class Supplier(models.Model): restaurant = models.ForeignKey(Restaurant) diff --git a/tests/model_inheritance_regress/tests.py b/tests/model_inheritance_regress/tests.py index 10a1230685fe..7f78fc7a987d 100644 --- a/tests/model_inheritance_regress/tests.py +++ b/tests/model_inheritance_regress/tests.py @@ -14,7 +14,8 @@ ParkingLot2, ParkingLot3, Supplier, Wholesaler, Child, SelfRefParent, SelfRefChild, ArticleWithAuthor, M2MChild, QualityControl, DerivedM, Person, BirthdayParty, BachelorParty, MessyBachelorParty, - InternalCertificationAudit, BusStation, TrainStation, User, Profile) + InternalCertificationAudit, BusStation, TrainStation, User, Profile, + ParkingLot4A, ParkingLot4B) class ModelInheritanceTest(TestCase): @@ -311,6 +312,19 @@ def test_use_explicit_o2o_to_parent_as_pk(self): 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