Ticket #15279: patch_r16324.diff

File patch_r16324.diff, 7.0 KB (added by Stephen Burrows, 13 years ago)
  • django/db/models/fields/__init__.py

    diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
    index 9037265..5e1d51f 100644
    a b class Field(object):  
    115115            messages.update(getattr(c, 'default_error_messages', {}))
    116116        messages.update(error_messages or {})
    117117        self.error_messages = messages
     118        self._declared_on_class = None
     119        self._first_concrete_class = None
    118120
    119121    def __cmp__(self, other):
    120122        # This is needed because bisect does not take a comparison function.
    class Field(object):  
    231233    def contribute_to_class(self, cls, name):
    232234        self.set_attributes_from_name(name)
    233235        self.model = cls
     236        if self._declared_on_class is None:
     237            self._declared_on_class = cls
     238        if self._first_concrete_class is None and not cls._meta.abstract:
     239            self._first_concrete_class = cls
     240        for f in cls._meta.local_fields + cls._meta.local_many_to_many:
     241            if f.name == self.name:
     242                if self._is_duplicate(f):
     243                    return
     244                break
    234245        cls._meta.add_field(self)
    235246        if self.choices:
    236247            setattr(cls, 'get_%s_display' % self.name, curry(cls._get_FIELD_display, field=self))
    237248
     249    def _is_duplicate(self, other):
     250        """
     251        Returns ``True`` if ``self`` is a duplicate of ``other`` and false otherwise.
     252       
     253        Two fields are duplicates if they were declared on the same class
     254        and if either neither field has been on a concrete class or one
     255        field's first concrete class is a subclass of the other field's first
     256        concrete class.
     257       
     258        """
     259        ocon = other._first_concrete_class
     260        scon = self._first_concrete_class
     261        return other._declared_on_class == self._declared_on_class and ((ocon is None and scon is None) or issubclass(scon, ocon) or issubclass(ocon, scon))
     262
    238263    def get_attname(self):
    239264        return self.name
    240265
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    index 6f0f406..55072e6 100644
    a b class Options(object):  
    233233
    234234    def _fill_fields_cache(self):
    235235        cache = []
     236        checks = {}
    236237        for parent in self.parents:
    237238            for field, model in parent._meta.get_fields_with_model():
    238239                if model:
    239240                    cache.append((field, model))
    240241                else:
    241242                    cache.append((field, parent))
    242         cache.extend([(f, None) for f in self.local_fields])
     243                checks[field.name] = field
     244        cache.extend([(f, None) for f in self.local_fields if f.name not in checks or not f._is_duplicate(checks[f.name])])
    243245        self._field_cache = tuple(cache)
    244246        self._field_name_cache = [x for x, _ in cache]
    245247
    class Options(object):  
    263265
    264266    def _fill_m2m_cache(self):
    265267        cache = SortedDict()
     268        checks = {}
    266269        for parent in self.parents:
    267270            for field, model in parent._meta.get_m2m_with_model():
    268271                if model:
    269272                    cache[field] = model
    270273                else:
    271274                    cache[field] = parent
     275                checks[field.name] = field
    272276        for field in self.local_many_to_many:
    273             cache[field] = None
     277            if field.name not in checks or not field._is_duplicate(checks[field.name]):
     278                cache[field] = None
    274279        self._m2m_cache = cache
    275280
    276281    def get_field(self, name, many_to_many=True):
  • tests/modeltests/model_inheritance/models.py

    diff --git a/tests/modeltests/model_inheritance/models.py b/tests/modeltests/model_inheritance/models.py
    index a0fed8a..46cf8f6 100644
    a b class StudentWorker(Student, Worker):  
    4848class Post(models.Model):
    4949    title = models.CharField(max_length=50)
    5050
     51class Tag(models.Model):
     52    name = models.CharField(max_length=10)
     53
    5154class Attachment(models.Model):
    5255    post = models.ForeignKey(Post, related_name='attached_%(class)s_set')
    5356    content = models.TextField()
     57    tags = models.ManyToManyField(Tag, related_name='tagged_%(class)s_set')
    5458
    5559    class Meta:
    5660        abstract = True
    class Comment(Attachment):  
    6468class Link(Attachment):
    6569    url = models.URLField()
    6670
     71class OrangeAttachment(Attachment):
     72    class Meta:
     73        abstract = True
     74
     75class BananaAttachment(Attachment):
     76    class Meta:
     77        abstract = True
     78
     79class BlueberryAttachment(Attachment):
     80    class Meta:
     81        abstract = True
     82
    6783#
    6884# Multi-table inheritance
    6985#
  • tests/modeltests/model_inheritance/tests.py

    diff --git a/tests/modeltests/model_inheritance/tests.py b/tests/modeltests/model_inheritance/tests.py
    index 334297a..5e61784 100644
    a b from django.core.exceptions import FieldError  
    44from django.test import TestCase
    55
    66from 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, BlueberryAttachment)
    89
    910
    1011class ModelInheritanceTests(TestCase):
    class ModelInheritanceTests(TestCase):  
    6970            StudentWorker.objects.get, pk__lt=sw2.pk + 100
    7071        )
    7172
     73    def test_multiple_abstract_inheritance(self):
     74        # Define class here to avoid model validation errors while running tests.
     75        def check_duplicates(cls):
     76            field_names = [field.name for field in cls._meta.fields]
     77            field_name_set = list(set(field_names))
     78            self.assertEqual(sorted(field_names), sorted(field_name_set))
     79            m2m_names = [field.name for field in cls._meta.many_to_many]
     80            m2m_name_set = list(set(m2m_names))
     81            self.assertEqual(sorted(m2m_names), sorted(m2m_name_set))
     82
     83        class SmoothieAttachment(OrangeAttachment, BananaAttachment):
     84            class Meta:
     85                app_label = 'model_inheritance'
     86
     87        check_duplicates(SmoothieAttachment)
     88
     89        class AbstractSmoothieAttachment(OrangeAttachment, BananaAttachment):
     90            class Meta:
     91                abstract = True
     92                app_label = 'model_inheritance'
     93
     94        check_duplicates(AbstractSmoothieAttachment)
     95
     96        class BlueberrySmoothieAttachment(BlueberryAttachment, SmoothieAttachment):
     97            class Meta:
     98                app_label = 'model_inheritance'
     99
     100        check_duplicates(BlueberrySmoothieAttachment)
     101
     102        class SmoothieBlueberryAttachment(SmoothieAttachment, BlueberryAttachment):
     103            class Meta:
     104                app_label = 'model_inheritance'
     105
     106        check_duplicates(SmoothieBlueberryAttachment)
     107
    72108    def test_multiple_table(self):
    73109        post = Post.objects.create(title="Lorem Ipsum")
    74110        # The Post model has distinct accessors for the Comment and Link models.
Back to Top