diff --git a/django/db/models/base.py b/django/db/models/base.py
index 4000379..3c4b65d 100644
|
a
|
b
|
class ModelBase(type):
|
| 101 | 101 | new_class._meta.virtual_fields |
| 102 | 102 | field_names = set([f.name for f in new_fields]) |
| 103 | 103 | |
| | 104 | # Things without _meta aren't functional models, so they're |
| | 105 | # uninteresting parents. |
| | 106 | parents = [p for p in parents if hasattr(p, '_meta')] |
| | 107 | new_class._meta.model_bases = parents |
| | 108 | |
| 104 | 109 | # Basic setup for proxy models. |
| 105 | 110 | if is_proxy: |
| 106 | 111 | base = None |
| 107 | | for parent in [cls for cls in parents if hasattr(cls, '_meta')]: |
| | 112 | for parent in parents: |
| 108 | 113 | if parent._meta.abstract: |
| 109 | 114 | if parent._meta.fields: |
| 110 | 115 | raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name) |
| … |
… |
class ModelBase(type):
|
| 129 | 134 | |
| 130 | 135 | for base in parents: |
| 131 | 136 | original_base = base |
| 132 | | if not hasattr(base, '_meta'): |
| 133 | | # Things without _meta aren't functional models, so they're |
| 134 | | # uninteresting parents. |
| 135 | | continue |
| 136 | | |
| 137 | 137 | parent_fields = base._meta.local_fields + base._meta.local_many_to_many |
| 138 | 138 | # Check for clashes between locally declared fields and those |
| 139 | 139 | # on the base classes (we cannot handle shadowed fields at the |
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 2f64c56..052d158 100644
|
a
|
b
|
class Options(object):
|
| 47 | 47 | self.proxy = False |
| 48 | 48 | self.proxy_for_model = None |
| 49 | 49 | self.parents = SortedDict() |
| | 50 | self.model_bases = [] |
| 50 | 51 | self.duplicate_targets = {} |
| 51 | 52 | self.auto_created = False |
| 52 | 53 | |
| … |
… |
class Options(object):
|
| 232 | 233 | |
| 233 | 234 | def _fill_fields_cache(self): |
| 234 | 235 | cache = [] |
| 235 | | for parent in self.parents: |
| 236 | | for field, model in parent._meta.get_fields_with_model(): |
| 237 | | if model: |
| 238 | | cache.append((field, model)) |
| | 236 | local_fields = list(self.local_fields) |
| | 237 | for field in self.local_fields: |
| | 238 | if isinstance(field, AutoField): |
| | 239 | cache.append((field, None)) |
| | 240 | local_fields.remove(field) |
| | 241 | break |
| | 242 | for base in self.model_bases: |
| | 243 | for field, model in base._meta.get_fields_with_model(): |
| | 244 | if not model: |
| | 245 | model = base |
| | 246 | if model._meta.abstract: |
| | 247 | field_index = local_fields.index(field) |
| | 248 | cache.append((local_fields[field_index], None)) |
| | 249 | del local_fields[field_index] |
| 239 | 250 | else: |
| 240 | | cache.append((field, parent)) |
| 241 | | cache.extend([(f, None) for f in self.local_fields]) |
| | 251 | cache.append((field, model)) |
| | 252 | if base in self.parents: |
| | 253 | field = self.parents[base] |
| | 254 | if field: |
| | 255 | cache.append((field, None)) |
| | 256 | local_fields.remove(field) |
| | 257 | cache.extend([(f, None) for f in local_fields]) |
| 242 | 258 | self._field_cache = tuple(cache) |
| 243 | 259 | self._field_name_cache = [x for x, _ in cache] |
| 244 | 260 | |
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index a0fed8a..1537b1e 100644
|
a
|
b
|
class ParkingLot(Place):
|
| 119 | 119 | def __unicode__(self): |
| 120 | 120 | return u"%s the parking lot" % self.name |
| 121 | 121 | |
| | 122 | class AbstractModelOne(models.Model): |
| | 123 | field_one = models.BooleanField() |
| | 124 | field_two = models.TextField() |
| | 125 | |
| | 126 | class Meta: |
| | 127 | abstract = True |
| | 128 | |
| | 129 | class AbstractModelTwo(models.Model): |
| | 130 | field_three = models.BooleanField() |
| | 131 | field_four = models.TextField() |
| | 132 | |
| | 133 | class Meta: |
| | 134 | abstract = True |
| | 135 | |
| | 136 | class OneTwo(AbstractModelOne, AbstractModelTwo): |
| | 137 | pass |
| | 138 | |
| | 139 | class TwoOne(AbstractModelTwo, AbstractModelOne): |
| | 140 | pass |
| | 141 | |
| 122 | 142 | # |
| 123 | 143 | # Abstract base classes with related models where the sub-class has the |
| 124 | 144 | # same name in a different app and inherits from the same abstract base |
diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py
index 334297a..1078b1e 100644
|
a
|
b
|
from django.core.exceptions import FieldError
|
| 4 | 4 | from django.test import TestCase |
| 5 | 5 | |
| 6 | 6 | from models import (Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place, |
| 7 | | Post, Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel) |
| | 7 | Post, Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel, |
| | 8 | OneTwo, TwoOne) |
| 8 | 9 | |
| 9 | 10 | |
| 10 | 11 | class ModelInheritanceTests(TestCase): |
| … |
… |
class ModelInheritanceTests(TestCase):
|
| 269 | 270 | self.assertNumQueries(1, |
| 270 | 271 | lambda: ItalianRestaurant.objects.select_related("chef")[0].chef |
| 271 | 272 | ) |
| | 273 | |
| | 274 | names = [field.name for field in OneTwo._meta.fields] |
| | 275 | self.assertEqual(["id", "field_one", "field_two", "field_three", |
| | 276 | "field_four"], names) |
| | 277 | names = [field.name for field in TwoOne._meta.fields] |
| | 278 | self.assertEqual(["id", "field_three", "field_four", "field_one", |
| | 279 | "field_two"], names) |
| 272 | 280 | |
| 273 | 281 | def test_mixin_init(self): |
| 274 | 282 | m = MixinModel() |