Reputation: 1381
I have two models, linked through a separate model as follow:
class Project(models.Model):
name = models.CharField(max_length=50, unique=True)
class Employee(models.Model):
name = models.CharField(max_length=50, unique=True)
project = models.ManyToManyField(
Project,
through='Schedule'
)
def __str__(self):
return self.name
class Schedule(models.Model):
employee = models.ForeignKey(Employee, on_delete=models.PROTECT)
project = models.ForeignKey(Project, on_delete=models.PROTECT)
date = models.DateField(null=False)
hours = models.IntegerField()
Serialized is pretty straight forward:
class EmployeeScheduleSerializer(serializers.ModelSerializer):
month = serializers.DateField()
booked = serializers.IntegerField()
class Meta:
model = Employee
fields = ['id', 'name', 'month', 'booked']
A viewset returns the total number of houses assigned to each employee for different projects, summed over dates:
class ScheduleViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Employee.objects.all()
serializer_class = EmployeeScheduleSerializer
lookup_field = 'employee'
def get_queryset(self):
qs = Employee.objects.values(
'name', month=F('schedule__date')
).annotate(
booked=Sum('schedule__hours', distinct=True)
)
if self.request.query_params.get('id') is not None:
qs = qs.filter(id=self.request.query_params.get('id'))
return qs
def retrieve(self, request, *args, **kwargs):
serializer = self.get_serializer(self.get_queryset(), many=True)
return Response(data=serializer.data)
In the output we have multiple records for each employee:
[
{
"name": "John Doe",
"month": "10/01/2020",
"booked": 100
},
{
"name": "John Doe",
"month": "11/01/2020",
"booked": 120
},
...
]
I want to convert it to a nested list so that the booked hours show up as a list for each employee. I checked multiple threads, but couldn't find a reasonable solution. I think it's possible to convert it to a grouped dataframe and output as JSON, but it wouldn't be part of the Django Rest framework.
As far as I know, Django ORM doesn't support nested queryset. Is there a workaround for that?
Upvotes: 0
Views: 92
Reputation: 801
You can create a EmployeeSerializer with a nested serializer with Schedule.
First, to improve reading, you should add a related name to schedule
class Schedule(models.Model):
employee = models.ForeignKey(Employee, on_delete=models.PROTECT, related_name="booked_hours")
project = models.ForeignKey(Project, on_delete=models.PROTECT)
date = models.DateField(null=False)
hours = models.IntegerField()
To have the booked hours, you can update the model :
class Employee(models.Model):
name = models.CharField(max_length=50, unique=True)
project = models.ManyToManyField(
Project,
through='Schedule'
)
def __str__(self):
return self.name
@property
def booked_hours_count:
return [ dict(date=date, count=self.booked_hours.filter(date=date).count()) for date in set(self.booked_hours.values_list('date', flat=True))]
and then in the serializer
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ['id', 'project', 'date', 'hours']
class EmployeeScheduleSerializer(serializers.ModelSerializer):
booked_hours = ScheduleSerializer(many=True)
class Meta:
model = Employee
fields = ['id', 'name', 'booked_hours', 'booked_hours_count']
Thanks to that, you will have :
[
{
"name": "John Doe",
"booked_hours": [
{
"project":...,
"hours"....,
"date"...
},
],
...
]
To optimize the queryset, you should not forget to use prefect_related
.
The viewset will be :
class ScheduleViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Employee.objects.all()
serializer_class = EmployeeScheduleSerializer
def get_queryset(self):
queryset = Employee.objects.all()
queryset = queryset.prefetch_related('booked_hours')
return queryset
ReadOnlyModelViewSet
is doing the rest of the job here.
Upvotes: 1