Browse Source

Fixed #26327 -- Added JsonAgg to contrib.postgres.

Thanks Tim Graham for review.
Mads Jensen 8 years ago
parent
commit
0a26f3c338

+ 12 - 1
django/contrib/postgres/aggregates/general.py

@@ -1,7 +1,8 @@
+from django.contrib.postgres.fields import JSONField
 from django.db.models.aggregates import Aggregate
 
 __all__ = [
-    'ArrayAgg', 'BitAnd', 'BitOr', 'BoolAnd', 'BoolOr', 'StringAgg',
+    'ArrayAgg', 'BitAnd', 'BitOr', 'BoolAnd', 'BoolOr', 'JsonAgg', 'StringAgg',
 ]
 
 
@@ -30,6 +31,16 @@ class BoolOr(Aggregate):
     function = 'BOOL_OR'
 
 
+class JsonAgg(Aggregate):
+    function = 'JSONB_AGG'
+    _output_field = JSONField()
+
+    def convert_value(self, value, expression, connection, context):
+        if not value:
+            return []
+        return value
+
+
 class StringAgg(Aggregate):
     function = 'STRING_AGG'
     template = "%(function)s(%(distinct)s%(expressions)s, '%(delimiter)s')"

+ 9 - 0
docs/ref/contrib/postgres/aggregates.txt

@@ -58,6 +58,15 @@ General-purpose aggregation functions
     Returns ``True`` if at least one input value is true, ``None`` if all
     values are null or if there are no values, otherwise ``False``.
 
+``JsonAgg``
+-----------
+
+.. class:: JsonAgg(expressions, **extra)
+
+    .. versionadded:: 1.11
+
+    Returns the input values as a ``JSON`` array.
+
 ``StringAgg``
 -------------
 

+ 3 - 0
docs/releases/1.11.txt

@@ -172,6 +172,9 @@ Minor features
   operation allow using PostgreSQL's ``citext`` extension for case-insensitive
   lookups.
 
+* The new :class:`~django.contrib.postgres.aggregates.JsonAgg` allows
+  aggregating values as a JSON array.
+
 :mod:`django.contrib.redirects`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 22 - 5
tests/postgres_tests/test_aggregates.py

@@ -1,14 +1,21 @@
-from django.contrib.postgres.aggregates import (
-    ArrayAgg, BitAnd, BitOr, BoolAnd, BoolOr, Corr, CovarPop, RegrAvgX,
-    RegrAvgY, RegrCount, RegrIntercept, RegrR2, RegrSlope, RegrSXX, RegrSXY,
-    RegrSYY, StatAggregate, StringAgg,
-)
+import json
+
 from django.db.models.expressions import F, Value
+from django.test.testcases import skipUnlessDBFeature
 from django.test.utils import Approximate
 
 from . import PostgreSQLTestCase
 from .models import AggregateTestModel, StatTestModel
 
+try:
+    from django.contrib.postgres.aggregates import (
+        ArrayAgg, BitAnd, BitOr, BoolAnd, BoolOr, Corr, CovarPop, JsonAgg,
+        RegrAvgX, RegrAvgY, RegrCount, RegrIntercept, RegrR2, RegrSlope,
+        RegrSXX, RegrSXY, RegrSYY, StatAggregate, StringAgg,
+    )
+except ImportError:
+    pass  # psycopg2 is not installed
+
 
 class TestGeneralAggregate(PostgreSQLTestCase):
     @classmethod
@@ -110,6 +117,16 @@ class TestGeneralAggregate(PostgreSQLTestCase):
         values = AggregateTestModel.objects.aggregate(stringagg=StringAgg('char_field', delimiter=';'))
         self.assertEqual(values, {'stringagg': ''})
 
+    @skipUnlessDBFeature('has_jsonb_datatype')
+    def test_json_agg(self):
+        values = AggregateTestModel.objects.aggregate(jsonagg=JsonAgg('char_field'))
+        self.assertEqual(values, {'jsonagg': ['Foo1', 'Foo2', 'Foo3', 'Foo4']})
+
+    @skipUnlessDBFeature('has_jsonb_datatype')
+    def test_json_agg_empty(self):
+        values = AggregateTestModel.objects.none().aggregate(jsonagg=JsonAgg('integer_field'))
+        self.assertEqual(values, json.loads('{"jsonagg": []}'))
+
 
 class TestStringAggregateDistinct(PostgreSQLTestCase):
     @classmethod