Search code examples
djangodjango-rest-framework

nesting a serializer by year then month


class Transactions(models.Model):
    id = models.CharField(max_length=100, primary_key=True)
    owner = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
    date = models.DateField()
    watch = models.CharField(max_length=100)
    purchase_price = models.IntegerField()
    sale_price = models.IntegerField()

My Transactions model has a date field, purchase price, and sale_price.

@api_view(['GET'])
@permission_classes([IsAuthenticatedOrReadOnly])
def profit_chart(request):
    current_year = datetime.now().year
    queryset = Transactions.objects.filter(date__year=current_year).values(month=ExtractMonth('date'),
                                                                           year=ExtractYear('date')).annotate(
        profit=Sum(F('sale_price')) - Sum(F('purchase_price'))).order_by('month')
    serializer = ProfitChartSerializer(queryset, many=True)
    return Response(serializer.data, status=status.HTTP_200_OK)

My view sorts transactions by the current year and month and then calculates the profit for that month.

class ProfitChartSerializer(serializers.ModelSerializer):
    year = serializers.IntegerField()
    month = serializers.IntegerField()
    profit = serializers.IntegerField()

    class Meta:
        model = Transactions
        fields = ['year', 'month', 'profit']

The response I get from DRF is below.

[
    {
        "year": 2024,
        "month": 8,
        "profit": 5000
    }
]

The question I am trying to figure out is how I can manipulate the above response to look like below.

[
    year:  2024
    data: {
        month: 8
        profit: 5000
    }
]

Solution

  • I don't think it makes much sense. Indeed, you filter by year:

    queryset = Transactions.objects.filter(date__year=current_year)

    so the year of all returned records will be the same.

    Here you can however customize the serializer with:

    class PartialChartSerializer(serializers.ModelSerializer):
        month = serializers.IntegerSerializer()
        profit = serializers.IntegerField()
    
        class Meta:
            model = Transactions
            fields = ['month', 'profit']
    
    
    class ProfitChartSerializer(serializers.Serializer):
        year = serializers.IntegerField()
        data = PartialChartSerializer(many=True)

    and feed the items to the serializer as:

    from itertools import groupby
    from operator import itemgetter
    
    
    @api_view(['GET'])
    @permission_classes([IsAuthenticatedOrReadOnly])
    def profit_chart(request):
        queryset = (
            Transactions.objects.values(
                month=ExtractMonth('date'), year=ExtractYear('date')
            )
            .annotate(profit=Sum(F('sale_price')) - Sum(F('purchase_price')))
            .order_by('month')
        )
        serializer = ProfitChartSerializer(
            [
                {'year': k, 'data': list(vs)}
                for k, vs in groupby(queryset, itemgetter('year'))
            ],
            many=True,
        )
        return Response(serializer.data, status=status.HTTP_200_OK)

    Note: Normally a Django model is given a singular name [django-antipatterns], so Transaction instead of Transactions.