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() |