HaBravo
HaBravo

Reputation: 3

Django REST serialize field group by date

Problem: Not able to group my JSON output by date

SOLUTION IN BOTTOM OF THIS POST

I am serializing a model and getting this output:

[
    {
        "date": "2020-11-24",
        "name": "Chest",
        "workout": {
            "name": "Chest",
            "exercise": 1,
            "repetitions": 10,
            "weight": 80
        }
    },
    {
        "date": "2020-11-24",
        "name": "Chest",
        "workout": {
            "name": "Chest",
            "exercise": 1,
            "repetitions": 10,
            "weight": 85
        }
    },
    {
        "date": "2020-11-24",
        "name": "Chest",
        "workout": {
            "name": "Chest",
            "exercise": 1,
            "repetitions": 10,
            "weight": 90
        }
    },

I want to get it like the JSON below and group it by date.

[
    {
        "date": "2020-11-24",
        "workout": {
            "name": "Chest",
            "exercise": 1,
            "repetitions": 10,
            "weight": 80
        },
            "name": "Chest",
            "exercise": 1,
            "repetitions": 10,
            "weight": 85
        },
            "name": "Chest",
            "exercise": 1,
            "repetitions": 10,
            "weight": 90
        },
    }
]

I have one model:

class WorkoutLog(models.Model):
    date = models.DateField()
    name = models.CharField(max_length=50) #When save() name = Workout.name
    exercise = models.ForeignKey('Exercise', related_name='log', on_delete=models.CASCADE)
    repetitions = models.IntegerField()
    weight = models.IntegerField()

Trying to serialize and group the JSON by date:

class WorkoutLogSerializer(serializers.ModelSerializer):
    class Meta:
        model = WorkoutLog
        fields = ['date', 'workout']
    
    workout = serializers.SerializerMethodField('get_workout')

    def get_workout(self, obj):
        return {
            'name': obj.name,
            'exercise': obj.exercise_id,
            'repetitions': obj.repetitions,
            'weight': obj.weight,
        }

The code lets me custom the field layout, but not really grouping it by date. Do you have any suggestions on how to structure it?

Many thanks for all help!

in case its needed, here is my view.py

def workout_log(request):
    if request.method == 'GET':
        workout_log = WorkoutLog.objects.all()
        serializer = WorkoutLogSerializer(workout_log, many=True)
        return JsonResponse(serializer.data, safe=False)

SOLUTION BY Mahmoud Adel:

views.py

def workout_log(request):
    if request.method == 'GET':
        workout_log = WorkoutLog.objects.order_by('date').values('date').distinct()
        serializer = WorkoutLogSerializer(workout_log, many=True)
        return JsonResponse(serializer.data, safe=False)

serializers.py

class WorkoutFieldSerializer(serializers.Serializer):
      name = serializers.CharField()
      #exercise = serializers.IntegerField()
      repetitions = serializers.IntegerField()
      weight = serializers.IntegerField()
      
class WorkoutLogSerializer(serializers.ModelSerializer):
    class Meta:
        model = WorkoutLog
        fields = ['date', 'workout']
    
    workout = serializers.SerializerMethodField('get_workout')

    def get_workout(self, obj):
        workouts = WorkoutLog.objects.filter(date=obj['date'])
        workout_serializer = WorkoutFieldSerializer(workouts, many=True)
        return workout_serializer.data

Upvotes: 0

Views: 1832

Answers (1)

Mahmoud Adel
Mahmoud Adel

Reputation: 1330

You can do something like this

Let's start first with your view, I will tweak the queryset like that

def workout_log(request):
    if request.method == 'GET':
        workout_log = WorkoutLog.objects.order_by('date').distinct('date').only('date')
        serializer = WorkoutLogSerializer(workout_log, many=True)
        return JsonResponse(serializer.data, safe=False)

Then on your WorkoutLogSerializer

class WorkoutLogSerializer(serializers.ModelSerializer):
    class Meta:
        model = WorkoutLog
        fields = ['date', 'workout']
    
    workout = serializers.SerializerMethodField('get_workout')

    def get_workout(self, obj):
        workouts = WorkoutLog.objects.filter(date=obj.date)
        workout_serializer = WorkoutSerializer(workouts, many=True)
        return workout_serializer.data

And finally, create WorkoutSerializer

class WorkoutSerializer(serializers.Serializers):
      name = serializers.CharField()
      exercise = serializers.IntegerField()
      repetitions = serializers.IntegerField()
      weight = serializers.IntegerField()

The previous way will first be got to the DB to get the distinct dates then on WorkoutLogSerializer will use each date to select the corresponding objects that have it, then we serialize those objects.

We get the intended result by doing so, note that this will result in 2 DB hits, there may be another way that will do it in one DB hit, I will update my answer if I have figured it out

NOTE: I have written this to explain the flow and the logic I didn't run it, although it should work I may forget something that will show an error for you, feel free to try it.

UPDATE: check this answer comments if you are using SQLite.

Upvotes: 2

Related Questions