Loading django/db/models/sql/query.py +3 −2 Original line number Diff line number Diff line Loading @@ -1204,7 +1204,8 @@ class Query(object): if negate: self.promote_joins(join_list) if lookup_type != 'isnull' and (self.is_nullable(target) or len(join_list) > 1): if (lookup_type != 'isnull' and ( self.is_nullable(target) or self.alias_map[join_list[-1]].join_type == self.LOUTER)): # The condition added here will be SQL like this: # NOT (col IS NOT NULL), where the first NOT is added in # upper layers of code. The reason for addition is that if col Loading Loading @@ -1447,7 +1448,7 @@ class Query(object): # nothing if self.is_nullable(query.select[0].field): alias, col = query.select[0].col query.where.add((Constraint(alias, col, None), 'isnull', False), AND) query.where.add((Constraint(alias, col, query.select[0].field), 'isnull', False), AND) # Still make sure that the trimmed parts in the inner query and # trimmed prefix are in sync. So, use the trimmed_joins to make sure Loading tests/queries/models.py +16 −0 Original line number Diff line number Diff line Loading @@ -454,3 +454,19 @@ class Program(models.Model): class Channel(models.Model): programs = models.ManyToManyField(Program) identifier = models.OneToOneField(Identifier) class Book(models.Model): title = models.TextField() chapter = models.ForeignKey('Chapter') class Chapter(models.Model): title = models.TextField() paragraph = models.ForeignKey('Paragraph') class Paragraph(models.Model): text = models.TextField() page = models.ManyToManyField('Page') class Page(models.Model): text = models.TextField() tests/queries/tests.py +24 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,8 @@ from .models import (Annotation, Article, Author, Celebrity, Child, Cover, Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory, SpecialCategory, OneToOneCategory, NullableName, ProxyCategory, SingleObject, RelatedObject, ModelA, ModelD, Responsibility, Job, JobResponsibilities, BaseA, Identifier, Program, Channel) JobResponsibilities, BaseA, Identifier, Program, Channel, Page, Paragraph, Chapter, Book) class BaseQuerysetTest(TestCase): Loading Loading @@ -2638,3 +2639,25 @@ class ManyToManyExcludeTest(TestCase): Identifier.objects.exclude(program__channel=None).order_by('name'), ['<Identifier: program>'] ) def test_ticket_12823(self): pg3 = Page.objects.create(text='pg3') pg2 = Page.objects.create(text='pg2') pg1 = Page.objects.create(text='pg1') pa1 = Paragraph.objects.create(text='pa1') pa1.page = [pg1, pg2] pa2 = Paragraph.objects.create(text='pa2') pa2.page = [pg2, pg3] pa3 = Paragraph.objects.create(text='pa3') ch1 = Chapter.objects.create(title='ch1', paragraph=pa1) ch2 = Chapter.objects.create(title='ch2', paragraph=pa2) ch3 = Chapter.objects.create(title='ch3', paragraph=pa3) b1 = Book.objects.create(title='b1', chapter=ch1) b2 = Book.objects.create(title='b2', chapter=ch2) b3 = Book.objects.create(title='b3', chapter=ch3) q = Book.objects.exclude(chapter__paragraph__page__text='pg1') self.assertNotIn('IS NOT NULL', str(q.query)) self.assertEqual(len(q), 2) self.assertNotIn(b1, q) self.assertIn(b2, q) self.assertIn(b3, q) Loading
django/db/models/sql/query.py +3 −2 Original line number Diff line number Diff line Loading @@ -1204,7 +1204,8 @@ class Query(object): if negate: self.promote_joins(join_list) if lookup_type != 'isnull' and (self.is_nullable(target) or len(join_list) > 1): if (lookup_type != 'isnull' and ( self.is_nullable(target) or self.alias_map[join_list[-1]].join_type == self.LOUTER)): # The condition added here will be SQL like this: # NOT (col IS NOT NULL), where the first NOT is added in # upper layers of code. The reason for addition is that if col Loading Loading @@ -1447,7 +1448,7 @@ class Query(object): # nothing if self.is_nullable(query.select[0].field): alias, col = query.select[0].col query.where.add((Constraint(alias, col, None), 'isnull', False), AND) query.where.add((Constraint(alias, col, query.select[0].field), 'isnull', False), AND) # Still make sure that the trimmed parts in the inner query and # trimmed prefix are in sync. So, use the trimmed_joins to make sure Loading
tests/queries/models.py +16 −0 Original line number Diff line number Diff line Loading @@ -454,3 +454,19 @@ class Program(models.Model): class Channel(models.Model): programs = models.ManyToManyField(Program) identifier = models.OneToOneField(Identifier) class Book(models.Model): title = models.TextField() chapter = models.ForeignKey('Chapter') class Chapter(models.Model): title = models.TextField() paragraph = models.ForeignKey('Paragraph') class Paragraph(models.Model): text = models.TextField() page = models.ManyToManyField('Page') class Page(models.Model): text = models.TextField()
tests/queries/tests.py +24 −1 Original line number Diff line number Diff line Loading @@ -24,7 +24,8 @@ from .models import (Annotation, Article, Author, Celebrity, Child, Cover, Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory, SpecialCategory, OneToOneCategory, NullableName, ProxyCategory, SingleObject, RelatedObject, ModelA, ModelD, Responsibility, Job, JobResponsibilities, BaseA, Identifier, Program, Channel) JobResponsibilities, BaseA, Identifier, Program, Channel, Page, Paragraph, Chapter, Book) class BaseQuerysetTest(TestCase): Loading Loading @@ -2638,3 +2639,25 @@ class ManyToManyExcludeTest(TestCase): Identifier.objects.exclude(program__channel=None).order_by('name'), ['<Identifier: program>'] ) def test_ticket_12823(self): pg3 = Page.objects.create(text='pg3') pg2 = Page.objects.create(text='pg2') pg1 = Page.objects.create(text='pg1') pa1 = Paragraph.objects.create(text='pa1') pa1.page = [pg1, pg2] pa2 = Paragraph.objects.create(text='pa2') pa2.page = [pg2, pg3] pa3 = Paragraph.objects.create(text='pa3') ch1 = Chapter.objects.create(title='ch1', paragraph=pa1) ch2 = Chapter.objects.create(title='ch2', paragraph=pa2) ch3 = Chapter.objects.create(title='ch3', paragraph=pa3) b1 = Book.objects.create(title='b1', chapter=ch1) b2 = Book.objects.create(title='b2', chapter=ch2) b3 = Book.objects.create(title='b3', chapter=ch3) q = Book.objects.exclude(chapter__paragraph__page__text='pg1') self.assertNotIn('IS NOT NULL', str(q.query)) self.assertEqual(len(q), 2) self.assertNotIn(b1, q) self.assertIn(b2, q) self.assertIn(b3, q)