Ticket #7512: null_fk_ordering_r7722.patch
File null_fk_ordering_r7722.patch, 8.3 KB (added by , 16 years ago) |
---|
-
django/db/models/sql/query.py
diff -r f7a90d86bf60 django/db/models/sql/query.py
a b 1096 1096 """ 1097 1097 joins = [alias] 1098 1098 last = [0] 1099 promoted = False 1099 1100 for pos, name in enumerate(names): 1100 1101 last.append(len(joins)) 1101 1102 if name == 'pk': … … 1119 1120 for alias in joins: 1120 1121 self.unref_alias(alias) 1121 1122 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 1122 1131 if model: 1123 1132 # The field lives on a base class of the current model. 1124 1133 alias_list = [] … … 1126 1135 lhs_col = opts.parents[int_model].column 1127 1136 opts = int_model._meta 1128 1137 alias = self.join((alias, opts.db_table, lhs_col, 1129 opts.pk.column), exclusions=joins)1138 opts.pk.column), promote=promoted, exclusions=joins) 1130 1139 joins.append(alias) 1131 1140 cached_data = opts._join_cache.get(name) 1132 1141 orig_opts = opts … … 1151 1160 target) 1152 1161 1153 1162 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) 1155 1164 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) 1157 1166 joins.extend([int_alias, alias]) 1158 1167 elif field.rel: 1159 1168 # One-to-one or many-to-one field … … 1169 1178 opts, target) 1170 1179 1171 1180 alias = self.join((alias, table, from_col, to_col), 1172 exclusions=joins, nullable=field.null)1181 exclusions=joins, promote=promoted, nullable=field.null) 1173 1182 joins.append(alias) 1174 1183 else: 1175 1184 # Non-relation fields. … … 1178 1187 else: 1179 1188 orig_field = field 1180 1189 field = field.field 1190 1191 # See comment above about null FKs. 1192 if getattr(field, 'null', None): 1193 promoted = True 1194 1181 1195 if m2m: 1182 1196 # Many-to-many field defined on the target model. 1183 1197 if cached_data: … … 1197 1211 target) 1198 1212 1199 1213 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) 1201 1215 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) 1203 1217 joins.extend([int_alias, alias]) 1204 1218 else: 1205 1219 # One-to-many field (ForeignKey defined on the target model) … … 1217 1231 opts, target) 1218 1232 1219 1233 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) 1221 1235 joins.append(alias) 1222 1236 1223 1237 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 """ 2 Regression 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 8 from django.db import models 9 10 # The first two models represent a very simple null FK ordering case. 11 class Author(models.Model): 12 name = models.CharField(max_length=150) 13 14 class 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. 26 class SystemInfo(models.Model): 27 system_name = models.CharField(max_length=32) 28 29 class Forum(models.Model): 30 system_info = models.ForeignKey(SystemInfo) 31 forum_name = models.CharField(max_length=32) 32 33 class 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 40 class 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 52 class Author(models.Model): 53 name = models.CharField(max_length=150) 54 55 class 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 230 230 Checking that no join types are "left outer" joins. 231 231 >>> query = Item.objects.filter(tags=t2).query 232 232 >>> query.LOUTER not in [x[2] for x in query.alias_map.values()] 233 True233 False 234 234 235 235 >>> Item.objects.filter(Q(tags=t1)).order_by('name') 236 236 [<Item: one>, <Item: two>] … … 479 479 # ForeignKey) is legal, but the results might not make sense. That isn't 480 480 # Django's problem. Garbage in, garbage out. 481 481 >>> 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>] 483 483 484 484 # If we replace the default ordering, Django adjusts the required tables 485 485 # automatically. Item normally requires a join with Note to do the default