Прежде всего, класс AlexaViewSet
- это не сериализатор, а ViewSet. Вы не указали класс сериализатора в этом ViewSet, поэтому мне нужно указать это.
С другой стороны, если вы хотите передать пользовательский параметр запроса в URL, вам следует переопределить list
метод этого ViewSet и проанализируйте строку запроса, переданную в объекте request
, чтобы получить значение group_by
, проверить его, а затем выполнить агрегат самостоятельно.
Другая проблема, которую я вижу, состоит в том, что вам также нужно определить, что должно агрегировать поле JSON, которое не поддерживается в SQL, и оно очень относительное, поэтому вы можете рассмотреть вопрос о том, как изменить сохраните информацию этого поля JSON, если вы хотите выполнить агрегацию в полях внутри него. Я бы предложил извлечь поля, которые вы хотите агрегировать, из JSON (при их сохранении в базе данных) и поместить их в столбец SQL отдельно, чтобы вы могли выполнить агрегацию позже. Клиент также может передать операцию агрегирования в качестве параметра запроса, например aggregation=sum
или aggregation=avg
.
В простом случае, когда вам просто нужно среднее значение рангов, это может быть полезно в качестве примера (вы можете добавить TruncQuarter
, et c.):
class AlexaViewSet(viewsets.ModelViewSet):
serializer_class = AlexaSerializer
queryset = Alexa.objects.all()
filter_fields = {'created_at': ['iexact', 'lte', 'gte']}
http_method_names = ['get', 'post', 'head']
GROUP_CASTING_MAP = { # Used for outputing the reset datetime when grouping
'day': Cast(TruncDate('created_at'), output_field=DateTimeField()),
'month': Cast(TruncMonth('created_at'), output_field=DateTimeField()),
'week': Cast(TruncWeek('created_at'), output_field=DateTimeField()),
'year': Cast(TruncYear('created_at'), output_field=DateTimeField()),
}
GROUP_ANNOTATIONS_MAP = { # Defines the fields used for grouping
'day': {
'day': TruncDay('created_at'),
'month': TruncMonth('created_at'),
'year': TruncYear('created_at'),
},
'week': {
'week': TruncWeek('created_at')
},
'month': {
'month': TruncMonth('created_at'),
'year': TruncYear('created_at'),
},
'year': {
'year': TruncYear('created_at'),
},
}
def list(self, request, *args, **kwargs):
group_by_field = request.GET.get('group_by', None)
if group_by_field and group_by_field not in self.GROUP_CASTING_MAP.keys(): # validate possible values
return Response(status=status.HTTP_400_BAD_REQUEST)
queryset = self.filter_queryset(self.get_queryset())
if group_by_field:
queryset = queryset.annotate(**self.GROUP_ANNOTATIONS_MAP[group_by_field]) \
.values(*self.GROUP_ANNOTATIONS_MAP[group_by_field]) \
.annotate(rank=Avg('rank'), created_at=self.GROUP_CASTING_MAP[group_by_field]) \
.values('rank', 'created_at')
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
Для этих значений:
GET /alexa
[
{
"id": 1,
"created_at": "2020-03-16T12:04:59.096098Z",
"extra": "{}",
"rank": 2
},
{
"id": 2,
"created_at": "2020-02-15T12:05:01.907920Z",
"extra": "{}",
"rank": 64
},
{
"id": 3,
"created_at": "2020-02-15T12:05:03.890150Z",
"extra": "{}",
"rank": 232
},
{
"id": 4,
"created_at": "2020-02-15T12:05:06.357748Z",
"extra": "{}",
"rank": 12
}
]
GET /alexa/?group_by=day
[
{
"created_at": "2020-02-15T00:00:00Z",
"extra": null,
"rank": 102
},
{
"created_at": "2020-03-16T00:00:00Z",
"extra": null,
"rank": 2
}
]
GET /alexa/?group_by=week
[
{
"created_at": "2020-02-10T00:00:00Z",
"extra": null,
"rank": 102
},
{
"created_at": "2020-03-16T00:00:00Z",
"extra": null,
"rank": 2
}
]
GET /alexa/?group_by=month
[
{
"created_at": "2020-02-01T00:00:00Z",
"extra": null,
"rank": 102
},
{
"created_at": "2020-03-01T00:00:00Z",
"extra": null,
"rank": 2
}
]
GET /alexa/?group_by=year
[
{
"created_at": "2020-01-01T00:00:00Z",
"extra": null,
"rank": 77
}
]