J'ai une application django (DRF) dans laquelle je stocke des données de séries temporelles périodiques basées sur la réponse de l'API. Voici mon 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)

J'utilise django-filters pour interroger des données en fonction d'une plage (__lte, __gte). Comme /api/alexa/?created_at__lte=2020-02-14T09:15:52.329641Z renvoie toutes les données créées avant 2020-02-14T09:15:52.329641Z

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

Existe-t-il un moyen de créer un point de terminaison pour renvoyer des données agrégées regroupées par jour, semaine, mois et année en fonction des paramètres de requête que je transmets. Par exemple, /api/alexa/?created_at__lte=2020-02-14T09:15:52.329641Z&group_by=month reviendrait

[
    {
        "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 
    },
 ]

Voici mes vues actuelles.py

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

J'ai vu plusieurs extraits faire l'agrégation, mais aucun ne répondait complètement à mes exigences ni ne me donnait une idée complète du sujet.

Je suis nouveau sur Django et je crée des tableaux de bord analytiques en général, s'il existe un autre moyen de représenter de telles données de séries temporelles pour la consommation dans des graphiques frontaux, j'apprécierais également vos suggestions à ce sujet.

EDIT : Voici mon serializer.py

class AlexaSerializer(serializers.ModelSerializer):
     class Meta:
         model = Alexa
         fields = '__all__'
1
Shouvik Mitra 15 févr. 2020 à 10:48

1 réponse

Meilleure réponse

Tout d'abord, la classe AlexaViewSet n'est pas un sérialiseur mais un ViewSet. Vous n'avez pas spécifié la classe du sérialiseur sur ce ViewSet, vous devez donc le spécifier.

D'un autre côté, si vous souhaitez passer un paramètre de requête personnalisé sur l'URL, vous devez remplacer la méthode list de ce ViewSet et analyser la chaîne de requête passée dans l'objet request pour récupérer la valeur de group_by, validez-le, puis effectuez vous-même l'agrégation.

Un autre problème que je vois est que vous devez également définir ce qu'est l'agrégation d'un champ JSON, qui n'est pas pris en charge dans SQL et qui est très relatif, vous pouvez donc envisager de reconcevoir la façon dont vous stockez les informations de ce champ JSON si vous le souhaitez. pour effectuer des agrégations sur les champs à l'intérieur. Je suggérerais d'extraire les champs que vous souhaitez agréger du JSON (lors de leur stockage dans la base de données) et de les placer séparément dans une colonne SQL afin que vous puissiez effectuer des agrégations plus tard. Le client peut également transmettre l'opération d'agrégation en tant que paramètre de requête, par exemple aggregation=sum ou aggregation=avg.

Dans un cas simple, où vous avez juste besoin de la moyenne des rangs, cela devrait être utile comme exemple (vous pouvez ajouter TruncQuarter, etc.) :

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)

Pour ces valeurs:

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
    }
]
1
Hernan 15 févr. 2020 à 19:52