Fixed #31420 -- Fixed crash when filtering subquery annotation against a SimpleLazyObject.

Thanks Simon Charette for the solution and analysis.
This commit is contained in:
Hasan Ramezani 2020-04-04 20:55:20 +02:00 committed by Mariusz Felisiak
parent fa5e7e46d8
commit 4237050684
4 changed files with 28 additions and 4 deletions

View File

@ -233,7 +233,8 @@ class Query(BaseExpression):
@property @property
def output_field(self): def output_field(self):
if len(self.select) == 1: if len(self.select) == 1:
return self.select[0].field select = self.select[0]
return getattr(select, 'target', None) or select.field
elif len(self.annotation_select) == 1: elif len(self.annotation_select) == 1:
return next(iter(self.annotation_select.values())).output_field return next(iter(self.annotation_select.values())).output_field

View File

@ -9,4 +9,6 @@ Django 3.0.6 fixes several bugs in 3.0.5.
Bugfixes Bugfixes
======== ========
* ... * Fixed a regression in Django 3.0 that caused a crash when filtering a
``Subquery()`` annotation of a queryset containing a single related field
against a ``SimpleLazyObject`` (:ticket:`31420`).

View File

@ -6,10 +6,15 @@ import uuid
from django.db import models from django.db import models
class Manager(models.Model):
name = models.CharField(max_length=50)
class Employee(models.Model): class Employee(models.Model):
firstname = models.CharField(max_length=50) firstname = models.CharField(max_length=50)
lastname = models.CharField(max_length=50) lastname = models.CharField(max_length=50)
salary = models.IntegerField(blank=True, null=True) salary = models.IntegerField(blank=True, null=True)
manager = models.ForeignKey(Manager, models.CASCADE, null=True)
def __str__(self): def __str__(self):
return '%s %s' % (self.firstname, self.lastname) return '%s %s' % (self.firstname, self.lastname)

View File

@ -21,10 +21,11 @@ from django.db.models.sql import constants
from django.db.models.sql.datastructures import Join from django.db.models.sql.datastructures import Join
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.test.utils import Approximate, isolate_apps from django.test.utils import Approximate, isolate_apps
from django.utils.functional import SimpleLazyObject
from .models import ( from .models import (
UUID, UUIDPK, Company, Employee, Experiment, Number, RemoteEmployee, UUID, UUIDPK, Company, Employee, Experiment, Manager, Number,
Result, SimulationRun, Time, RemoteEmployee, Result, SimulationRun, Time,
) )
@ -608,6 +609,21 @@ class BasicExpressionsTests(TestCase):
) )
self.assertEqual(qs.get().float, 1.2) self.assertEqual(qs.get().float, 1.2)
def test_subquery_filter_by_lazy(self):
self.max.manager = Manager.objects.create(name='Manager')
self.max.save()
max_manager = SimpleLazyObject(
lambda: Manager.objects.get(pk=self.max.manager.pk)
)
qs = Company.objects.annotate(
ceo_manager=Subquery(
Employee.objects.filter(
lastname=OuterRef('ceo__lastname'),
).values('manager'),
),
).filter(ceo_manager=max_manager)
self.assertEqual(qs.get(), self.gmbh)
def test_aggregate_subquery_annotation(self): def test_aggregate_subquery_annotation(self):
with self.assertNumQueries(1) as ctx: with self.assertNumQueries(1) as ctx:
aggregate = Company.objects.annotate( aggregate = Company.objects.annotate(