Fixed #24986 -- Added support for annotations in DISTINCT queries.
This commit is contained in:
parent
98bcdfa8bd
commit
1f7b25c1a7
2
AUTHORS
2
AUTHORS
|
@ -208,6 +208,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Dmitri Fedortchenko <zeraien@gmail.com>
|
Dmitri Fedortchenko <zeraien@gmail.com>
|
||||||
Dmitry Jemerov <intelliyole@gmail.com>
|
Dmitry Jemerov <intelliyole@gmail.com>
|
||||||
dne@mayonnaise.net
|
dne@mayonnaise.net
|
||||||
|
Donald Harvey <donald@donaldharvey.co.uk>
|
||||||
Donald Stufft <donald@stufft.io>
|
Donald Stufft <donald@stufft.io>
|
||||||
Don Spaulding <donspauldingii@gmail.com>
|
Don Spaulding <donspauldingii@gmail.com>
|
||||||
Doug Beck <doug@douglasbeck.com>
|
Doug Beck <doug@douglasbeck.com>
|
||||||
|
@ -710,6 +711,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Tyler Tarabula <tyler.tarabula@gmail.com>
|
Tyler Tarabula <tyler.tarabula@gmail.com>
|
||||||
Tyson Tate <tyson@fallingbullets.com>
|
Tyson Tate <tyson@fallingbullets.com>
|
||||||
Unai Zalakain <unai@gisa-elkartea.org>
|
Unai Zalakain <unai@gisa-elkartea.org>
|
||||||
|
Valentina Mukhamedzhanova <umirra@gmail.com>
|
||||||
valtron
|
valtron
|
||||||
Vasiliy Stavenko <stavenko@gmail.com>
|
Vasiliy Stavenko <stavenko@gmail.com>
|
||||||
Vasil Vangelovski
|
Vasil Vangelovski
|
||||||
|
|
|
@ -548,6 +548,9 @@ class SQLCompiler(object):
|
||||||
_, targets, alias, joins, path, _ = self._setup_joins(parts, opts, None)
|
_, targets, alias, joins, path, _ = self._setup_joins(parts, opts, None)
|
||||||
targets, alias, _ = self.query.trim_joins(targets, joins, path)
|
targets, alias, _ = self.query.trim_joins(targets, joins, path)
|
||||||
for target in targets:
|
for target in targets:
|
||||||
|
if name in self.query.annotation_select:
|
||||||
|
result.append(name)
|
||||||
|
else:
|
||||||
result.append("%s.%s" % (qn(alias), qn2(target.column)))
|
result.append("%s.%s" % (qn(alias), qn2(target.column)))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -1292,9 +1292,15 @@ class Query(object):
|
||||||
cur_names_with_path = (name, [])
|
cur_names_with_path = (name, [])
|
||||||
if name == 'pk':
|
if name == 'pk':
|
||||||
name = opts.pk.name
|
name = opts.pk.name
|
||||||
|
|
||||||
|
field = None
|
||||||
try:
|
try:
|
||||||
field = opts.get_field(name)
|
field = opts.get_field(name)
|
||||||
|
except FieldDoesNotExist:
|
||||||
|
if name in self.annotation_select:
|
||||||
|
field = self.annotation_select[name].output_field
|
||||||
|
|
||||||
|
if field is not None:
|
||||||
# Fields that contain one-to-many relations with a generic
|
# Fields that contain one-to-many relations with a generic
|
||||||
# model (like a GenericForeignKey) cannot generate reverse
|
# model (like a GenericForeignKey) cannot generate reverse
|
||||||
# relations and therefore cannot be used for reverse querying.
|
# relations and therefore cannot be used for reverse querying.
|
||||||
|
@ -1305,8 +1311,11 @@ class Query(object):
|
||||||
"querying. If it is a GenericForeignKey, consider "
|
"querying. If it is a GenericForeignKey, consider "
|
||||||
"adding a GenericRelation." % name
|
"adding a GenericRelation." % name
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
model = field.model._meta.concrete_model
|
model = field.model._meta.concrete_model
|
||||||
except FieldDoesNotExist:
|
except AttributeError:
|
||||||
|
model = None
|
||||||
|
else:
|
||||||
# We didn't find the current field, so move position back
|
# We didn't find the current field, so move position back
|
||||||
# one step.
|
# one step.
|
||||||
pos -= 1
|
pos -= 1
|
||||||
|
|
|
@ -462,6 +462,8 @@ Models
|
||||||
:attr:`~django.db.models.SlugField.allow_unicode` argument to allow Unicode
|
:attr:`~django.db.models.SlugField.allow_unicode` argument to allow Unicode
|
||||||
characters in slugs.
|
characters in slugs.
|
||||||
|
|
||||||
|
* Added support for referencing annotations in ``QuerySet.distinct()``.
|
||||||
|
|
||||||
CSRF
|
CSRF
|
||||||
^^^^
|
^^^^
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ from django.db.models import (
|
||||||
F, BooleanField, CharField, Count, DateTimeField, ExpressionWrapper, Func,
|
F, BooleanField, CharField, Count, DateTimeField, ExpressionWrapper, Func,
|
||||||
IntegerField, Sum, Value,
|
IntegerField, Sum, Value,
|
||||||
)
|
)
|
||||||
from django.test import TestCase
|
from django.db.models.functions import Lower
|
||||||
|
from django.test import TestCase, skipUnlessDBFeature
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
|
@ -160,6 +161,40 @@ class NonAggregateAnnotationTestCase(TestCase):
|
||||||
other_agg = Author.objects.aggregate(age_sum=Sum('age'))
|
other_agg = Author.objects.aggregate(age_sum=Sum('age'))
|
||||||
self.assertEqual(agg['otherage_sum'], other_agg['age_sum'])
|
self.assertEqual(agg['otherage_sum'], other_agg['age_sum'])
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('can_distinct_on_fields')
|
||||||
|
def test_distinct_on_with_annotation(self):
|
||||||
|
store = Store.objects.create(
|
||||||
|
name='test store',
|
||||||
|
original_opening=datetime.datetime.now(),
|
||||||
|
friday_night_closing=datetime.time(21, 00, 00),
|
||||||
|
)
|
||||||
|
names = [
|
||||||
|
'Theodore Roosevelt',
|
||||||
|
'Eleanor Roosevelt',
|
||||||
|
'Franklin Roosevelt',
|
||||||
|
'Ned Stark',
|
||||||
|
'Catelyn Stark',
|
||||||
|
]
|
||||||
|
for name in names:
|
||||||
|
Employee.objects.create(
|
||||||
|
store=store,
|
||||||
|
first_name=name.split()[0],
|
||||||
|
last_name=name.split()[1],
|
||||||
|
age=30, salary=2000,
|
||||||
|
)
|
||||||
|
|
||||||
|
people = Employee.objects.annotate(
|
||||||
|
name_lower=Lower('last_name'),
|
||||||
|
).distinct('name_lower')
|
||||||
|
|
||||||
|
self.assertEqual(set(p.last_name for p in people), {'Stark', 'Roosevelt'})
|
||||||
|
self.assertEqual(len(people), 2)
|
||||||
|
|
||||||
|
people2 = Employee.objects.annotate(
|
||||||
|
test_alias=F('store__name'),
|
||||||
|
).distinct('test_alias')
|
||||||
|
self.assertEqual(len(people2), 1)
|
||||||
|
|
||||||
def test_filter_annotation(self):
|
def test_filter_annotation(self):
|
||||||
books = Book.objects.annotate(
|
books = Book.objects.annotate(
|
||||||
is_book=Value(1, output_field=IntegerField())
|
is_book=Value(1, output_field=IntegerField())
|
||||||
|
|
Loading…
Reference in New Issue