8
8
from django .utils import timezone
9
9
from dateutil .relativedelta import relativedelta
10
10
from django .db .models import ExpressionWrapper , F , FloatField , Max , Min , Sum , Avg , Q
11
- from django .db .models .functions import Cast , TruncDate
11
+ from django .db .models .functions import Cast , TruncDay , TruncHour , TruncMinute , TruncMonth
12
12
from rest_framework import mixins , pagination , viewsets
13
13
14
- from ..models import SensorDataStat , LastActiveNodes , City , Node
15
- from .serializers import SensorDataStatSerializer , CitySerializer
14
+ from django .db import connection
15
+
16
+ from ..models import LastActiveNodes , City , Node
17
+ from .serializers import RawSensorDataStatSerializer , CitySerializer
16
18
17
19
from feinstaub .sensors .views import StandardResultsSetPagination
18
20
@@ -76,7 +78,8 @@ def get_paginated_response(self, data_stats):
76
78
results [city_slug ][value_type ] = [] if from_date else {}
77
79
78
80
values = results [city_slug ][value_type ]
79
- include_result = getattr (values , "append" if from_date else "update" )
81
+ include_result = getattr (
82
+ values , "append" if from_date else "update" )
80
83
include_result (
81
84
{
82
85
"average" : data_stat ["average" ],
@@ -98,8 +101,7 @@ def get_paginated_response(self, data_stats):
98
101
99
102
100
103
class SensorDataStatView (mixins .ListModelMixin , viewsets .GenericViewSet ):
101
- queryset = SensorDataStat .objects .none ()
102
- serializer_class = SensorDataStatSerializer
104
+ serializer_class = RawSensorDataStatSerializer
103
105
pagination_class = CustomPagination
104
106
105
107
@method_decorator (cache_page (3600 ))
@@ -112,60 +114,31 @@ def get_queryset(self):
112
114
city_slugs = self .request .query_params .get ("city" , None )
113
115
from_date = self .request .query_params .get ("from" , None )
114
116
to_date = self .request .query_params .get ("to" , None )
117
+ avg = self .request .query_params .get ("avg" , 'day' )
115
118
116
119
if to_date and not from_date :
117
- raise ValidationError ({"from" : "Must be provide along with to query" })
120
+ raise ValidationError (
121
+ {"from" : "Must be provide along with to query" })
118
122
if from_date :
119
- validate_date (from_date , {"from" : "Must be a date in the format Y-m-d." })
123
+ validate_date (
124
+ from_date , {"from" : "Must be a date in the format Y-m-d." })
120
125
if to_date :
121
- validate_date (to_date , {"to" : "Must be a date in the format Y-m-d." })
126
+ validate_date (
127
+ to_date , {"to" : "Must be a date in the format Y-m-d." })
122
128
123
- value_type_to_filter = self .request .query_params .get ("value_type" , None )
129
+ value_type_to_filter = self .request .query_params .get (
130
+ "value_type" , None )
124
131
125
132
filter_value_types = value_types [sensor_type ]
126
133
if value_type_to_filter :
127
- filter_value_types = set (value_type_to_filter .upper ().split ("," )) & set (
134
+ filter_value_types = "," . join ( set (value_type_to_filter .upper ().split ("," )) & set (
128
135
[x .upper () for x in value_types [sensor_type ]]
129
- )
136
+ ))
130
137
131
138
if not from_date and not to_date :
132
- return self ._retrieve_past_24hrs (city_slugs , filter_value_types )
133
-
134
- return self ._retrieve_range (from_date , to_date , city_slugs , filter_value_types )
135
-
136
- @staticmethod
137
- def _retrieve_past_24hrs (city_slugs , filter_value_types ):
138
- to_date = timezone .now ().replace (minute = 0 , second = 0 , microsecond = 0 )
139
- from_date = to_date - datetime .timedelta (hours = 24 )
140
-
141
- queryset = SensorDataStat .objects .filter (
142
- value_type__in = filter_value_types ,
143
- timestamp__gte = from_date ,
144
- timestamp__lte = to_date ,
145
- )
146
-
147
- if city_slugs :
148
- queryset = queryset .filter (city_slug__in = city_slugs .split ("," ))
149
-
150
- return (
151
- queryset .order_by ()
152
- .values ("value_type" , "city_slug" )
153
- .annotate (
154
- start_datetime = Min ("timestamp" ),
155
- end_datetime = Max ("timestamp" ),
156
- average = ExpressionWrapper (
157
- Sum (F ("average" ) * F ("sample_size" )) / Sum ("sample_size" ),
158
- output_field = FloatField (),
159
- ),
160
- minimum = Min ("minimum" ),
161
- maximum = Max ("maximum" ),
162
- )
163
- .order_by ("city_slug" )
164
- )
165
-
166
- @staticmethod
167
- def _retrieve_range (from_date , to_date , city_slugs , filter_value_types ):
168
- if not to_date :
139
+ to_date = timezone .now ().replace (minute = 0 , second = 0 , microsecond = 0 )
140
+ from_date = to_date - datetime .timedelta (hours = 24 )
141
+ elif not to_date :
169
142
from_date = beginning_of_day (from_date )
170
143
# Get data from_date until the end
171
144
# of day yesterday which is the beginning of today
@@ -174,31 +147,36 @@ def _retrieve_range(from_date, to_date, city_slugs, filter_value_types):
174
147
from_date = beginning_of_day (from_date )
175
148
to_date = end_of_day (to_date )
176
149
177
- queryset = SensorDataStat .objects .filter (
178
- value_type__in = filter_value_types ,
179
- timestamp__gte = from_date ,
180
- timestamp__lt = to_date ,
181
- )
182
-
183
- if city_slugs :
184
- queryset = queryset .filter (city_slug__in = city_slugs .split ("," ))
185
-
186
- return (
187
- queryset .annotate (date = TruncDate ("timestamp" ))
188
- .values ("date" , "value_type" )
189
- .annotate (
190
- city_slug = F ("city_slug" ),
191
- start_datetime = Min ("timestamp" ),
192
- end_datetime = Max ("timestamp" ),
193
- average = ExpressionWrapper (
194
- Sum (F ("average" ) * F ("sample_size" )) / Sum ("sample_size" ),
195
- output_field = FloatField (),
196
- ),
197
- minimum = Min ("minimum" ),
198
- maximum = Max ("maximum" ),
199
- )
200
- .order_by ("-date" )
201
- )
150
+ with connection .cursor () as cursor :
151
+ cursor .execute (
152
+ """
153
+ SELECT
154
+ sl.city as city_slug,
155
+ min(sd."timestamp") as start_datetime,
156
+ max(sd."timestamp") as end_datetime,
157
+ sum(CAST("value" as float)) / COUNT(*) AS average,
158
+ min(CAST("value" as float)) as minimum,
159
+ max(CAST("value" as float)) as maximum,
160
+ v.value_type
161
+ FROM
162
+ sensors_sensordatavalue v
163
+ INNER JOIN sensors_sensordata sd ON sd.id = sensordata_id
164
+ INNER JOIN sensors_sensorlocation sl ON sl.id = location_id
165
+ WHERE
166
+ v.value_type IN (%s)
167
+ """
168
+ +
169
+ ("AND sl.city IN (%s)" if city_slugs else "" )
170
+ +
171
+ """
172
+ AND sd."timestamp" >= TIMESTAMP %s
173
+ AND sd."timestamp" <= TIMESTAMP %s
174
+ GROUP BY
175
+ DATE_TRUNC(%s, sd."timestamp"),
176
+ v.value_type,
177
+ sl.city
178
+ """ , [filter_value_types , city_slugs , from_date , to_date , avg ] if city_slugs else [filter_value_types , from_date , to_date , avg ])
179
+ return cursor .fetchall ()
202
180
203
181
204
182
class CityView (mixins .ListModelMixin , viewsets .GenericViewSet ):
@@ -225,7 +203,8 @@ def list(self, request):
225
203
moved_to = None
226
204
# Get data stats from 5mins before last_data_received_at
227
205
if last_data_received_at :
228
- last_5_mins = last_data_received_at - datetime .timedelta (minutes = 5 )
206
+ last_5_mins = last_data_received_at - \
207
+ datetime .timedelta (minutes = 5 )
229
208
stats = (
230
209
SensorDataValue .objects .filter (
231
210
Q (sensordata__sensor__node = last_active .node .id ),
0 commit comments