Fixed #29648 -- Fixed crash when using subqueries inside datetime truncation functions.

This commit is contained in:
Raphael Michel 2018-08-07 18:08:39 +02:00 committed by Tim Graham
parent 53e8570522
commit 155b31d4ec
3 changed files with 35 additions and 4 deletions

View File

@ -164,8 +164,6 @@ class TruncBase(TimezoneMixin, Transform):
def as_sql(self, compiler, connection): def as_sql(self, compiler, connection):
inner_sql, inner_params = compiler.compile(self.lhs) inner_sql, inner_params = compiler.compile(self.lhs)
# Escape any params because trunc_sql will format the string.
inner_sql = inner_sql.replace('%s', '%%s')
if isinstance(self.output_field, DateTimeField): if isinstance(self.output_field, DateTimeField):
tzname = self.get_tzname() tzname = self.get_tzname()
sql = connection.ops.datetime_trunc_sql(self.kind, inner_sql, tzname) sql = connection.ops.datetime_trunc_sql(self.kind, inner_sql, tzname)

View File

@ -32,6 +32,7 @@ class Fan(models.Model):
name = models.CharField(max_length=50) name = models.CharField(max_length=50)
age = models.PositiveSmallIntegerField(default=30) age = models.PositiveSmallIntegerField(default=30)
author = models.ForeignKey(Author, models.CASCADE, related_name='fans') author = models.ForeignKey(Author, models.CASCADE, related_name='fans')
fan_since = models.DateTimeField(null=True, blank=True)
def __str__(self): def __str__(self):
return self.name return self.name

View File

@ -3,7 +3,9 @@ from datetime import datetime, timedelta
import pytz import pytz
from django.conf import settings from django.conf import settings
from django.db.models import DateField, DateTimeField, IntegerField, TimeField from django.db.models import (
DateField, DateTimeField, IntegerField, Max, OuterRef, Subquery, TimeField,
)
from django.db.models.functions import ( from django.db.models.functions import (
Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth, Extract, ExtractDay, ExtractHour, ExtractMinute, ExtractMonth,
ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear, ExtractQuarter, ExtractSecond, ExtractWeek, ExtractWeekDay, ExtractYear,
@ -15,7 +17,7 @@ from django.test import (
) )
from django.utils import timezone from django.utils import timezone
from .models import DTModel from .models import Author, DTModel, Fan
def truncate_to(value, kind, tzinfo=None): def truncate_to(value, kind, tzinfo=None):
@ -854,6 +856,36 @@ class DateFunctionTests(TestCase):
with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"): with self.assertRaisesMessage(ValueError, "Cannot truncate DateField 'start_date' to DateTimeField"):
list(DTModel.objects.annotate(truncated=TruncSecond('start_date', output_field=DateField()))) list(DTModel.objects.annotate(truncated=TruncSecond('start_date', output_field=DateField())))
def test_trunc_subquery_with_parameters(self):
author_1 = Author.objects.create(name='J. R. R. Tolkien')
author_2 = Author.objects.create(name='G. R. R. Martin')
fan_since_1 = datetime(2016, 2, 3, 15, 0, 0)
fan_since_2 = datetime(2015, 2, 3, 15, 0, 0)
fan_since_3 = datetime(2017, 2, 3, 15, 0, 0)
if settings.USE_TZ:
fan_since_1 = timezone.make_aware(fan_since_1, is_dst=False)
fan_since_2 = timezone.make_aware(fan_since_2, is_dst=False)
fan_since_3 = timezone.make_aware(fan_since_3, is_dst=False)
Fan.objects.create(author=author_1, name='Tom', fan_since=fan_since_1)
Fan.objects.create(author=author_1, name='Emma', fan_since=fan_since_2)
Fan.objects.create(author=author_2, name='Isabella', fan_since=fan_since_3)
inner = Fan.objects.filter(
author=OuterRef('pk'),
name__in=('Emma', 'Isabella', 'Tom')
).values('author').annotate(newest_fan=Max('fan_since')).values('newest_fan')
outer = Author.objects.annotate(
newest_fan_year=TruncYear(Subquery(inner, output_field=DateTimeField()))
)
tz = pytz.UTC if settings.USE_TZ else None
self.assertSequenceEqual(
outer.order_by('name').values('name', 'newest_fan_year'),
[
{'name': 'G. R. R. Martin', 'newest_fan_year': datetime(2017, 1, 1, 0, 0, tzinfo=tz)},
{'name': 'J. R. R. Tolkien', 'newest_fan_year': datetime(2016, 1, 1, 0, 0, tzinfo=tz)},
]
)
@override_settings(USE_TZ=True, TIME_ZONE='UTC') @override_settings(USE_TZ=True, TIME_ZONE='UTC')
class DateFunctionWithTimeZoneTests(DateFunctionTests): class DateFunctionWithTimeZoneTests(DateFunctionTests):