pythondjangoapidjango-rest-frameworkdjango-queryset

Group by with related_name relation using Prefetch


I need to make an API with this body structure for graph with multiple lines:

[
  {
    "title": "Test title",
    "dots": [
      {
        "date": "2023-03-03",
        "sum_weight": 5000
      },
      {
        "date": "2023-03-06",
        "sum_weight": 1500
      }
    ]
  }
]

But I have a problem with Prefetch, since it's not possible to use .values() to do group_by date during query. Right now my ungrouped API looks like this:

[
  {
    "title": "Test title",
    "dots": [
      {
        "date": "2023-03-03",
        "sum_weight": 5000
      },
      {
        "date": "2023-03-06",
        "sum_weight": 500
      }, 
      {
        "date": "2023-03-06",
        "sum_weight": 1000
      }
    ]
  }
]

My code right now:

Query:

groups = Group.objects.prefetch_related(
    Prefetch("facts",
             queryset=Facts.objects.order_by("date").annotate(sum_weight=Sum("weight")))
)

Serializers:

class GraphDot(serializers.ModelSerializer):
    sum_weight = serializers.SerializerMethodField()

    def get_sum_weight(self, obj):
        if not hasattr(obj, 'sum_weight'):
            return 0
        return obj.sum_weight

    class Meta:
        model = Facts
        fields = read_only_fields = ("date", "sum_weight",)


class FoodGraphSerializer(serializers.ModelSerializer):
    dots = GraphDot(many=True, source="facts")

    class Meta:
        model = Group
        fields = read_only_fields = ("title", "dots")

Is there any way to make a query that is grouped by date so my sum_weight annotation is summed within it?


Solution

  • Thanks to @PTomasz comment, I've added Window expression to my query.

    So the final query looks like this:

    groups = Group.objects.prefetch_related(
        Prefetch("facts",
                 queryset=Facts.objects.annotate(
                     sum_weight=Window(
                         expression=Sum('weight'), 
                         partition_by=[F('date')],
                         order_by=OrderBy(F('date'))))
                 .distinct("date"))
    )
    

    Without .distinct("date") objects were still duplicating, but with right sum_weight (both had 1500).

    With it everyting is good.