Search code examples
djangoannotations

Annotations in django with model managers


I have two models with an one to many relation. One model named repairorder, which can have one or more instances of work that is performed on that order.

What I need is to annotate the Repairorder queryset to sum the cummulative Work duration. On the Work model I annotated the duration of a single Work instance based on the start and end date time stamps. Now I need to use this annotated field to sum the total cummulative Work that is performed for each order. I tried to extend the base model manager:

from django.db import models

class WorkManager(models.Manager):
    def get_queryset(self):
        return super(OrderholdManager, self).get_queryset().annotate(duration=ExpressionWrapper(Coalesce(F('enddate'), Now()) - F('startdate'), output_field=DurationField()))


class Work(models.Model):
    #...
    order_idorder = models.ForeignKey('Repairorder', models.DO_NOTHING)
    startdate = models.DateTimeField()
    enddate = models.DateTimeField()
    objects = WorkManager()


class RepairorderManager(models.Manager):
      def get_queryset(self):
          return super(RepairorderexternalManager, self).get_queryset().annotate(totalwork=Sum('work__duration'), output_field=DurationField())


class Repairorder(models.Model):
   #...
   idrepairorder = models.autofield(primary_key=True)
   objects = RepairorderManager()

For each Repairorder I want to display the 'totalwork', however this error appears: QuerySet.annotate() received non-expression(s): . and if I remove the output_field=DurationField() from the RepairorderMananager, it says: Cannot resolve keyword 'duration' into field.

Doing it the 'Python way' by using model properties is not an option with big datasets.


Solution

  • You will need to add the calculation to the RepairorderManager as well:

    class RepairorderManager(models.Manager):
        def get_queryset(self):
            return super(RepairorderexternalManager, self).get_queryset().annotate(
                totalwork=ExpressionWrapper(
                    Sum(Coalesce(F('work__enddate'), Now()) - F('work__startdate')),
                    output_field=DurationField()
                )
            )

    Django does not take into account annotations introduced by manager on related objects.