【问题标题】:Django Filter timestamp data GROUP BY day, week, month, yearDjango过滤时间戳数据GROUP BY天,周,月,年
【发布时间】:2020-05-30 21:00:40
【问题描述】:

我有一个 django(DRF) 应用程序,我在其中存储基于 API 响应的周期性时间序列数据。这是我的 模型.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-filters 根据范围(__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']

我见过几个 sn-ps 进行聚合,但没有一个完全满足我的要求,也没有给我关于这个主题的完整概念。

我是 Django 的新手,并且一般都在构建分析仪表板,如果有任何其他方式可以在前端图表中表示此类时间序列数据以供消费,我也将不胜感激您的建议。

编辑: 这是我的serializer.py

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

【问题讨论】:

    标签: python django django-rest-framework django-orm django-filters


    【解决方案1】:

    首先,AlexaViewSet 类不是序列化程序而是 ViewSet。您没有在该 ViewSet 上指定序列化程序类,所以我需要指定它。

    另一方面,如果您想在 URL 上传递自定义查询参数,那么您应该重写此 ViewSet 的 list 方法并解析在 request 对象中传递的查询字符串以检索 @ 的值987654325@,验证它,然后自己执行聚合。

    我看到的另一个问题是你还需要定义什么是聚合一个 JSON 字段,这在 SQL 中是不支持的,而且是非常相对的,所以你可能要考虑重新设计如何存储这个 JSON 字段的信息如果您想对其中的字段进行聚合。我建议从 JSON 中提取要聚合的字段(将它们存储在数据库中时)并将它们单独放在 SQL 列中,以便稍后执行聚合。 客户端还可以将聚合操作作为查询参数传递,例如aggregation=sumaggregation=avg

    在一个简单的情况下,您只需要排名的平均值,这应该是有用的示例(您可以添加 TruncQuarter 等):

    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
        }
    ]
    

    【讨论】:

    • 感谢您的快速回复,我理解您的做法!你能帮我理解为什么会出现这个错误吗?我在尝试上面的代码时得到了这个。我已经添加了我的序列化程序,以防相关AttributeError at /api/alexa/ 'dict' object has no attribute 'extra'
    • 很高兴您能理解。你能给我更多关于你的错误的细节吗?也许整个堆栈跟踪会有所帮助。
    • 据我所知,错误似乎是我的序列化程序期望 dict 中有一个“额外”属性。每当我使用exclude = ['extra'] 将它从我的序列化程序中排除时,一切似乎都按预期工作。这是完整的stack trace
    猜你喜欢
    • 2021-05-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-18
    • 2015-10-21
    • 2021-04-29
    • 1970-01-01
    • 2010-10-05
    相关资源
    最近更新 更多