Django REST Framework group by in nested model

Viewed 521

I'm new to Django REST Framework.

I have a nested model for saving orders of a restaurant. When I send a GET request I get the following response:

[
  {
    "menu": {
      "id": 1,
      "food_name": "food1"
    },
    "user": {
      "id": 49,
      "username": "A"
    }
  },
  {
    "menu": {
      "id": 1,
      "food_name": "food1"
    },
    "user": {
      "id": 63,
      "username": "B"
    }
  }
]

But I want to group users with the same menu like this:

[
  {
    "menu": {
      "id": 1,
      "food_name": "food1",
      "users": {
        "1": {
          "id": 49,
          "username": "A"
        },
        "2": {
          "id": 63,
          "username": "B"
        }
      }
    }
  }
]

Here is my code:

models.py

class Order(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    menu = models.ForeignKey(Menu, on_delete=models.CASCADE)

views.py

class OrderViewSet(viewsets.ModelViewSet):
    queryset = Order.objects.all()
    serializer_class = OrderSerializer

serilizers.py

class OrderSerializer(serializers.HyperlinkedModelSerializer):
    user = UserSerializer()
    menu = MenuSerializer()
    class Meta:
        model = Order
        fields = ['id', 'user', 'menu']

Thanks

2 Answers

Since your output suggests that you are working with menus, I suggest you create a separate viewset that works with menus and returns the expected output you want here.

Doing the expected results in the order viewset is possible, but is tricky to optimise using select_related and prefetch_related. But if you really want this in your order viewset, then you can do this with:

class MenuSerializer(serializers.ModelSerializer):
    users = serializers.SerializerMethodField()

    class Meta:
        model = Menu
        fields = ['id', 'food_name', 'users']

    def get_users(self, obj):
        users = User.objects.filter(pk__in=obj.order_set.all().values('user'))
        return UserSerializer(users, many=True).data


class OrderSerializer(serializers.HyperlinkedModelSerializer):
    menu = MenuSerializer()
    class Meta:
        model = Order
        fields = ['id', 'menu']

You also need to change the queryset of the view if you want distinct results, otherwise you will get duplicates:

class OrderViewSet(viewsets.ModelViewSet):
    queryset = Order.objects.all().distinct('menu')
    serializer_class = OrderSerializer

Do note that this is not optimised, and each row in your order table will require hitting the database just to get the users through menu. distinct('menu') also only works on PostgreSQL.

You can nest User into Menu if you want to group by menu

class OrderSerializer(serializers.HyperlinkedModelSerializer):
    menu = MenuSerializer(many=True)

    class Meta:
        model = Order
        fields = ['id', 'menu']


class MenuSerializer(serializers.ModelSerializer):
    user = UserSerializer(many=True)
    
    class Meta:
        model = Menu
        fields = ['id', 'user']
Related