Ticket #18174: ticket_18174_patch.diff

File ticket_18174_patch.diff, 7.1 KB (added by phowe, 3 years ago)
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    index 7308a15..540b505 100644
    a b class Options(object): 
    478478            result.update(parent._meta.get_parent_list())
    479479        return result
    480480
    481     def get_ancestor_link(self, ancestor):
     481    def get_ancestor_link(self, ancestor,grandparents=True):
    482482        """
    483483        Returns the field on the current model which points to the given
    484484        "ancestor". This is possible an indirect link (a pointer to a parent
    class Options(object): 
    489489        """
    490490        if ancestor in self.parents:
    491491            return self.parents[ancestor]
     492        if not grandparents:
     493          return None
    492494        for parent in self.parents:
    493495            # Tries to get a link field from the immediate parent
    494496            parent_link = parent._meta.get_ancestor_link(ancestor)
  • django/db/models/sql/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    index 53dad60..df9da5c 100644
    a b class Query(object): 
    877877        If 'nullable' is True, the join can potentially involve NULL values and
    878878        is a candidate for promotion (to "left outer") when combining querysets.
    879879        """
     880       
    880881        lhs, table, lhs_col, col = connection
    881882        if lhs in self.alias_map:
    882883            lhs_table = self.alias_map[lhs].table_name
    class Query(object): 
    938939        root_alias = self.tables[0]
    939940        seen = {None: root_alias}
    940941
    941         for field, model in opts.get_fields_with_model():
    942             if model not in seen:
    943                 link_field = opts.get_ancestor_link(model)
    944                 seen[model] = self.join((root_alias, model._meta.db_table,
    945                         link_field.column, model._meta.pk.column))
     942        ancestors = []
     943        redo = [i for i in opts.get_fields_with_model()]
     944        todo = []
     945
     946        # We maintain a list of things todo, and models we coudn't link from this
     947        # for the next.  If the lenth of both is the same, the previous loop didn't
     948        # Link anything, let's bail so we don't end up with an endless loop
     949        while len(redo) > 0 and len(redo) != len(todo):
     950            todo = redo
     951            redo = []
     952            for field, model in todo:
     953                link_field = None
     954                if model not in seen:
     955                    link_field = opts.get_ancestor_link(model,grandparents=False)
     956                    if link_field:
     957                        seen[model] = self.join((root_alias, model._meta.db_table,link_field.column, model._meta.pk.column))
     958                        ancestors.append(model)
     959                    else: # we didn't link this model to our curent model, try the ancestors
     960                        for ancestor in ancestors:
     961                            link_field = ancestor._meta.concrete_model._meta.get_ancestor_link(model,grandparents=False)
     962                            if link_field:
     963                                seen[model] = self.join((ancestor._meta.db_table, model._meta.db_table,link_field.column, model._meta.pk.column))
     964                                ancestors.append(model)
     965                                break
     966                    if not link_field: # didn't find it in the ancestors either, add it to the list to try next time
     967                       redo.append((field, model))
     968
    946969        self.included_inherited_models = seen
    947970
    948971    def remove_inherited_models(self):
  • tests/regressiontests/queries/models.py

    diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
    index 6328776..2eda9e5 100644
    a b class NullableName(models.Model): 
    358358
    359359    class Meta:
    360360        ordering = ['id']
     361
     362
     363class Base1(models.Model):
     364    b1_id = models.AutoField(primary_key=True)
     365    b1_desc = models.CharField(max_length=100, null=True)
     366
     367class Base2(models.Model):
     368    b2_id = models.AutoField(primary_key=True)
     369    b2_desc = models.CharField(max_length=100, null=True)
     370
     371class Base3(models.Model):
     372    b3_id = models.AutoField(primary_key=True)
     373    b3_desc = models.CharField(max_length=100, null=True)
     374
     375class Middle(Base1, Base2, Base3):
     376    m_desc = models.CharField(max_length=100, null=True)
     377
     378class Top(Middle):
     379    t_desc = models.CharField(max_length=100)
     380
     381class Top2(Top):
     382    t2_desc = models.CharField(max_length=100)
     383
     384class Top3(Top2):
     385    t3_desc = models.CharField(max_length=100)
     386
     387class Top4(Top3):
     388    t4_desc = models.CharField(max_length=100)
     389
  • tests/regressiontests/queries/tests.py

    diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py
    index 1582993..03fe879 100644
    a b from .models import (Annotation, Article, Author, Celebrity, Child, Cover, 
    2323    Ranking, Related, Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten,
    2424    Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory,
    2525    SpecialCategory, OneToOneCategory, NullableName, ProxyCategory,
    26     SingleObject, RelatedObject)
     26    SingleObject, RelatedObject, Top, Base1, Top2, Top3, Top4)
    2727
    2828
    2929class BaseQuerysetTest(TestCase):
    class Queries1Tests(BaseQuerysetTest): 
    847847        )
    848848        Tag._meta.ordering = original_ordering
    849849
     850    def test_ticket18174(self):
     851        """
     852        Check that in a model like this:
     853                         / Base1
     854            Top -> Middle- Base2
     855                         \ Base3
     856
     857        The joins are done from Top to Middle to Base1, Base2, Base3.
     858        """
     859        Base1.objects.create()
     860        Top.objects.create(t_desc='foo')
     861        qs = Top.objects.all()
     862        # The joins to the parent tables are added only when the query is
     863        # evaluated.
     864        results = list(qs)
     865        self.assertEqual(qs.query.alias_map['queries_base1'].lhs_alias, 'queries_middle')
     866        self.assertEqual(len(results), 1)
     867
    850868    def test_exclude(self):
    851869        self.assertQuerysetEqual(
    852870            Item.objects.exclude(tags__name='t4'),
    class Queries1Tests(BaseQuerysetTest): 
    880898            Item.objects.filter(Q(tags__name__in=['t4', 't3'])),
    881899            [repr(i) for i in Item.objects.filter(~~Q(tags__name__in=['t4', 't3']))])
    882900
     901
     902    def test_ticket18174_2(self):
     903        """
     904        Check that in a model like this:
     905                                                 / Base1
     906            Top4 -> Top3 -> Top2 -> Top -> Middle- Base2
     907                                                 \ Base3
     908
     909        The joins are done from Top to Middle to Base1, Base2, Base3.
     910        Adding Extra Grandparents to make sure the chaning to the top works.
     911        """
     912        Base1.objects.create()
     913        Top4.objects.create(t_desc='foo')
     914        qs = Top4.objects.all()
     915        # The joins to the parent tables are added only when the query is
     916        # evaluated.
     917        results = list(qs)
     918        self.assertEqual(qs.query.alias_map['queries_base1'].lhs_alias, 'queries_middle')
     919        self.assertEqual(len(results), 1)
     920
     921
    883922class Queries2Tests(TestCase):
    884923    def setUp(self):
    885924        Number.objects.create(num=4)
Back to Top