Fixed #24986 -- Added support for annotations in DISTINCT queries.

This commit is contained in:
Valentina Mukhamedzhanova 2015-08-14 20:22:08 +02:00 committed by Tim Graham
parent 98bcdfa8bd
commit 1f7b25c1a7
5 changed files with 55 additions and 4 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
^^^^ ^^^^

View File

@ -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())