[Fixed]-Django RestFramework group by

8👍

Let’s assume that the kwdGroup field is the relation field to a model called KeyWordGroup.

The default ListSerializer uses the to_representation method to render a list of the serialized objects rest_framework :

class ListSerializer(BaseSerializer):
     ...

    def to_representation(self, data):
        """
          List of object instances -> List of dicts of primitive datatypes.
        """
        # Dealing with nested relationships, data can be a Manager,
        # so, first get a queryset from the Manager if needed
        iterable = data.all() if isinstance(data, models.Manager) else data

        return [
            self.child.to_representation(item) for item in iterable
        ]

We can modify the ListSerializer to group the results for example:

class VocabListSerializer(serializers.ListSerializer):

    def to_representation(self, data):
        iterable = data.all() if isinstance(data, models.Manager) else data
        return {
            kwdGroup: super().to_representation(Vocab.objects.filter(kwdGroup=kwdGroup))
            for kwdGroup in KeyWordGroup.objects.all()
        }

We can then use the modified VocabListSerializer with the VocabSerializer.

class VocabSerializer(serializers.Serializer):
...
    class Meta:
        list_serializer_class = VocabListSerializer

3👍

One way to achieve this is to use a SerializerMethodField. The below might be slightly different than your use case, but you can adopt accordingly. There are other ways of achieving this as well, including overwriting to_representation methods, but they rely on messing with the inner workings of DRF more than is relevant here.

models.py

class Dictionary(Model):
    id = PrimaryKey


class Word(Model):
    dictionary = ForeignKey(Dictionary, related_name='words')
    word = Charfield()
    group = Charfield()

serializers.py

class WordSerializer(serializers.ModelSerializer):
    word = serializers.CharField(read_only=True)

    class Meta:
            model = Word
            fields = ('word',)


class DictionarySerializer(serializers.ModelSerializer):
    group_a = serializers.SerializerMethodField()
    group_b = serializers.SerializerMethodField()

    def get_group_a(self, instance):
        return WordSerializer(instance.words.filter(group='a'), many=True).data

    def get_group_b(self, instance):
        return WordSerializer(instance.words.filter(group='b'), many=True).data

    class Meta:
        model = Dictionary
        fields = ('group_a', 'group_b')

An example

>>> my_dictionary = Dictionary.objects.create()
>>> Word.objects.bulk_create(
        Word(word='arrow', group='a' dictionary=my_dictionary),
        Word(word='apple', group='a' dictionary=my_dictionary),
        Word(word='baby', group='b' dictionary=my_dictionary),
        Word(word='banana', group='b' dictionary=my_dictionary)
)
>>> serializer = DictionarySerializer(my_dictionary)
>>> print serializer.data
{
    'group_a': {
        'word': 'arrow',
        'word': 'apple'
    },
    'group_b': {
        'word': 'baby',
        'word': 'banana'
    },
}

0👍

If the previously mentioned VocabListSerializer doesn’t work, try this method:

class VocabListSerializer(serializers.ListSerializer):
    child:serializers.ModelSerializer

    def to_representation(self, data):

        iterable:Iterable[Sentence] = data.all() if isinstance(data, models.Manager) else data
        group_by_dict = {}
        for item in iterable : 
            kwdGroup_id = item.kwdGroup.id
            if kwdGroup_id in group_by_dict.keys ():
                group_by_dict[kwdGroup_id].append (self.child.to_representation(item))
            else : 
                group_by_dict [kwdGroup_id] = []

        return list(group_by_dict.values())

class VocabSerializer(serializers.Serializer):
    ...
    class Meta:
        list_serializer_class = VocabListSerializer

Note that the ListSerializer must create a list. Returning a dict like the original question requested does not seem possible by just overriding to_representation. If you don’t use

list(group_by_dict.values())

at the end, only the list of the foreign keys (kwdGroup) will be returned.

Leave a comment