Ticket #7512: null_fk_ordering_r7722.patch

File null_fk_ordering_r7722.patch, 8.3 KB (added by George Vilches, 16 years ago)

Fixes null ForeignKey ordering against r7722.

  • django/db/models/sql/query.py

    diff -r f7a90d86bf60 django/db/models/sql/query.py
    a b  
    10961096        """
    10971097        joins = [alias]
    10981098        last = [0]
     1099        promoted = False
    10991100        for pos, name in enumerate(names):
    11001101            last.append(len(joins))
    11011102            if name == 'pk':
     
    11191120                for alias in joins:
    11201121                    self.unref_alias(alias)
    11211122                raise MultiJoin(pos + 1)
     1123
     1124            # Any time a null FK is encountered, promotion on the join
     1125            # must occur, and must continue along all further joins.
     1126            # Necessary to do it this way since field could be either a
     1127            # Field subclass or a RelatedObject.
     1128            if getattr(field, 'null', None):
     1129                promoted = True
     1130
    11221131            if model:
    11231132                # The field lives on a base class of the current model.
    11241133                alias_list = []
     
    11261135                    lhs_col = opts.parents[int_model].column
    11271136                    opts = int_model._meta
    11281137                    alias = self.join((alias, opts.db_table, lhs_col,
    1129                             opts.pk.column), exclusions=joins)
     1138                            opts.pk.column), promote=promoted, exclusions=joins)
    11301139                    joins.append(alias)
    11311140            cached_data = opts._join_cache.get(name)
    11321141            orig_opts = opts
     
    11511160                                target)
    11521161
    11531162                    int_alias = self.join((alias, table1, from_col1, to_col1),
    1154                             dupe_multis, joins, nullable=True, reuse=can_reuse)
     1163                            dupe_multis, joins, promote=promoted, nullable=True, reuse=can_reuse)
    11551164                    alias = self.join((int_alias, table2, from_col2, to_col2),
    1156                             dupe_multis, joins, nullable=True, reuse=can_reuse)
     1165                            dupe_multis, joins, promote=promoted, nullable=True, reuse=can_reuse)
    11571166                    joins.extend([int_alias, alias])
    11581167                elif field.rel:
    11591168                    # One-to-one or many-to-one field
     
    11691178                                opts, target)
    11701179
    11711180                    alias = self.join((alias, table, from_col, to_col),
    1172                             exclusions=joins, nullable=field.null)
     1181                            exclusions=joins, promote=promoted, nullable=field.null)
    11731182                    joins.append(alias)
    11741183                else:
    11751184                    # Non-relation fields.
     
    11781187            else:
    11791188                orig_field = field
    11801189                field = field.field
     1190
     1191                # See comment above about null FKs.
     1192                if getattr(field, 'null', None):
     1193                    promoted = True
     1194
    11811195                if m2m:
    11821196                    # Many-to-many field defined on the target model.
    11831197                    if cached_data:
     
    11971211                                target)
    11981212
    11991213                    int_alias = self.join((alias, table1, from_col1, to_col1),
    1200                             dupe_multis, joins, nullable=True, reuse=can_reuse)
     1214                            dupe_multis, joins, promote=promoted, nullable=True, reuse=can_reuse)
    12011215                    alias = self.join((int_alias, table2, from_col2, to_col2),
    1202                             dupe_multis, joins, nullable=True, reuse=can_reuse)
     1216                            dupe_multis, joins, promote=promoted, nullable=True, reuse=can_reuse)
    12031217                    joins.extend([int_alias, alias])
    12041218                else:
    12051219                    # One-to-many field (ForeignKey defined on the target model)
     
    12171231                                opts, target)
    12181232
    12191233                    alias = self.join((alias, table, from_col, to_col),
    1220                             dupe_multis, joins, nullable=True, reuse=can_reuse)
     1234                            dupe_multis, joins, promote=promoted, nullable=True, reuse=can_reuse)
    12211235                    joins.append(alias)
    12221236
    12231237        if pos != len(names) - 1:
  • new file tests/regressiontests/null_fk_ordering/models.py

    diff -r f7a90d86bf60 tests/regressiontests/null_fk_ordering/models.py
    - +  
     1"""
     2Regression tests for proper working of ForeignKey(null=True). Tests these bugs:
     3
     4    * #7512: including a nullable foreign key reference in Meta ordering has unexpected results
     5
     6"""
     7
     8from django.db import models
     9
     10# The first two models represent a very simple null FK ordering case.
     11class Author(models.Model):
     12    name = models.CharField(max_length=150)
     13
     14class Article(models.Model):
     15    title = models.CharField(max_length=150)
     16    author = models.ForeignKey(Author, null=True)
     17   
     18    def __unicode__(self):
     19        return u'Article titled: %s' % (self.title, )
     20   
     21    class Meta:
     22        ordering = ['author__name', ]
     23   
     24
     25# These following 4 models represent a far more complex ordering case.
     26class SystemInfo(models.Model):
     27    system_name = models.CharField(max_length=32)
     28
     29class Forum(models.Model):
     30    system_info = models.ForeignKey(SystemInfo)
     31    forum_name = models.CharField(max_length=32)
     32
     33class Post(models.Model):
     34    forum = models.ForeignKey(Forum, null=True)
     35    title = models.CharField(max_length=32)
     36
     37    def __unicode__(self):
     38        return self.title
     39
     40class Comment(models.Model):
     41    post = models.ForeignKey(Post, null=True)
     42    comment_text = models.CharField(max_length=250)
     43
     44    class Meta:
     45        ordering = ['post__forum__system_info__system_name', 'comment_text']
     46
     47    def __unicode__(self):
     48        return self.comment_text
     49
     50__test__ = {'API_TESTS':"""
     51
     52class Author(models.Model):
     53    name = models.CharField(max_length=150)
     54
     55class Article(models.Model):
     56    title = models.CharField(max_length=150)
     57    author = models.ForeignKey(Author, null=True)
     58>>> author_1 = Author.objects.create(name='Tom Jones')
     59>>> author_2 = Author.objects.create(name='Bob Smith')
     60>>> article_1 = Article.objects.create(title='No author on this article')
     61>>> article_2 = Article.objects.create(author=author_1, title='This article written by Tom Jones')
     62>>> article_3 = Article.objects.create(author=author_2, title='This article written by Bob Smith')
     63>>> Article.objects.all()
     64[<Article: Article titled: No author on this article>, <Article: Article titled: This article written by Bob Smith>, <Article: Article titled: This article written by Tom Jones>]
     65
     66>>> s = SystemInfo.objects.create(system_name='System Info')
     67>>> f = Forum.objects.create(system_info=s, forum_name='First forum')
     68>>> p = Post.objects.create(forum=f, title='First Post')
     69>>> c1 = Comment.objects.create(post=p, comment_text='My first comment')
     70>>> c2 = Comment.objects.create(comment_text='My second comment')
     71>>> s2 = SystemInfo.objects.create(system_name='More System Info')
     72>>> f2 = Forum.objects.create(system_info=s2, forum_name='Second forum')
     73>>> p2 = Post.objects.create(forum=f2, title='Second Post')
     74>>> c3 = Comment.objects.create(comment_text='Another first comment')
     75>>> c4 = Comment.objects.create(post=p2, comment_text='Another second comment')
     76
     77>>> Comment.objects.all()
     78[<Comment: Another first comment>, <Comment: My second comment>, <Comment: Another second comment>, <Comment: My first comment>]
     79
     80"""}
  • tests/regressiontests/queries/models.py

    diff -r f7a90d86bf60 tests/regressiontests/queries/models.py
    a b  
    230230Checking that no join types are "left outer" joins.
    231231>>> query = Item.objects.filter(tags=t2).query
    232232>>> query.LOUTER not in [x[2] for x in query.alias_map.values()]
    233 True
     233False
    234234
    235235>>> Item.objects.filter(Q(tags=t1)).order_by('name')
    236236[<Item: one>, <Item: two>]
     
    479479# ForeignKey) is legal, but the results might not make sense. That isn't
    480480# Django's problem. Garbage in, garbage out.
    481481>>> Item.objects.all().order_by('tags', 'id')
    482 [<Item: one>, <Item: two>, <Item: one>, <Item: two>, <Item: four>]
     482[<Item: three>, <Item: one>, <Item: two>, <Item: one>, <Item: two>, <Item: four>]
    483483
    484484# If we replace the default ordering, Django adjusts the required tables
    485485# automatically. Item normally requires a join with Note to do the default
Back to Top