[Django]-Django aggregation Many To Many into list of dict

3👍

If you use postgres, you can do this:

subquery = Address.objects.filter(person_id=OuterRef("pk")).annotate(
    data=JSONObject(city=F("city"), zip=F("zip"))
).values_list("data")

persons = Persons.objects.annotate(addresses=ArraySubquery(subquery))

1👍

Your requirement: To make an aggregation of customized JSON objects after group_by (values) in Django.

Currently, to my knowledge, Django is not providing any function to aggregate manually created JSON objects. There are a couple of ways to solve this. Firstly, make a customized function which is quite laborious. However, there is another approach that is pretty much easy, using both aggregate functions (ArrayAgg or JSONBAgg) and RawSQL together.

from django.contrib.postgres.aggregates import JSONBAgg, ArrayAgg
result = Person.objects.values('name').annotate(addresses=JSONBAgg(RawSQL("json_build_object('city', city, 'zip', zip)", ())))

I hope it would help you.

👤Khalid

0👍

person.address already holds a queryset of addresses. From there you can use list-comprehension / model_from_dict to get the values you want.

0👍

Another option you can try, if the fields you need from the ManyToManyField are not much, is to chain the annotation and create a dictionary from it.

E.g

from django.contrib.postgres.aggregates import JSONBAgg
result = Person.objects.values(
   'name',
   addresses_cities=JSONBAgg('address__city')
   addresses_zips=JSONBAgg('address__zip')
)

then you can loop through the result queryset and create a dictionary from the address_cities and addresses_zips array like below:

address_dict=[
{"city": city, "zip": zip_} for city, zip_ in zip(result["addresses_cities"],result["addresses_zips"])
]

This works for me. I hope it helps.

Edit: 6/24/2023

Found a better way and thought to update this response.

from django.contrib.postgres.aggregates import JSONBAgg
from django.db.models import (
CharField,
Func,
SlugField,
Value,
)
 result = Person.objects.values(
   ...
  tags=ArrayAgg(
                Func(
                    Value("name"),#the name of the json key
                    "tags__name",
                    Value("slug"),
                    "tags__slug", #referencing the slug field from the Tags M2M Model
                    function="jsonb_build_object",
                    output_field=CharField, #this can be any field type. It's important to unify all fields especially if you are filtering for different field types. E.g above I was filtering for tags__slug (SlugField) and tags__name (CharField). Django threw an error that I have to specify a field type. Doing so fixed the error.
                ),
                distinct=True, #to prevent duplicates
            )
)

Leave a comment