diff --git a/AUTHORS b/AUTHORS index 99c71549aa..984b49f4f5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -756,6 +756,7 @@ answer newbie questions, and generally made Django that much better: tobias@neuyork.de Todd O'Bryan Tom Christie + Tom Forbes Tom Insam Tom Tobin Tomáš Ehrlich diff --git a/django/contrib/postgres/aggregates/general.py b/django/contrib/postgres/aggregates/general.py index ac18a516d6..91835a9ca3 100644 --- a/django/contrib/postgres/aggregates/general.py +++ b/django/contrib/postgres/aggregates/general.py @@ -8,6 +8,10 @@ __all__ = [ class ArrayAgg(Aggregate): function = 'ARRAY_AGG' + template = '%(function)s(%(distinct)s%(expressions)s)' + + def __init__(self, expression, distinct=False, **extra): + super().__init__(expression, distinct='DISTINCT ' if distinct else '', **extra) def convert_value(self, value, expression, connection, context): if not value: diff --git a/docs/ref/contrib/postgres/aggregates.txt b/docs/ref/contrib/postgres/aggregates.txt index 3939023e26..1c738c35ae 100644 --- a/docs/ref/contrib/postgres/aggregates.txt +++ b/docs/ref/contrib/postgres/aggregates.txt @@ -22,10 +22,17 @@ General-purpose aggregation functions ``ArrayAgg`` ------------ -.. class:: ArrayAgg(expression, **extra) +.. class:: ArrayAgg(expression, distinct=False, **extra) Returns a list of values, including nulls, concatenated into an array. + .. attribute:: distinct + + .. versionadded:: 2.0 + + An optional boolean argument that determines if array values + will be distinct. Defaults to ``False``. + ``BitAnd`` ---------- diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt index 4e42bd2b55..a43ae5e368 100644 --- a/docs/releases/2.0.txt +++ b/docs/releases/2.0.txt @@ -72,7 +72,9 @@ Minor features :mod:`django.contrib.postgres` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* ... +* The new ``distinct`` argument for + :class:`~django.contrib.postgres.aggregates.ArrayAgg` determines if + concatenated values will be distinct. :mod:`django.contrib.redirects` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/postgres_tests/test_aggregates.py b/tests/postgres_tests/test_aggregates.py index 9aa0e06595..056d08441b 100644 --- a/tests/postgres_tests/test_aggregates.py +++ b/tests/postgres_tests/test_aggregates.py @@ -128,7 +128,7 @@ class TestGeneralAggregate(PostgreSQLTestCase): self.assertEqual(values, json.loads('{"jsonagg": []}')) -class TestStringAggregateDistinct(PostgreSQLTestCase): +class TestAggregateDistinct(PostgreSQLTestCase): @classmethod def setUpTestData(cls): AggregateTestModel.objects.create(char_field='Foo') @@ -145,6 +145,14 @@ class TestStringAggregateDistinct(PostgreSQLTestCase): self.assertEqual(values['stringagg'].count('Foo'), 1) self.assertEqual(values['stringagg'].count('Bar'), 1) + def test_array_agg_distinct_false(self): + values = AggregateTestModel.objects.aggregate(arrayagg=ArrayAgg('char_field', distinct=False)) + self.assertEqual(sorted(values['arrayagg']), ['Bar', 'Foo', 'Foo']) + + def test_array_agg_distinct_true(self): + values = AggregateTestModel.objects.aggregate(arrayagg=ArrayAgg('char_field', distinct=True)) + self.assertEqual(sorted(values['arrayagg']), ['Bar', 'Foo']) + class TestStatisticsAggregate(PostgreSQLTestCase): @classmethod