Django Фильтрация временных меток данных GROUP BY день, неделя, месяц, год - PullRequest
0 голосов
/ 15 февраля 2020

У меня есть приложение django (DRF), в котором я храню данные периодов c временных рядов на основе ответа API. Вот мой model.py

# Model to store the Alexa API Data
class Alexa(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    extra = jsonfield.JSONField(null=True)
    rank =  models.PositiveIntegerField(default=0, null=True)

Я использую django -фильтры для запроса данных на основе диапазона (__lte, __gte). Как и /api/alexa/?created_at__lte=2020-02-14T09:15:52.329641Z вернуть все данные, созданные до 2020-02-14T09:15:52.329641Z

[
    {
        "id": 1,
        "created_at": "2020-02-03T19:30:57.868588Z",
        "extra": "{'load_time': 00, 'backlink': 0}",
        "rank": 0
    },
    ...
 ]

Есть ли способ построить конечную точку для возврата агрегированных данных, сгруппированных по дням, неделям, месяцам и годам на основе параметров запроса, которые я передаю , Например, /api/alexa/?created_at__lte=2020-02-14T09:15:52.329641Z&group_by=month вернет

[
    {
        "created_at": "2020-01-01T00:00:00.000000Z",
        "extra": "{'load_time': 00, 'backlink': 0}", <- Aggregated Data 
        "rank": 0                                    <- Aggregated Data
    },
    {
        "created_at": "2020-02-01T00:00:00.000000Z",
        "extra": "{'load_time': 00, 'backlink': 0}", <- Aggregated Data 
        "rank": 0                                    <- Aggregated Data 
    },
 ]

Вот мои текущие views.py

class AlexaViewSet(viewsets.ModelViewSet):
    queryset = Alexa.objects.all()
    filter_fields = {'created_at' : ['iexact', 'lte', 'gte']}
    http_method_names = ['get', 'post', 'head']

Я видел несколько фрагментов, выполняющих агрегацию, но ни один из них не полностью удовлетворяет моим требованиям и не дает мне полного Идея о топи c.

Я новичок в Django и вообще строю аналитические инструментальные панели, если есть какой-либо другой способ представления таких временных рядов для потребления в интерфейсных графах, я был бы признателен за ваши предложения по этому поводу.

РЕДАКТИРОВАТЬ: Вот мой serializer.py

class AlexaSerializer(serializers.ModelSerializer):
     class Meta:
         model = Alexa
         fields = '__all__'

1 Ответ

1 голос
/ 15 февраля 2020

Прежде всего, класс 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
    }
]
...