brewcrazy
brewcrazy

Reputation: 665

DRF: Serializer Group By Model Field

I want my api to return Account objects grouped by the account_type field in the model. I want to do this so that the grouped accounts are easier to access in my JS code. This is what is returned now:

[{
  "id": 6,
  "owner": 1,
  "account_name": "Credit Card 1",
  "account_type": "credit card",
  "created_time": "2019-07-18T02:57:44.288654Z",
  "modified_time": "2019-07-18T02:57:44.288842Z"
}, {
  "id": 11,
  "owner": 1,
  "account_name": "Savings 1",
  "account_type": "savings",
  "created_time": "2019-07-18T03:00:22.122226Z",
  "modified_time": "2019-07-18T03:00:22.122283Z"
}, {
  "id": 5,
  "owner": 1,
  "account_name": "Checking 1",
  "account_type": "checking",
  "created_time": "2019-07-18T02:57:28.580268Z",
  "modified_time": "2019-07-18T02:57:28.580305Z"
}, {
  "id": 9,
  "owner": 1,
  "account_name": "Savings 2",
  "account_type": "savings",
  "created_time": "2019-07-18T02:59:57.156837Z",
  "modified_time": "2019-07-18T02:59:57.156875Z"
}, {
  "id": 10,
  "owner": 1,
  "account_name": "Savings 3",
  "account_type": "savings",
  "created_time": "2019-07-18T03:00:12.873799Z",
  "modified_time": "2019-07-18T03:00:12.873846Z"
}, {
  "id": 7,
  "owner": 1,
  "account_name": "Credit Card 2",
  "account_type": "credit card",
  "created_time": "2019-07-18T02:57:55.921586Z",
  "modified_time": "2019-07-18T02:57:55.921613Z"
}]

And I'd like it to return something like this:

{
  "credit card": [
    {   "id": 6,
        "owner": 1,
        "account_name": "Credit Card 1",
        "account_type": "credit card",
        "created_time": "2019-07-18T02:57:44.288654Z",
        "modified_time": "2019-07-18T02:57:44.288842Z"
    },
    {
        "id": 7,
        "owner": 1,
        "account_name": "Credit Card 2",
        "account_type": "credit card",
        "created_time": "2019-07-18T02:57:55.921586Z",
        "modified_time": "2019-07-18T02:57:55.921613Z"
    }  
  ],
  "savings": [
    {
        "id": 11,
        "owner": 1,
        "account_name": "Savings 1",
        "account_type": "savings",
        "created_time": "2019-07-18T03:00:22.122226Z",
        "modified_time": "2019-07-18T03:00:22.122283Z"
    },
    {
        "id": 9,
        "owner": 1,
        "account_name": "Savings 2",
        "account_type": "savings",
        "created_time": "2019-07-18T02:59:57.156837Z",
        "modified_time": "2019-07-18T02:59:57.156875Z"
    },
    {
        "id": 10,
        "owner": 1,
        "account_name": "Savings 3",
        "account_type": "savings",
        "created_time": "2019-07-18T03:00:12.873799Z",
        "modified_time": "2019-07-18T03:00:12.873846Z"
    }
  ],
  "checking": [
    {
        "id": 5,
        "owner": 1,
        "account_name": "Checking 1",
        "account_type": "checking",
        "created_time": "2019-07-18T02:57:28.580268Z",
        "modified_time": "2019-07-18T02:57:28.580305Z"
    }
  ]
}

Model:

class Account(models.Model):
    CHECKING = 'checking'
    CREDIT_CARD = 'credit card'
    SAVINGS = 'savings'

    ACCOUNT_TYPE_CHOICES = [
        (CHECKING, 'Checking'),
        (CREDIT_CARD, 'Credit Card'),
        (SAVINGS, 'Savings'),
    ]

    owner = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE
    )
    account_name = models.CharField(
        max_length=128,
        null=False
    )
    account_type = models.CharField(
        max_length=128,
        null=False,
        choices=ACCOUNT_TYPE_CHOICES
    )
    created_time = models.DateTimeField(auto_now_add=True)
    modified_time = models.DateTimeField(auto_now=True)

Serializer:

class AccountSerializer(serializers.ModelSerializer):

    class Meta:
        model = Account
        fields = ('id',
                  'owner',
                  'account_name',
                  'account_type',
                  'created_time',
                  'modified_time')

    def create(self, validated_data):
        account = Account.objects.create(**validated_data)
        return account

Edit: Adding my views.py because I'm curious if that's the issue.

class AccountViewSet(viewsets.ModelViewSet):
    queryset = Account.objects.all().order_by('account_name')
    # serializer_class = AccountSerializer
    serializer_class = AccountByTypeSerializer

    def list(self, request, *args, **kwargs):
        page = self.paginate_queryset(self.queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

    def retrieve(self, request, pk, *args, **kwargs):

        instance = self.get_object()
        serializer = self.get_serializer(instance)
        return Response(serializer.data)

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data,
                        status=status.HTTP_201_CREATED,
                        headers=headers)

    def update(self, request, *args, **kwargs):
        # partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance,
                                         data=request.data,
                                         partial=True)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        return Response(serializer.data)

Upvotes: 7

Views: 3910

Answers (1)

Kevin Languasco
Kevin Languasco

Reputation: 2426

Since you have only a limited amount of account type choices, you can use SerializerMethodFields, like this:

class AccountByTypeSerializer(serializers.ModelSerializer):
    checking = serializers.SerializerMethodField()
    savings = serializers.SerializerMethodField()
    credit_card = serializers.SerializerMethodField()

    def _build_account_list(account_type):
        accounts = Account.objects.filter(account_type=account_type)
        serializer = AccountSerializer(accounts, many=True)
        return serializer.data

    def get_savings(self, obj):
        return self._build_account_list(Account.SAVINGS)

    ...

    class Meta:
        model = Account
        fields = ('checking', 'savings', 'credit_card')

I suggest you to use credit_card instead of credit card, but if you really need to use the latter, just override to_representation.

Upvotes: 5

Related Questions