diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index fd0a295..3eea553 100644
a
|
b
|
class Field(object):
|
234 | 234 | def contribute_to_class(self, cls, name): |
235 | 235 | self.set_attributes_from_name(name) |
236 | 236 | self.model = cls |
| 237 | if not hasattr(self, "_declared_on_class"): |
| 238 | self._declared_on_class = cls |
| 239 | if not hasattr(self, "_first_concrete_class") and not cls._meta.abstract: |
| 240 | self._first_concrete_class = cls |
237 | 241 | cls._meta.add_field(self) |
238 | | if self.choices: |
239 | | setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self)) |
240 | 242 | |
241 | 243 | def get_attname(self): |
242 | 244 | return self.name |
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 10617dc..fe16663 100644
a
|
b
|
from django.db.models.loading import get_models, app_cache_ready
|
10 | 10 | from django.utils.translation import activate, deactivate_all, get_language, string_concat |
11 | 11 | from django.utils.encoding import force_unicode, smart_str |
12 | 12 | from django.utils.datastructures import SortedDict |
| 13 | from django.utils.functional import curry |
13 | 14 | |
14 | 15 | try: |
15 | 16 | all |
… |
… |
class Options(object):
|
64 | 65 | from django.db.backends.util import truncate_name |
65 | 66 | |
66 | 67 | cls._meta = self |
| 68 | self.model = cls |
67 | 69 | self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS |
68 | 70 | # First, construct the default values for these options. |
69 | 71 | self.object_name = cls.__name__ |
… |
… |
class Options(object):
|
158 | 160 | # Insert the given field in the order in which it was created, using |
159 | 161 | # the "creation_counter" attribute of the field. |
160 | 162 | # Move many-to-many related fields from self.fields into |
161 | | # self.many_to_many. |
| 163 | # self.many_to_many. Ignore duplicate fields inherited from a |
| 164 | # single abstract base class. |
| 165 | try: |
| 166 | f = self.get_field(field.name) |
| 167 | except FieldDoesNotExist: |
| 168 | pass |
| 169 | else: |
| 170 | if f._declared_on_class == field._declared_on_class and getattr(f, '_first_concrete_class', None) == getattr(field, '_first_concrete_class', None): |
| 171 | return |
| 172 | |
162 | 173 | if field.rel and isinstance(field.rel, ManyToManyRel): |
163 | 174 | self.local_many_to_many.insert(bisect(self.local_many_to_many, field), field) |
164 | 175 | if hasattr(self, '_m2m_cache'): |
… |
… |
class Options(object):
|
173 | 184 | if hasattr(self, '_name_map'): |
174 | 185 | del self._name_map |
175 | 186 | |
| 187 | if field.choices: |
| 188 | setattr(self.model, 'get_%s_display' % field.name, curry(self.model._get_FIELD_display, field=field)) |
| 189 | |
176 | 190 | def add_virtual_field(self, field): |
177 | 191 | self.virtual_fields.append(field) |
178 | 192 | |
diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
index a0fed8a..bbdbd37 100644
a
|
b
|
class Comment(Attachment):
|
64 | 64 | class Link(Attachment): |
65 | 65 | url = models.URLField() |
66 | 66 | |
| 67 | class OrangeAttachment(Attachment): |
| 68 | class Meta: |
| 69 | abstract = True |
| 70 | |
| 71 | class BananaAttachment(Attachment): |
| 72 | class Meta: |
| 73 | abstract = True |
| 74 | |
67 | 75 | # |
68 | 76 | # Multi-table inheritance |
69 | 77 | # |
diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py
index 334297a..bdcc28d 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 | OrangeAttachment, BananaAttachment) |
8 | 9 | |
9 | 10 | |
10 | 11 | class ModelInheritanceTests(TestCase): |
… |
… |
class ModelInheritanceTests(TestCase):
|
69 | 70 | StudentWorker.objects.get, pk__lt=sw2.pk + 100 |
70 | 71 | ) |
71 | 72 | |
| 73 | def test_multiple_abstract_inheritance(self): |
| 74 | # Define class here to avoid model validation errors while running tests. |
| 75 | class SmoothieAttachment(OrangeAttachment, BananaAttachment): |
| 76 | class Meta: |
| 77 | app_label = 'model_inheritance' |
| 78 | |
| 79 | # There should be no duplicate field names. |
| 80 | field_names = [field.name for field in SmoothieAttachment._meta.fields] |
| 81 | self.assertEqual(field_names.sort(), list(set(field_names)).sort()) |
| 82 | |
72 | 83 | def test_multiple_table(self): |
73 | 84 | post = Post.objects.create(title="Lorem Ipsum") |
74 | 85 | # The Post model has distinct accessors for the Comment and Link models. |