From adc57632bc26cc8fe42bdb6aff463f883214980a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anssi=20K=C3=A4=C3=A4ri=C3=A4inen?= <akaariai@gmail.com>
Date: Tue, 5 May 2015 14:44:33 +0300
Subject: [PATCH] Fixed #24748 -- Fixed incorrect GROUP BY on MySQL in some
 queries

When the query's model had a self-referential foreign key, the
compiler.get_group_by() code incorrectly used the self-referential
foreign key's column (for example parent_id) as GROUP BY clause
when it should have used the model's primary key column (id).
---
 django/db/models/sql/compiler.py    |  7 +++++--
 docs/releases/1.8.2.txt             |  3 +++
 tests/aggregation_regress/models.py |  5 +++++
 tests/aggregation_regress/tests.py  | 14 +++++++++++++-
 4 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index 7ada8e85d0..291ce8298f 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -143,9 +143,12 @@ class SQLCompiler(object):
             # then also add having expressions to group by.
             pk = None
             for expr in expressions:
-                if (expr.output_field.primary_key and
-                        getattr(expr.output_field, 'model') == self.query.model):
+                # Is this a reference to query's base table primary key? If the
+                # expression isn't a Col-like, then skip the expression.
+                if (getattr(expr, 'target', None) == self.query.model._meta.pk and
+                        getattr(expr, 'alias', None) == self.query.tables[0]):
                     pk = expr
+                    break
             if pk:
                 # MySQLism: Columns in HAVING clause must be added to the GROUP BY.
                 expressions = [pk] + [expr for expr in expressions if expr in having]
diff --git a/docs/releases/1.8.2.txt b/docs/releases/1.8.2.txt
index f430eb6cb0..bf54aeefd4 100644
--- a/docs/releases/1.8.2.txt
+++ b/docs/releases/1.8.2.txt
@@ -17,3 +17,6 @@ Bugfixes
 * Corrected join promotion for ``Case`` expressions. For example, annotating a
   query with a  ``Case`` expression could unexpectedly filter out results
   (:ticket:`24766`).
+
+* Fixed incorrect GROUP BY clause generation on MySQL when the query's model
+  has a self-referential foreign key (:ticket:`24748`).
diff --git a/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py
index 2cad0a5486..4d675952c2 100644
--- a/tests/aggregation_regress/models.py
+++ b/tests/aggregation_regress/models.py
@@ -104,3 +104,8 @@ class Bravo(models.Model):
 class Charlie(models.Model):
     alfa = models.ForeignKey(Alfa, null=True)
     bravo = models.ForeignKey(Bravo, null=True)
+
+
+class SelfRefFK(models.Model):
+    name = models.CharField(max_length=50)
+    parent = models.ForeignKey('self', null=True, blank=True, related_name='children')
diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py
index 596f69c2dc..cb469363a0 100644
--- a/tests/aggregation_regress/tests.py
+++ b/tests/aggregation_regress/tests.py
@@ -17,7 +17,7 @@ from django.utils import six
 
 from .models import (
     Alfa, Author, Book, Bravo, Charlie, Clues, Entries, HardbackBook, ItemTag,
-    Publisher, Store, WithManualPK,
+    Publisher, SelfRefFK, Store, WithManualPK,
 )
 
 
@@ -1277,3 +1277,15 @@ class JoinPromotionTests(TestCase):
     def test_non_nullable_fk_not_promoted(self):
         qs = Book.objects.annotate(Count('contact__name'))
         self.assertIn(' INNER JOIN ', str(qs.query))
+
+
+class SelfReferentialFKTests(TestCase):
+    def test_ticket_24748(self):
+        t1 = SelfRefFK.objects.create(name='t1')
+        SelfRefFK.objects.create(name='t2', parent=t1)
+        SelfRefFK.objects.create(name='t3', parent=t1)
+        self.assertQuerysetEqual(
+            SelfRefFK.objects.annotate(num_children=Count('children')).order_by('name'),
+            [('t1', 2), ('t2', 0), ('t3', 0)],
+            lambda x: (x.name, x.num_children)
+        )