309 lines
11 KiB
Python
309 lines
11 KiB
Python
from django.test import TestCase
|
|
|
|
from .models import (
|
|
Event,
|
|
Movie,
|
|
Package,
|
|
PackageNullFK,
|
|
Person,
|
|
Screening,
|
|
ScreeningNullFK,
|
|
)
|
|
|
|
|
|
# These are tests for #16715. The basic scheme is always the same: 3 models with
|
|
# 2 relations. The first relation may be null, while the second is non-nullable.
|
|
# In some cases, Django would pick the wrong join type for the second relation,
|
|
# resulting in missing objects in the queryset.
|
|
#
|
|
# Model A
|
|
# | (Relation A/B : nullable)
|
|
# Model B
|
|
# | (Relation B/C : non-nullable)
|
|
# Model C
|
|
#
|
|
# Because of the possibility of NULL rows resulting from the LEFT OUTER JOIN
|
|
# between Model A and Model B (i.e. instances of A without reference to B),
|
|
# the second join must also be LEFT OUTER JOIN, so that we do not ignore
|
|
# instances of A that do not reference B.
|
|
#
|
|
# Relation A/B can either be an explicit foreign key or an implicit reverse
|
|
# relation such as introduced by one-to-one relations (through multi-table
|
|
# inheritance).
|
|
class NestedForeignKeysTests(TestCase):
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.director = Person.objects.create(name="Terry Gilliam / Terry Jones")
|
|
cls.movie = Movie.objects.create(
|
|
title="Monty Python and the Holy Grail", director=cls.director
|
|
)
|
|
|
|
# This test failed in #16715 because in some cases INNER JOIN was selected
|
|
# for the second foreign key relation instead of LEFT OUTER JOIN.
|
|
def test_inheritance(self):
|
|
Event.objects.create()
|
|
Screening.objects.create(movie=self.movie)
|
|
|
|
self.assertEqual(len(Event.objects.all()), 2)
|
|
self.assertEqual(len(Event.objects.select_related("screening")), 2)
|
|
# This failed.
|
|
self.assertEqual(len(Event.objects.select_related("screening__movie")), 2)
|
|
|
|
self.assertEqual(len(Event.objects.values()), 2)
|
|
self.assertEqual(len(Event.objects.values("screening__pk")), 2)
|
|
self.assertEqual(len(Event.objects.values("screening__movie__pk")), 2)
|
|
self.assertEqual(len(Event.objects.values("screening__movie__title")), 2)
|
|
# This failed.
|
|
self.assertEqual(
|
|
len(
|
|
Event.objects.values("screening__movie__pk", "screening__movie__title")
|
|
),
|
|
2,
|
|
)
|
|
|
|
# Simple filter/exclude queries for good measure.
|
|
self.assertEqual(Event.objects.filter(screening__movie=self.movie).count(), 1)
|
|
self.assertEqual(Event.objects.exclude(screening__movie=self.movie).count(), 1)
|
|
|
|
# These all work because the second foreign key in the chain has null=True.
|
|
def test_inheritance_null_FK(self):
|
|
Event.objects.create()
|
|
ScreeningNullFK.objects.create(movie=None)
|
|
ScreeningNullFK.objects.create(movie=self.movie)
|
|
|
|
self.assertEqual(len(Event.objects.all()), 3)
|
|
self.assertEqual(len(Event.objects.select_related("screeningnullfk")), 3)
|
|
self.assertEqual(len(Event.objects.select_related("screeningnullfk__movie")), 3)
|
|
|
|
self.assertEqual(len(Event.objects.values()), 3)
|
|
self.assertEqual(len(Event.objects.values("screeningnullfk__pk")), 3)
|
|
self.assertEqual(len(Event.objects.values("screeningnullfk__movie__pk")), 3)
|
|
self.assertEqual(len(Event.objects.values("screeningnullfk__movie__title")), 3)
|
|
self.assertEqual(
|
|
len(
|
|
Event.objects.values(
|
|
"screeningnullfk__movie__pk", "screeningnullfk__movie__title"
|
|
)
|
|
),
|
|
3,
|
|
)
|
|
|
|
self.assertEqual(
|
|
Event.objects.filter(screeningnullfk__movie=self.movie).count(), 1
|
|
)
|
|
self.assertEqual(
|
|
Event.objects.exclude(screeningnullfk__movie=self.movie).count(), 2
|
|
)
|
|
|
|
def test_null_exclude(self):
|
|
screening = ScreeningNullFK.objects.create(movie=None)
|
|
ScreeningNullFK.objects.create(movie=self.movie)
|
|
self.assertEqual(
|
|
list(ScreeningNullFK.objects.exclude(movie__id=self.movie.pk)), [screening]
|
|
)
|
|
|
|
# This test failed in #16715 because in some cases INNER JOIN was selected
|
|
# for the second foreign key relation instead of LEFT OUTER JOIN.
|
|
def test_explicit_ForeignKey(self):
|
|
Package.objects.create()
|
|
screening = Screening.objects.create(movie=self.movie)
|
|
Package.objects.create(screening=screening)
|
|
|
|
self.assertEqual(len(Package.objects.all()), 2)
|
|
self.assertEqual(len(Package.objects.select_related("screening")), 2)
|
|
self.assertEqual(len(Package.objects.select_related("screening__movie")), 2)
|
|
|
|
self.assertEqual(len(Package.objects.values()), 2)
|
|
self.assertEqual(len(Package.objects.values("screening__pk")), 2)
|
|
self.assertEqual(len(Package.objects.values("screening__movie__pk")), 2)
|
|
self.assertEqual(len(Package.objects.values("screening__movie__title")), 2)
|
|
# This failed.
|
|
self.assertEqual(
|
|
len(
|
|
Package.objects.values(
|
|
"screening__movie__pk", "screening__movie__title"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
|
|
self.assertEqual(Package.objects.filter(screening__movie=self.movie).count(), 1)
|
|
self.assertEqual(
|
|
Package.objects.exclude(screening__movie=self.movie).count(), 1
|
|
)
|
|
|
|
# These all work because the second foreign key in the chain has null=True.
|
|
def test_explicit_ForeignKey_NullFK(self):
|
|
PackageNullFK.objects.create()
|
|
screening = ScreeningNullFK.objects.create(movie=None)
|
|
screening_with_movie = ScreeningNullFK.objects.create(movie=self.movie)
|
|
PackageNullFK.objects.create(screening=screening)
|
|
PackageNullFK.objects.create(screening=screening_with_movie)
|
|
|
|
self.assertEqual(len(PackageNullFK.objects.all()), 3)
|
|
self.assertEqual(len(PackageNullFK.objects.select_related("screening")), 3)
|
|
self.assertEqual(
|
|
len(PackageNullFK.objects.select_related("screening__movie")), 3
|
|
)
|
|
|
|
self.assertEqual(len(PackageNullFK.objects.values()), 3)
|
|
self.assertEqual(len(PackageNullFK.objects.values("screening__pk")), 3)
|
|
self.assertEqual(len(PackageNullFK.objects.values("screening__movie__pk")), 3)
|
|
self.assertEqual(
|
|
len(PackageNullFK.objects.values("screening__movie__title")), 3
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
PackageNullFK.objects.values(
|
|
"screening__movie__pk", "screening__movie__title"
|
|
)
|
|
),
|
|
3,
|
|
)
|
|
|
|
self.assertEqual(
|
|
PackageNullFK.objects.filter(screening__movie=self.movie).count(), 1
|
|
)
|
|
self.assertEqual(
|
|
PackageNullFK.objects.exclude(screening__movie=self.movie).count(), 2
|
|
)
|
|
|
|
|
|
# Some additional tests for #16715. The only difference is the depth of the
|
|
# nesting as we now use 4 models instead of 3 (and thus 3 relations). This
|
|
# checks if promotion of join types works for deeper nesting too.
|
|
class DeeplyNestedForeignKeysTests(TestCase):
|
|
@classmethod
|
|
def setUpTestData(cls):
|
|
cls.director = Person.objects.create(name="Terry Gilliam / Terry Jones")
|
|
cls.movie = Movie.objects.create(
|
|
title="Monty Python and the Holy Grail", director=cls.director
|
|
)
|
|
|
|
def test_inheritance(self):
|
|
Event.objects.create()
|
|
Screening.objects.create(movie=self.movie)
|
|
|
|
self.assertEqual(len(Event.objects.all()), 2)
|
|
self.assertEqual(
|
|
len(Event.objects.select_related("screening__movie__director")), 2
|
|
)
|
|
|
|
self.assertEqual(len(Event.objects.values()), 2)
|
|
self.assertEqual(len(Event.objects.values("screening__movie__director__pk")), 2)
|
|
self.assertEqual(
|
|
len(Event.objects.values("screening__movie__director__name")), 2
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Event.objects.values(
|
|
"screening__movie__director__pk", "screening__movie__director__name"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Event.objects.values(
|
|
"screening__movie__pk", "screening__movie__director__pk"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Event.objects.values(
|
|
"screening__movie__pk", "screening__movie__director__name"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Event.objects.values(
|
|
"screening__movie__title", "screening__movie__director__pk"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Event.objects.values(
|
|
"screening__movie__title", "screening__movie__director__name"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
|
|
self.assertEqual(
|
|
Event.objects.filter(screening__movie__director=self.director).count(), 1
|
|
)
|
|
self.assertEqual(
|
|
Event.objects.exclude(screening__movie__director=self.director).count(), 1
|
|
)
|
|
|
|
def test_explicit_ForeignKey(self):
|
|
Package.objects.create()
|
|
screening = Screening.objects.create(movie=self.movie)
|
|
Package.objects.create(screening=screening)
|
|
|
|
self.assertEqual(len(Package.objects.all()), 2)
|
|
self.assertEqual(
|
|
len(Package.objects.select_related("screening__movie__director")), 2
|
|
)
|
|
|
|
self.assertEqual(len(Package.objects.values()), 2)
|
|
self.assertEqual(
|
|
len(Package.objects.values("screening__movie__director__pk")), 2
|
|
)
|
|
self.assertEqual(
|
|
len(Package.objects.values("screening__movie__director__name")), 2
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Package.objects.values(
|
|
"screening__movie__director__pk", "screening__movie__director__name"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Package.objects.values(
|
|
"screening__movie__pk", "screening__movie__director__pk"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Package.objects.values(
|
|
"screening__movie__pk", "screening__movie__director__name"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Package.objects.values(
|
|
"screening__movie__title", "screening__movie__director__pk"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
self.assertEqual(
|
|
len(
|
|
Package.objects.values(
|
|
"screening__movie__title", "screening__movie__director__name"
|
|
)
|
|
),
|
|
2,
|
|
)
|
|
|
|
self.assertEqual(
|
|
Package.objects.filter(screening__movie__director=self.director).count(), 1
|
|
)
|
|
self.assertEqual(
|
|
Package.objects.exclude(screening__movie__director=self.director).count(), 1
|
|
)
|